diff --git a/.clang-format b/.clang-format
index efa5f1c3c4..56804459cb 100644
--- a/.clang-format
+++ b/.clang-format
@@ -68,6 +68,7 @@ IncludeCategories:
IncludeIsMainRegex: '$'
IndentCaseLabels: false
IndentWidth: 4
+AccessModifierOffset: -4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: '.*_BEGIN$' # only PREC_BEGIN ?
diff --git a/.gitmodules b/.gitmodules
index 7b390efe57..699d5526f7 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -64,12 +64,12 @@
[submodule "roms/vbootrom"]
path = roms/vbootrom
url = https://gitlab.com/qemu-project/vbootrom.git
-[submodule "ui/imgui"]
- path = ui/imgui
+[submodule "ui/thirdparty/imgui"]
+ path = ui/thirdparty/imgui
url = https://github.com/ocornut/imgui.git
ignore = untracked
-[submodule "ui/implot"]
- path = ui/implot
+[submodule "ui/thirdparty/implot"]
+ path = ui/thirdparty/implot
url = https://github.com/epezent/implot.git
ignore = untracked
[submodule "hw/xbox/nv2a/xxHash"]
diff --git a/build.sh b/build.sh
index 0163be239d..10d40de3bc 100755
--- a/build.sh
+++ b/build.sh
@@ -272,11 +272,6 @@ set -x # Print commands from now on
${opts} \
"$@"
-# Force imgui update now to work around annoying make issue
-if ! test -f "${project_source_dir}/ui/imgui/imgui.cpp"; then
- ./scripts/git-submodule.sh update ui/imgui
-fi
-
time make -j"${job_count}" ${target} 2>&1 | tee build.log
"${postbuild}" # call post build functions
diff --git a/config_spec.yml b/config_spec.yml
index d58b01e1f3..b1f7e9d538 100644
--- a/config_spec.yml
+++ b/config_spec.yml
@@ -6,8 +6,10 @@ general:
check:
type: bool
default: true
- misc:
- skip_boot_anim: bool
+ screenshot_dir: string
+ skip_boot_anim: bool
+ # throttle_io: bool
+ last_viewed_menu_index: integer
user_token: string
input:
@@ -16,6 +18,11 @@ input:
port2: string
port3: string
port4: string
+ gamecontrollerdb_path: string
+ auto_bind:
+ type: bool
+ default: true
+ background_input_capture: bool
display:
quality:
@@ -23,9 +30,27 @@ display:
type: integer
default: 1
window:
- last_width: integer
- last_height: integer
+ fullscreen_on_startup: bool
+ startup_size:
+ type: enum
+ values: [last_used, 640x480, 1280x720, 1280x800, 1280x960, 1920x1080, 2560x1440, 2560x1600, 2560x1920, 3840x2160]
+ default: 1280x960
+ last_width:
+ type: integer
+ default: 640
+ last_height:
+ type: integer
+ default: 480
+ vsync:
+ type: bool
+ default: true
ui:
+ show_menubar:
+ type: bool
+ default: true
+ use_animations:
+ type: bool
+ default: true
fit:
type: enum
values: [center, scale, scale_16_9, scale_4_3, stretch]
@@ -33,9 +58,15 @@ display:
scale:
type: integer
default: 1
+ auto_scale:
+ type: bool
+ default: true
audio:
use_dsp: bool
+ volume_limit:
+ type: number
+ default: 1
net:
enable: bool
@@ -52,12 +83,26 @@ net:
remote_addr:
type: string
default: 1.2.3.4:9368
+ nat:
+ forward_ports:
+ type: array
+ items:
+ host: integer
+ guest: integer
+ protocol:
+ type: enum
+ values: [tcp, udp]
+ default: tcp
sys:
mem_limit:
type: enum
values: ['64', '128']
default: '64'
+ avpack:
+ type: enum
+ values: [scart, hdtv, vga, rfu, svideo, composite, none]
+ default: hdtv
files:
bootrom_path: string
flashrom_path: string
@@ -69,3 +114,6 @@ perf:
hard_fpu:
type: bool
default: true
+ # cache_shaders:
+ # type: bool
+ # default: true
diff --git a/configure b/configure
index f71e805409..34fea06dba 100755
--- a/configure
+++ b/configure
@@ -261,7 +261,7 @@ else
git_submodules_action="ignore"
fi
-git_submodules="ui/keycodemapdb ui/imgui ui/implot util/xxHash"
+git_submodules="ui/keycodemapdb ui/thirdparty/imgui ui/thirdparty/implot util/xxHash tomlplusplus genconfig"
git="git"
# Don't accept a target_list environment variable.
diff --git a/data/roboto_medium.ttf b/data/Roboto-Medium.ttf
similarity index 97%
rename from data/roboto_medium.ttf
rename to data/Roboto-Medium.ttf
index f714a514d9..e89b0b79a2 100644
Binary files a/data/roboto_medium.ttf and b/data/Roboto-Medium.ttf differ
diff --git a/data/RobotoCondensed-Regular.ttf b/data/RobotoCondensed-Regular.ttf
new file mode 100644
index 0000000000..17e8ea57b1
Binary files /dev/null and b/data/RobotoCondensed-Regular.ttf differ
diff --git a/data/abxy.svg b/data/abxy.svg
new file mode 100644
index 0000000000..9adde380e1
--- /dev/null
+++ b/data/abxy.svg
@@ -0,0 +1,156 @@
+
+
+
+
diff --git a/data/abxy.ttf b/data/abxy.ttf
new file mode 100644
index 0000000000..d7b4b2f1d1
Binary files /dev/null and b/data/abxy.ttf differ
diff --git a/data/abxy_font.svg b/data/abxy_font.svg
new file mode 100644
index 0000000000..caaeb154ce
--- /dev/null
+++ b/data/abxy_font.svg
@@ -0,0 +1,161 @@
+
+
+
+
diff --git a/data/font_awesome_6_1_1_solid.otf b/data/font_awesome_6_1_1_solid.otf
new file mode 100644
index 0000000000..2452af5832
Binary files /dev/null and b/data/font_awesome_6_1_1_solid.otf differ
diff --git a/data/meson.build b/data/meson.build
index 785e335a40..89e6123d90 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -2,7 +2,10 @@ pfiles = [
'controller_mask.png',
'logo_sdf.png',
'xemu_64x64.png',
- 'roboto_medium.ttf',
+ 'abxy.ttf',
+ 'Roboto-Medium.ttf',
+ 'RobotoCondensed-Regular.ttf',
+ 'font_awesome_6_1_1_solid.otf',
]
libpfile_targets = []
diff --git a/genconfig b/genconfig
index 8220e87489..5da3fd2463 160000
--- a/genconfig
+++ b/genconfig
@@ -1 +1 @@
-Subproject commit 8220e8748922ddd9bba4cbacd608601586c9a4bb
+Subproject commit 5da3fd2463288d9e048dbf3ea41f2bad0a4287a8
diff --git a/hw/xbox/mcpx/apu.c b/hw/xbox/mcpx/apu.c
index 1344793337..cf34f14197 100644
--- a/hw/xbox/mcpx/apu.c
+++ b/hw/xbox/mcpx/apu.c
@@ -297,6 +297,27 @@ void mcpx_apu_debug_toggle_mute(uint16_t v)
g_dbg_muted_voices[v / 64] ^= (1LL << (v % 64));
}
+static void mcpx_apu_update_dsp_preference(MCPXAPUState *d)
+{
+ static int last_known_preference = -1;
+
+ if (last_known_preference == (int)g_config.audio.use_dsp) {
+ return;
+ }
+
+ if (g_config.audio.use_dsp) {
+ d->mon = MCPX_APU_DEBUG_MON_GP_OR_EP;
+ d->gp.realtime = true;
+ d->ep.realtime = true;
+ } else {
+ d->mon = MCPX_APU_DEBUG_MON_VP;
+ d->gp.realtime = false;
+ d->ep.realtime = false;
+ }
+
+ last_known_preference = g_config.audio.use_dsp;
+}
+
static float clampf(float v, float min, float max)
{
if (v < min) {
@@ -2050,6 +2071,7 @@ static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2],
static void se_frame(MCPXAPUState *d)
{
+ mcpx_apu_update_dsp_preference(d);
mcpx_debug_begin_frame();
g_dbg.gp_realtime = d->gp.realtime;
g_dbg.ep_realtime = d->ep.realtime;
@@ -2137,6 +2159,7 @@ static void se_frame(MCPXAPUState *d)
d->apu_fifo_output[off + i][0] += isamp[2*i];
d->apu_fifo_output[off + i][1] += isamp[2*i+1];
}
+
memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf));
memset(mixbins, 0, sizeof(mixbins));
}
@@ -2198,6 +2221,15 @@ static void se_frame(MCPXAPUState *d)
fwrite(d->apu_fifo_output, sizeof(d->apu_fifo_output), 1, fd);
fclose(fd);
#endif
+
+ if (0 <= g_config.audio.volume_limit && g_config.audio.volume_limit < 1) {
+ float f = pow(g_config.audio.volume_limit, M_E);
+ for (int i = 0; i < 256; i++) {
+ d->apu_fifo_output[i][0] *= f;
+ d->apu_fifo_output[i][1] *= f;
+ }
+ }
+
qemu_spin_lock(&d->vp.out_buf_lock);
int num_bytes_free = fifo8_num_free(&d->vp.out_buf);
assert(num_bytes_free >= sizeof(d->apu_fifo_output));
@@ -2624,15 +2656,7 @@ void mcpx_apu_init(PCIBus *bus, int devfn, MemoryRegion *ram)
/* Until DSP is more performant, a switch to decide whether or not we should
* use the full audio pipeline or not.
*/
- if (g_config.audio.use_dsp) {
- d->mon = MCPX_APU_DEBUG_MON_GP_OR_EP;
- d->gp.realtime = true;
- d->ep.realtime = true;
- } else {
- d->mon = MCPX_APU_DEBUG_MON_VP;
- d->gp.realtime = false;
- d->ep.realtime = false;
- }
+ mcpx_apu_update_dsp_preference(d);
qemu_thread_create(&d->apu_thread, "mcpx.apu_thread", mcpx_apu_frame_thread,
d, QEMU_THREAD_JOINABLE);
diff --git a/licenses/fontawesome.license.txt b/licenses/fontawesome.license.txt
new file mode 100644
index 0000000000..cc557ece45
--- /dev/null
+++ b/licenses/fontawesome.license.txt
@@ -0,0 +1,165 @@
+Fonticons, Inc. (https://fontawesome.com)
+
+--------------------------------------------------------------------------------
+
+Font Awesome Free License
+
+Font Awesome Free is free, open source, and GPL friendly. You can use it for
+commercial projects, open source projects, or really almost whatever you want.
+Full Font Awesome Free license: https://fontawesome.com/license/free.
+
+--------------------------------------------------------------------------------
+
+# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)
+
+The Font Awesome Free download is licensed under a Creative Commons
+Attribution 4.0 International License and applies to all icons packaged
+as SVG and JS file types.
+
+--------------------------------------------------------------------------------
+
+# Fonts: SIL OFL 1.1 License
+
+In the Font Awesome Free download, the SIL OFL license applies to all icons
+packaged as web and desktop font files.
+
+Copyright (c) 2022 Fonticons, Inc. (https://fontawesome.com)
+with Reserved Font Name: "Font Awesome".
+
+This Font Software is licensed under the SIL Open Font License, Version 1.1.
+This license is copied below, and is also available with a FAQ at:
+http://scripts.sil.org/OFL
+
+SIL OPEN FONT LICENSE
+Version 1.1 - 26 February 2007
+
+PREAMBLE
+The goals of the Open Font License (OFL) are to stimulate worldwide
+development of collaborative font projects, to support the font creation
+efforts of academic and linguistic communities, and to provide a free and
+open framework in which fonts may be shared and improved in partnership
+with others.
+
+The OFL allows the licensed fonts to be used, studied, modified and
+redistributed freely as long as they are not sold by themselves. The
+fonts, including any derivative works, can be bundled, embedded,
+redistributed and/or sold with any software provided that any reserved
+names are not used by derivative works. The fonts and derivatives,
+however, cannot be released under any other type of license. The
+requirement for fonts to remain under this license does not apply
+to any document created using the fonts or their derivatives.
+
+DEFINITIONS
+"Font Software" refers to the set of files released by the Copyright
+Holder(s) under this license and clearly marked as such. This may
+include source files, build scripts and documentation.
+
+"Reserved Font Name" refers to any names specified as such after the
+copyright statement(s).
+
+"Original Version" refers to the collection of Font Software components as
+distributed by the Copyright Holder(s).
+
+"Modified Version" refers to any derivative made by adding to, deleting,
+or substituting — in part or in whole — any of the components of the
+Original Version, by changing formats or by porting the Font Software to a
+new environment.
+
+"Author" refers to any designer, engineer, programmer, technical
+writer or other person who contributed to the Font Software.
+
+PERMISSION & CONDITIONS
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of the Font Software, to use, study, copy, merge, embed, modify,
+redistribute, and sell modified and unmodified copies of the Font
+Software, subject to the following conditions:
+
+1) Neither the Font Software nor any of its individual components,
+in Original or Modified Versions, may be sold by itself.
+
+2) Original or Modified Versions of the Font Software may be bundled,
+redistributed and/or sold with any software, provided that each copy
+contains the above copyright notice and this license. These can be
+included either as stand-alone text files, human-readable headers or
+in the appropriate machine-readable metadata fields within text or
+binary files as long as those fields can be easily viewed by the user.
+
+3) No Modified Version of the Font Software may use the Reserved Font
+Name(s) unless explicit written permission is granted by the corresponding
+Copyright Holder. This restriction only applies to the primary font name as
+presented to the users.
+
+4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
+Software shall not be used to promote, endorse or advertise any
+Modified Version, except to acknowledge the contribution(s) of the
+Copyright Holder(s) and the Author(s) or with their explicit written
+permission.
+
+5) The Font Software, modified or unmodified, in part or in whole,
+must be distributed entirely under this license, and must not be
+distributed under any other license. The requirement for fonts to
+remain under this license does not apply to any document created
+using the Font Software.
+
+TERMINATION
+This license becomes null and void if any of the above conditions are
+not met.
+
+DISCLAIMER
+THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
+OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
+COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
+DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
+OTHER DEALINGS IN THE FONT SOFTWARE.
+
+--------------------------------------------------------------------------------
+
+# Code: MIT License (https://opensource.org/licenses/MIT)
+
+In the Font Awesome Free download, the MIT license applies to all non-font and
+non-icon files.
+
+Copyright 2022 Fonticons, Inc.
+
+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.
+
+--------------------------------------------------------------------------------
+
+# Attribution
+
+Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font
+Awesome Free files already contain embedded comments with sufficient
+attribution, so you shouldn't need to do anything additional when using these
+files normally.
+
+We've kept attribution comments terse, so we ask that you do not actively work
+to remove them from files, especially code. They're a great way for folks to
+learn about Font Awesome.
+
+--------------------------------------------------------------------------------
+
+# Brand Icons
+
+All brand icons are trademarks of their respective owners. The use of these
+trademarks does not indicate endorsement of the trademark holder by Font
+Awesome, nor vice versa. **Please do not use brand logos for any purpose except
+to represent the company, product, or service to which they refer.**
diff --git a/licenses/fpng.license.txt b/licenses/fpng.license.txt
new file mode 100644
index 0000000000..68a49daad8
--- /dev/null
+++ b/licenses/fpng.license.txt
@@ -0,0 +1,24 @@
+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/licenses/tomlplusplus.license.txt b/licenses/tomlplusplus.license.txt
new file mode 100644
index 0000000000..261cd61587
--- /dev/null
+++ b/licenses/tomlplusplus.license.txt
@@ -0,0 +1,16 @@
+MIT License
+
+Copyright (c) Mark Gillard
+
+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/net/slirp.c b/net/slirp.c
index ad3a838e0b..d5f6d621c1 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -641,6 +641,15 @@ static SlirpState *slirp_lookup(Monitor *mon, const char *id)
}
}
+#ifdef XBOX
+void *slirp_get_state_from_netdev(const char *id)
+{
+ SlirpState *s = slirp_lookup(NULL, id);
+ if (!s) return NULL;
+ return s->slirp;
+}
+#endif
+
void hmp_hostfwd_remove(Monitor *mon, const QDict *qdict)
{
struct in_addr host_addr = { .s_addr = INADDR_ANY };
diff --git a/scripts/archive-source.sh b/scripts/archive-source.sh
index a2044943d2..debb1d344d 100755
--- a/scripts/archive-source.sh
+++ b/scripts/archive-source.sh
@@ -28,7 +28,7 @@ sub_file="${sub_tdir}/submodule.tar"
# different to the host OS.
submodules="dtc slirp meson ui/keycodemapdb"
submodules="$submodules tests/fp/berkeley-softfloat-3 tests/fp/berkeley-testfloat-3"
-submodules="$submodules ui/imgui ui/implot util/xxHash tomlplusplus genconfig" # xemu extras
+submodules="$submodules ui/thirdparty/imgui ui/thirdparty/implot util/xxHash tomlplusplus genconfig" # xemu extras
sub_deinit=""
function cleanup() {
diff --git a/scripts/gen-license.py b/scripts/gen-license.py
index 705f8f96a1..66ec79cb98 100755
--- a/scripts/gen-license.py
+++ b/scripts/gen-license.py
@@ -20,6 +20,7 @@ bsd_3clause = 'bsd-3clause'
zlib = 'zlib'
lgplv2_1 = 'lgplv2_1'
apache2 = 'apache2'
+unlicense = 'unlicense'
multi = 'multi'
@@ -179,13 +180,13 @@ Lib('slirp', 'https://gitlab.freedesktop.org/slirp',
Lib('imgui', 'https://github.com/ocornut/imgui',
mit, 'https://raw.githubusercontent.com/ocornut/imgui/master/LICENSE.txt',
ships_static=all_platforms,
- submodule=Submodule('ui/imgui')
+ submodule=Submodule('ui/thirdparty/imgui')
),
Lib('implot', 'https://github.com/epezent/implot',
mit, 'https://raw.githubusercontent.com/epezent/implot/master/LICENSE',
ships_static=all_platforms,
- submodule=Submodule('ui/implot')
+ submodule=Submodule('ui/thirdparty/implot')
),
Lib('httplib', 'https://github.com/yhirose/cpp-httplib',
@@ -206,10 +207,10 @@ Lib('stb_image', 'https://github.com/nothings/stb',
version='2.25'
),
-Lib('inih', 'https://github.com/benhoyt/inih',
- bsd, 'https://raw.githubusercontent.com/mborgerson/xemu/master/ui/inih/LICENSE.txt',
+Lib('tomlplusplus', 'https://github.com/marzer/tomlplusplus',
+ mit, 'https://raw.githubusercontent.com/marzer/tomlplusplus/master/LICENSE',
ships_static=all_platforms,
- version='351217124ddb3e3fe2b982248a04c672350bb0af'
+ submodule=Submodule('tomlplusplus')
),
Lib('xxHash', 'https://github.com/Cyan4973/xxHash.git',
@@ -218,6 +219,12 @@ Lib('xxHash', 'https://github.com/Cyan4973/xxHash.git',
submodule=Submodule('util/xxHash')
),
+Lib('fpng', 'https://github.com/richgel999/fpng',
+ unlicense, 'https://github.com/richgel999/fpng/blob/main/README.md',
+ ships_static=all_platforms,
+ version='6926f5a0a78f22d42b074a0ab8032e07736babd4'
+ ),
+
#
# Data files included with xemu
#
@@ -228,6 +235,12 @@ Lib('roboto', 'https://github.com/googlefonts/roboto',
version='2.138'
),
+Lib('fontawesome', 'https://fontawesome.com',
+ multi, '',
+ ships_static=all_platforms,
+ version='6.1.1'
+ ),
+
#
# Libraries either linked statically, dynamically linked & shipped, or dynamically linked with system-installed libraries only
#
diff --git a/softmmu/vl.c b/softmmu/vl.c
index 0a76d0335c..2eecce1cd7 100644
--- a/softmmu/vl.c
+++ b/softmmu/vl.c
@@ -2866,10 +2866,21 @@ void qemu_init(int argc, char **argv, char **envp)
}
}
- fake_argv[fake_argc++] = g_strdup_printf("xbox%s%s%s",
+ const char *avpack_str = (const char *[]){
+ "scart",
+ "hdtv",
+ "vga",
+ "rfu",
+ "svideo",
+ "composite",
+ "none",
+ }[g_config.sys.avpack];
+
+ fake_argv[fake_argc++] = g_strdup_printf("xbox%s%s%s,avpack=%s",
(bootrom_arg != NULL) ? bootrom_arg : "",
- g_config.general.misc.skip_boot_anim ? ",short-animation=on" : "",
- ",kernel-irqchip=off"
+ g_config.general.skip_boot_anim ? ",short-animation=on" : "",
+ ",kernel-irqchip=off",
+ avpack_str
);
if (bootrom_arg != NULL) {
diff --git a/ui/imgui b/ui/imgui
deleted file mode 160000
index e18abe3619..0000000000
--- a/ui/imgui
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit e18abe3619cfa0eced163c027d0349506814816c
diff --git a/ui/implot b/ui/implot
deleted file mode 160000
index a6bab98517..0000000000
--- a/ui/implot
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit a6bab98517b1baa3116db52518dda1eb2d7eaab7
diff --git a/ui/meson.build b/ui/meson.build
index d73512360f..02064602e7 100644
--- a/ui/meson.build
+++ b/ui/meson.build
@@ -15,57 +15,35 @@ softmmu_ss.add(files(
'udmabuf.c',
))
-imgui_files = files(
- 'imgui/imgui.cpp',
- 'imgui/imgui_draw.cpp',
- 'imgui/imgui_tables.cpp',
- #'imgui/imgui_demo.cpp',
- 'imgui/imgui_widgets.cpp',
- 'imgui/backends/imgui_impl_opengl3.cpp',
- 'imgui/backends/imgui_impl_sdl.cpp',
- 'implot/implot.cpp',
- #'implot/implot_demo.cpp',
- 'implot/implot_items.cpp'
-)
-
-imgui_cppargs = ['-DIMGUI_IMPL_OPENGL_LOADER_CUSTOM="epoxy/gl.h"']
-
-libimgui = static_library('imgui',
- sources: imgui_files,
- cpp_args: imgui_cppargs,
- include_directories: 'imgui',
- dependencies: [sdl, opengl])
-imgui = declare_dependency(link_with: libimgui,
- compile_args: imgui_cppargs,
- include_directories: 'imgui')
+subdir('thirdparty')
xemu_ss = ss.source_set()
xemu_ss.add(files(
- 'xemu.c',
- 'xemu-custom-widgets.c',
- 'xemu-data.c',
'xemu-input.c',
'xemu-monitor.c',
'xemu-net.c',
'xemu-settings.cc',
- 'xemu-shaders.c',
- 'xemu-hud.cc',
- 'xemu-reporting.cc',
+
+ 'xemu.c',
+ 'xemu-data.c',
))
-xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-update.cc'))
+subdir('xui')
if 'CONFIG_DARWIN' in config_host
xemu_cocoa = dependency('appleframeworks', modules: 'Cocoa')
-xemu_ss.add(xemu_cocoa) # FIXME: Use existing cocoa name
+xemu_ss.add(xemu_cocoa)
endif
-xemu_ss.add(imgui, sdl, opengl, openssl)
-xemu_ss.add(when: 'CONFIG_LINUX', if_true: [xemu_gtk, files('xemu-os-utils-linux.c', 'noc_file_dialog_gtk.c')])
-xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-os-utils-windows.c', 'noc_file_dialog_win32.c'))
-xemu_ss.add(when: 'CONFIG_DARWIN', if_true: files('xemu-os-utils-macos.m', 'noc_file_dialog_macos.m'))
+if 'CONFIG_LINUX' in config_host
+xemu_ss.add(xemu_gtk)
+endif
+xemu_ss.add(when: 'CONFIG_LINUX', if_true: [xemu_gtk, files('xemu-os-utils-linux.c')])
+xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-os-utils-windows.c'))
+xemu_ss.add(when: 'CONFIG_DARWIN', if_true: files('xemu-os-utils-macos.m'))
xemu_ss.add(when: 'CONFIG_RENDERDOC', if_true: [libdl])
+xemu_ss.add(imgui, implot, stb_image, noc, sdl, opengl, openssl, fa, fpng, json, httplib)
softmmu_ss.add_all(xemu_ss)
diff --git a/ui/thirdparty/fa/IconsFontAwesome6.h b/ui/thirdparty/fa/IconsFontAwesome6.h
new file mode 100644
index 0000000000..050956448c
--- /dev/null
+++ b/ui/thirdparty/fa/IconsFontAwesome6.h
@@ -0,0 +1,1393 @@
+// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for languages C and C++
+// from https://github.com/FortAwesome/Font-Awesome/raw/6.x/metadata/icons.yml
+// for use with https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-regular-400.ttf, https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-solid-900.ttf
+#pragma once
+
+#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf"
+#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf"
+
+#define ICON_MIN_FA 0x21
+#define ICON_MAX_FA 0xf8ff
+#define ICON_FA_0 "0" // U+30
+#define ICON_FA_1 "1" // U+31
+#define ICON_FA_2 "2" // U+32
+#define ICON_FA_3 "3" // U+33
+#define ICON_FA_4 "4" // U+34
+#define ICON_FA_5 "5" // U+35
+#define ICON_FA_6 "6" // U+36
+#define ICON_FA_7 "7" // U+37
+#define ICON_FA_8 "8" // U+38
+#define ICON_FA_9 "9" // U+39
+#define ICON_FA_A "A" // U+41
+#define ICON_FA_ADDRESS_BOOK "\xef\x8a\xb9" // U+f2b9
+#define ICON_FA_ADDRESS_CARD "\xef\x8a\xbb" // U+f2bb
+#define ICON_FA_ALIGN_CENTER "\xef\x80\xb7" // U+f037
+#define ICON_FA_ALIGN_JUSTIFY "\xef\x80\xb9" // U+f039
+#define ICON_FA_ALIGN_LEFT "\xef\x80\xb6" // U+f036
+#define ICON_FA_ALIGN_RIGHT "\xef\x80\xb8" // U+f038
+#define ICON_FA_ANCHOR "\xef\x84\xbd" // U+f13d
+#define ICON_FA_ANCHOR_CIRCLE_CHECK "\xee\x92\xaa" // U+e4aa
+#define ICON_FA_ANCHOR_CIRCLE_EXCLAMATION "\xee\x92\xab" // U+e4ab
+#define ICON_FA_ANCHOR_CIRCLE_XMARK "\xee\x92\xac" // U+e4ac
+#define ICON_FA_ANCHOR_LOCK "\xee\x92\xad" // U+e4ad
+#define ICON_FA_ANGLE_DOWN "\xef\x84\x87" // U+f107
+#define ICON_FA_ANGLE_LEFT "\xef\x84\x84" // U+f104
+#define ICON_FA_ANGLE_RIGHT "\xef\x84\x85" // U+f105
+#define ICON_FA_ANGLE_UP "\xef\x84\x86" // U+f106
+#define ICON_FA_ANGLES_DOWN "\xef\x84\x83" // U+f103
+#define ICON_FA_ANGLES_LEFT "\xef\x84\x80" // U+f100
+#define ICON_FA_ANGLES_RIGHT "\xef\x84\x81" // U+f101
+#define ICON_FA_ANGLES_UP "\xef\x84\x82" // U+f102
+#define ICON_FA_ANKH "\xef\x99\x84" // U+f644
+#define ICON_FA_APPLE_WHOLE "\xef\x97\x91" // U+f5d1
+#define ICON_FA_ARCHWAY "\xef\x95\x97" // U+f557
+#define ICON_FA_ARROW_DOWN "\xef\x81\xa3" // U+f063
+#define ICON_FA_ARROW_DOWN_1_9 "\xef\x85\xa2" // U+f162
+#define ICON_FA_ARROW_DOWN_9_1 "\xef\xa2\x86" // U+f886
+#define ICON_FA_ARROW_DOWN_A_Z "\xef\x85\x9d" // U+f15d
+#define ICON_FA_ARROW_DOWN_LONG "\xef\x85\xb5" // U+f175
+#define ICON_FA_ARROW_DOWN_SHORT_WIDE "\xef\xa2\x84" // U+f884
+#define ICON_FA_ARROW_DOWN_UP_ACROSS_LINE "\xee\x92\xaf" // U+e4af
+#define ICON_FA_ARROW_DOWN_UP_LOCK "\xee\x92\xb0" // U+e4b0
+#define ICON_FA_ARROW_DOWN_WIDE_SHORT "\xef\x85\xa0" // U+f160
+#define ICON_FA_ARROW_DOWN_Z_A "\xef\xa2\x81" // U+f881
+#define ICON_FA_ARROW_LEFT "\xef\x81\xa0" // U+f060
+#define ICON_FA_ARROW_LEFT_LONG "\xef\x85\xb7" // U+f177
+#define ICON_FA_ARROW_POINTER "\xef\x89\x85" // U+f245
+#define ICON_FA_ARROW_RIGHT "\xef\x81\xa1" // U+f061
+#define ICON_FA_ARROW_RIGHT_ARROW_LEFT "\xef\x83\xac" // U+f0ec
+#define ICON_FA_ARROW_RIGHT_FROM_BRACKET "\xef\x82\x8b" // U+f08b
+#define ICON_FA_ARROW_RIGHT_LONG "\xef\x85\xb8" // U+f178
+#define ICON_FA_ARROW_RIGHT_TO_BRACKET "\xef\x82\x90" // U+f090
+#define ICON_FA_ARROW_RIGHT_TO_CITY "\xee\x92\xb3" // U+e4b3
+#define ICON_FA_ARROW_ROTATE_LEFT "\xef\x83\xa2" // U+f0e2
+#define ICON_FA_ARROW_ROTATE_RIGHT "\xef\x80\x9e" // U+f01e
+#define ICON_FA_ARROW_TREND_DOWN "\xee\x82\x97" // U+e097
+#define ICON_FA_ARROW_TREND_UP "\xee\x82\x98" // U+e098
+#define ICON_FA_ARROW_TURN_DOWN "\xef\x85\x89" // U+f149
+#define ICON_FA_ARROW_TURN_UP "\xef\x85\x88" // U+f148
+#define ICON_FA_ARROW_UP "\xef\x81\xa2" // U+f062
+#define ICON_FA_ARROW_UP_1_9 "\xef\x85\xa3" // U+f163
+#define ICON_FA_ARROW_UP_9_1 "\xef\xa2\x87" // U+f887
+#define ICON_FA_ARROW_UP_A_Z "\xef\x85\x9e" // U+f15e
+#define ICON_FA_ARROW_UP_FROM_BRACKET "\xee\x82\x9a" // U+e09a
+#define ICON_FA_ARROW_UP_FROM_GROUND_WATER "\xee\x92\xb5" // U+e4b5
+#define ICON_FA_ARROW_UP_FROM_WATER_PUMP "\xee\x92\xb6" // U+e4b6
+#define ICON_FA_ARROW_UP_LONG "\xef\x85\xb6" // U+f176
+#define ICON_FA_ARROW_UP_RIGHT_DOTS "\xee\x92\xb7" // U+e4b7
+#define ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE "\xef\x82\x8e" // U+f08e
+#define ICON_FA_ARROW_UP_SHORT_WIDE "\xef\xa2\x85" // U+f885
+#define ICON_FA_ARROW_UP_WIDE_SHORT "\xef\x85\xa1" // U+f161
+#define ICON_FA_ARROW_UP_Z_A "\xef\xa2\x82" // U+f882
+#define ICON_FA_ARROWS_DOWN_TO_LINE "\xee\x92\xb8" // U+e4b8
+#define ICON_FA_ARROWS_DOWN_TO_PEOPLE "\xee\x92\xb9" // U+e4b9
+#define ICON_FA_ARROWS_LEFT_RIGHT "\xef\x81\xbe" // U+f07e
+#define ICON_FA_ARROWS_LEFT_RIGHT_TO_LINE "\xee\x92\xba" // U+e4ba
+#define ICON_FA_ARROWS_ROTATE "\xef\x80\xa1" // U+f021
+#define ICON_FA_ARROWS_SPIN "\xee\x92\xbb" // U+e4bb
+#define ICON_FA_ARROWS_SPLIT_UP_AND_LEFT "\xee\x92\xbc" // U+e4bc
+#define ICON_FA_ARROWS_TO_CIRCLE "\xee\x92\xbd" // U+e4bd
+#define ICON_FA_ARROWS_TO_DOT "\xee\x92\xbe" // U+e4be
+#define ICON_FA_ARROWS_TO_EYE "\xee\x92\xbf" // U+e4bf
+#define ICON_FA_ARROWS_TURN_RIGHT "\xee\x93\x80" // U+e4c0
+#define ICON_FA_ARROWS_TURN_TO_DOTS "\xee\x93\x81" // U+e4c1
+#define ICON_FA_ARROWS_UP_DOWN "\xef\x81\xbd" // U+f07d
+#define ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT "\xef\x81\x87" // U+f047
+#define ICON_FA_ARROWS_UP_TO_LINE "\xee\x93\x82" // U+e4c2
+#define ICON_FA_ASTERISK "*" // U+2a
+#define ICON_FA_AT "@" // U+40
+#define ICON_FA_ATOM "\xef\x97\x92" // U+f5d2
+#define ICON_FA_AUDIO_DESCRIPTION "\xef\x8a\x9e" // U+f29e
+#define ICON_FA_AUSTRAL_SIGN "\xee\x82\xa9" // U+e0a9
+#define ICON_FA_AWARD "\xef\x95\x99" // U+f559
+#define ICON_FA_B "B" // U+42
+#define ICON_FA_BABY "\xef\x9d\xbc" // U+f77c
+#define ICON_FA_BABY_CARRIAGE "\xef\x9d\xbd" // U+f77d
+#define ICON_FA_BACKWARD "\xef\x81\x8a" // U+f04a
+#define ICON_FA_BACKWARD_FAST "\xef\x81\x89" // U+f049
+#define ICON_FA_BACKWARD_STEP "\xef\x81\x88" // U+f048
+#define ICON_FA_BACON "\xef\x9f\xa5" // U+f7e5
+#define ICON_FA_BACTERIA "\xee\x81\x99" // U+e059
+#define ICON_FA_BACTERIUM "\xee\x81\x9a" // U+e05a
+#define ICON_FA_BAG_SHOPPING "\xef\x8a\x90" // U+f290
+#define ICON_FA_BAHAI "\xef\x99\xa6" // U+f666
+#define ICON_FA_BAHT_SIGN "\xee\x82\xac" // U+e0ac
+#define ICON_FA_BAN "\xef\x81\x9e" // U+f05e
+#define ICON_FA_BAN_SMOKING "\xef\x95\x8d" // U+f54d
+#define ICON_FA_BANDAGE "\xef\x91\xa2" // U+f462
+#define ICON_FA_BARCODE "\xef\x80\xaa" // U+f02a
+#define ICON_FA_BARS "\xef\x83\x89" // U+f0c9
+#define ICON_FA_BARS_PROGRESS "\xef\xa0\xa8" // U+f828
+#define ICON_FA_BARS_STAGGERED "\xef\x95\x90" // U+f550
+#define ICON_FA_BASEBALL "\xef\x90\xb3" // U+f433
+#define ICON_FA_BASEBALL_BAT_BALL "\xef\x90\xb2" // U+f432
+#define ICON_FA_BASKET_SHOPPING "\xef\x8a\x91" // U+f291
+#define ICON_FA_BASKETBALL "\xef\x90\xb4" // U+f434
+#define ICON_FA_BATH "\xef\x8b\x8d" // U+f2cd
+#define ICON_FA_BATTERY_EMPTY "\xef\x89\x84" // U+f244
+#define ICON_FA_BATTERY_FULL "\xef\x89\x80" // U+f240
+#define ICON_FA_BATTERY_HALF "\xef\x89\x82" // U+f242
+#define ICON_FA_BATTERY_QUARTER "\xef\x89\x83" // U+f243
+#define ICON_FA_BATTERY_THREE_QUARTERS "\xef\x89\x81" // U+f241
+#define ICON_FA_BED "\xef\x88\xb6" // U+f236
+#define ICON_FA_BED_PULSE "\xef\x92\x87" // U+f487
+#define ICON_FA_BEER_MUG_EMPTY "\xef\x83\xbc" // U+f0fc
+#define ICON_FA_BELL "\xef\x83\xb3" // U+f0f3
+#define ICON_FA_BELL_CONCIERGE "\xef\x95\xa2" // U+f562
+#define ICON_FA_BELL_SLASH "\xef\x87\xb6" // U+f1f6
+#define ICON_FA_BEZIER_CURVE "\xef\x95\x9b" // U+f55b
+#define ICON_FA_BICYCLE "\xef\x88\x86" // U+f206
+#define ICON_FA_BINOCULARS "\xef\x87\xa5" // U+f1e5
+#define ICON_FA_BIOHAZARD "\xef\x9e\x80" // U+f780
+#define ICON_FA_BITCOIN_SIGN "\xee\x82\xb4" // U+e0b4
+#define ICON_FA_BLENDER "\xef\x94\x97" // U+f517
+#define ICON_FA_BLENDER_PHONE "\xef\x9a\xb6" // U+f6b6
+#define ICON_FA_BLOG "\xef\x9e\x81" // U+f781
+#define ICON_FA_BOLD "\xef\x80\xb2" // U+f032
+#define ICON_FA_BOLT "\xef\x83\xa7" // U+f0e7
+#define ICON_FA_BOLT_LIGHTNING "\xee\x82\xb7" // U+e0b7
+#define ICON_FA_BOMB "\xef\x87\xa2" // U+f1e2
+#define ICON_FA_BONE "\xef\x97\x97" // U+f5d7
+#define ICON_FA_BONG "\xef\x95\x9c" // U+f55c
+#define ICON_FA_BOOK "\xef\x80\xad" // U+f02d
+#define ICON_FA_BOOK_ATLAS "\xef\x95\x98" // U+f558
+#define ICON_FA_BOOK_BIBLE "\xef\x99\x87" // U+f647
+#define ICON_FA_BOOK_BOOKMARK "\xee\x82\xbb" // U+e0bb
+#define ICON_FA_BOOK_JOURNAL_WHILLS "\xef\x99\xaa" // U+f66a
+#define ICON_FA_BOOK_MEDICAL "\xef\x9f\xa6" // U+f7e6
+#define ICON_FA_BOOK_OPEN "\xef\x94\x98" // U+f518
+#define ICON_FA_BOOK_OPEN_READER "\xef\x97\x9a" // U+f5da
+#define ICON_FA_BOOK_QURAN "\xef\x9a\x87" // U+f687
+#define ICON_FA_BOOK_SKULL "\xef\x9a\xb7" // U+f6b7
+#define ICON_FA_BOOKMARK "\xef\x80\xae" // U+f02e
+#define ICON_FA_BORDER_ALL "\xef\xa1\x8c" // U+f84c
+#define ICON_FA_BORDER_NONE "\xef\xa1\x90" // U+f850
+#define ICON_FA_BORDER_TOP_LEFT "\xef\xa1\x93" // U+f853
+#define ICON_FA_BORE_HOLE "\xee\x93\x83" // U+e4c3
+#define ICON_FA_BOTTLE_DROPLET "\xee\x93\x84" // U+e4c4
+#define ICON_FA_BOTTLE_WATER "\xee\x93\x85" // U+e4c5
+#define ICON_FA_BOWL_FOOD "\xee\x93\x86" // U+e4c6
+#define ICON_FA_BOWL_RICE "\xee\x8b\xab" // U+e2eb
+#define ICON_FA_BOWLING_BALL "\xef\x90\xb6" // U+f436
+#define ICON_FA_BOX "\xef\x91\xa6" // U+f466
+#define ICON_FA_BOX_ARCHIVE "\xef\x86\x87" // U+f187
+#define ICON_FA_BOX_OPEN "\xef\x92\x9e" // U+f49e
+#define ICON_FA_BOX_TISSUE "\xee\x81\x9b" // U+e05b
+#define ICON_FA_BOXES_PACKING "\xee\x93\x87" // U+e4c7
+#define ICON_FA_BOXES_STACKED "\xef\x91\xa8" // U+f468
+#define ICON_FA_BRAILLE "\xef\x8a\xa1" // U+f2a1
+#define ICON_FA_BRAIN "\xef\x97\x9c" // U+f5dc
+#define ICON_FA_BRAZILIAN_REAL_SIGN "\xee\x91\xac" // U+e46c
+#define ICON_FA_BREAD_SLICE "\xef\x9f\xac" // U+f7ec
+#define ICON_FA_BRIDGE "\xee\x93\x88" // U+e4c8
+#define ICON_FA_BRIDGE_CIRCLE_CHECK "\xee\x93\x89" // U+e4c9
+#define ICON_FA_BRIDGE_CIRCLE_EXCLAMATION "\xee\x93\x8a" // U+e4ca
+#define ICON_FA_BRIDGE_CIRCLE_XMARK "\xee\x93\x8b" // U+e4cb
+#define ICON_FA_BRIDGE_LOCK "\xee\x93\x8c" // U+e4cc
+#define ICON_FA_BRIDGE_WATER "\xee\x93\x8e" // U+e4ce
+#define ICON_FA_BRIEFCASE "\xef\x82\xb1" // U+f0b1
+#define ICON_FA_BRIEFCASE_MEDICAL "\xef\x91\xa9" // U+f469
+#define ICON_FA_BROOM "\xef\x94\x9a" // U+f51a
+#define ICON_FA_BROOM_BALL "\xef\x91\x98" // U+f458
+#define ICON_FA_BRUSH "\xef\x95\x9d" // U+f55d
+#define ICON_FA_BUCKET "\xee\x93\x8f" // U+e4cf
+#define ICON_FA_BUG "\xef\x86\x88" // U+f188
+#define ICON_FA_BUG_SLASH "\xee\x92\x90" // U+e490
+#define ICON_FA_BUGS "\xee\x93\x90" // U+e4d0
+#define ICON_FA_BUILDING "\xef\x86\xad" // U+f1ad
+#define ICON_FA_BUILDING_CIRCLE_ARROW_RIGHT "\xee\x93\x91" // U+e4d1
+#define ICON_FA_BUILDING_CIRCLE_CHECK "\xee\x93\x92" // U+e4d2
+#define ICON_FA_BUILDING_CIRCLE_EXCLAMATION "\xee\x93\x93" // U+e4d3
+#define ICON_FA_BUILDING_CIRCLE_XMARK "\xee\x93\x94" // U+e4d4
+#define ICON_FA_BUILDING_COLUMNS "\xef\x86\x9c" // U+f19c
+#define ICON_FA_BUILDING_FLAG "\xee\x93\x95" // U+e4d5
+#define ICON_FA_BUILDING_LOCK "\xee\x93\x96" // U+e4d6
+#define ICON_FA_BUILDING_NGO "\xee\x93\x97" // U+e4d7
+#define ICON_FA_BUILDING_SHIELD "\xee\x93\x98" // U+e4d8
+#define ICON_FA_BUILDING_UN "\xee\x93\x99" // U+e4d9
+#define ICON_FA_BUILDING_USER "\xee\x93\x9a" // U+e4da
+#define ICON_FA_BUILDING_WHEAT "\xee\x93\x9b" // U+e4db
+#define ICON_FA_BULLHORN "\xef\x82\xa1" // U+f0a1
+#define ICON_FA_BULLSEYE "\xef\x85\x80" // U+f140
+#define ICON_FA_BURGER "\xef\xa0\x85" // U+f805
+#define ICON_FA_BURST "\xee\x93\x9c" // U+e4dc
+#define ICON_FA_BUS "\xef\x88\x87" // U+f207
+#define ICON_FA_BUS_SIMPLE "\xef\x95\x9e" // U+f55e
+#define ICON_FA_BUSINESS_TIME "\xef\x99\x8a" // U+f64a
+#define ICON_FA_C "C" // U+43
+#define ICON_FA_CAKE_CANDLES "\xef\x87\xbd" // U+f1fd
+#define ICON_FA_CALCULATOR "\xef\x87\xac" // U+f1ec
+#define ICON_FA_CALENDAR "\xef\x84\xb3" // U+f133
+#define ICON_FA_CALENDAR_CHECK "\xef\x89\xb4" // U+f274
+#define ICON_FA_CALENDAR_DAY "\xef\x9e\x83" // U+f783
+#define ICON_FA_CALENDAR_DAYS "\xef\x81\xb3" // U+f073
+#define ICON_FA_CALENDAR_MINUS "\xef\x89\xb2" // U+f272
+#define ICON_FA_CALENDAR_PLUS "\xef\x89\xb1" // U+f271
+#define ICON_FA_CALENDAR_WEEK "\xef\x9e\x84" // U+f784
+#define ICON_FA_CALENDAR_XMARK "\xef\x89\xb3" // U+f273
+#define ICON_FA_CAMERA "\xef\x80\xb0" // U+f030
+#define ICON_FA_CAMERA_RETRO "\xef\x82\x83" // U+f083
+#define ICON_FA_CAMERA_ROTATE "\xee\x83\x98" // U+e0d8
+#define ICON_FA_CAMPGROUND "\xef\x9a\xbb" // U+f6bb
+#define ICON_FA_CANDY_CANE "\xef\x9e\x86" // U+f786
+#define ICON_FA_CANNABIS "\xef\x95\x9f" // U+f55f
+#define ICON_FA_CAPSULES "\xef\x91\xab" // U+f46b
+#define ICON_FA_CAR "\xef\x86\xb9" // U+f1b9
+#define ICON_FA_CAR_BATTERY "\xef\x97\x9f" // U+f5df
+#define ICON_FA_CAR_BURST "\xef\x97\xa1" // U+f5e1
+#define ICON_FA_CAR_ON "\xee\x93\x9d" // U+e4dd
+#define ICON_FA_CAR_REAR "\xef\x97\x9e" // U+f5de
+#define ICON_FA_CAR_SIDE "\xef\x97\xa4" // U+f5e4
+#define ICON_FA_CAR_TUNNEL "\xee\x93\x9e" // U+e4de
+#define ICON_FA_CARAVAN "\xef\xa3\xbf" // U+f8ff
+#define ICON_FA_CARET_DOWN "\xef\x83\x97" // U+f0d7
+#define ICON_FA_CARET_LEFT "\xef\x83\x99" // U+f0d9
+#define ICON_FA_CARET_RIGHT "\xef\x83\x9a" // U+f0da
+#define ICON_FA_CARET_UP "\xef\x83\x98" // U+f0d8
+#define ICON_FA_CARROT "\xef\x9e\x87" // U+f787
+#define ICON_FA_CART_ARROW_DOWN "\xef\x88\x98" // U+f218
+#define ICON_FA_CART_FLATBED "\xef\x91\xb4" // U+f474
+#define ICON_FA_CART_FLATBED_SUITCASE "\xef\x96\x9d" // U+f59d
+#define ICON_FA_CART_PLUS "\xef\x88\x97" // U+f217
+#define ICON_FA_CART_SHOPPING "\xef\x81\xba" // U+f07a
+#define ICON_FA_CASH_REGISTER "\xef\x9e\x88" // U+f788
+#define ICON_FA_CAT "\xef\x9a\xbe" // U+f6be
+#define ICON_FA_CEDI_SIGN "\xee\x83\x9f" // U+e0df
+#define ICON_FA_CENT_SIGN "\xee\x8f\xb5" // U+e3f5
+#define ICON_FA_CERTIFICATE "\xef\x82\xa3" // U+f0a3
+#define ICON_FA_CHAIR "\xef\x9b\x80" // U+f6c0
+#define ICON_FA_CHALKBOARD "\xef\x94\x9b" // U+f51b
+#define ICON_FA_CHALKBOARD_USER "\xef\x94\x9c" // U+f51c
+#define ICON_FA_CHAMPAGNE_GLASSES "\xef\x9e\x9f" // U+f79f
+#define ICON_FA_CHARGING_STATION "\xef\x97\xa7" // U+f5e7
+#define ICON_FA_CHART_AREA "\xef\x87\xbe" // U+f1fe
+#define ICON_FA_CHART_BAR "\xef\x82\x80" // U+f080
+#define ICON_FA_CHART_COLUMN "\xee\x83\xa3" // U+e0e3
+#define ICON_FA_CHART_GANTT "\xee\x83\xa4" // U+e0e4
+#define ICON_FA_CHART_LINE "\xef\x88\x81" // U+f201
+#define ICON_FA_CHART_PIE "\xef\x88\x80" // U+f200
+#define ICON_FA_CHART_SIMPLE "\xee\x91\xb3" // U+e473
+#define ICON_FA_CHECK "\xef\x80\x8c" // U+f00c
+#define ICON_FA_CHECK_DOUBLE "\xef\x95\xa0" // U+f560
+#define ICON_FA_CHECK_TO_SLOT "\xef\x9d\xb2" // U+f772
+#define ICON_FA_CHEESE "\xef\x9f\xaf" // U+f7ef
+#define ICON_FA_CHESS "\xef\x90\xb9" // U+f439
+#define ICON_FA_CHESS_BISHOP "\xef\x90\xba" // U+f43a
+#define ICON_FA_CHESS_BOARD "\xef\x90\xbc" // U+f43c
+#define ICON_FA_CHESS_KING "\xef\x90\xbf" // U+f43f
+#define ICON_FA_CHESS_KNIGHT "\xef\x91\x81" // U+f441
+#define ICON_FA_CHESS_PAWN "\xef\x91\x83" // U+f443
+#define ICON_FA_CHESS_QUEEN "\xef\x91\x85" // U+f445
+#define ICON_FA_CHESS_ROOK "\xef\x91\x87" // U+f447
+#define ICON_FA_CHEVRON_DOWN "\xef\x81\xb8" // U+f078
+#define ICON_FA_CHEVRON_LEFT "\xef\x81\x93" // U+f053
+#define ICON_FA_CHEVRON_RIGHT "\xef\x81\x94" // U+f054
+#define ICON_FA_CHEVRON_UP "\xef\x81\xb7" // U+f077
+#define ICON_FA_CHILD "\xef\x86\xae" // U+f1ae
+#define ICON_FA_CHILD_RIFLE "\xee\x93\xa0" // U+e4e0
+#define ICON_FA_CHILDREN "\xee\x93\xa1" // U+e4e1
+#define ICON_FA_CHURCH "\xef\x94\x9d" // U+f51d
+#define ICON_FA_CIRCLE "\xef\x84\x91" // U+f111
+#define ICON_FA_CIRCLE_ARROW_DOWN "\xef\x82\xab" // U+f0ab
+#define ICON_FA_CIRCLE_ARROW_LEFT "\xef\x82\xa8" // U+f0a8
+#define ICON_FA_CIRCLE_ARROW_RIGHT "\xef\x82\xa9" // U+f0a9
+#define ICON_FA_CIRCLE_ARROW_UP "\xef\x82\xaa" // U+f0aa
+#define ICON_FA_CIRCLE_CHECK "\xef\x81\x98" // U+f058
+#define ICON_FA_CIRCLE_CHEVRON_DOWN "\xef\x84\xba" // U+f13a
+#define ICON_FA_CIRCLE_CHEVRON_LEFT "\xef\x84\xb7" // U+f137
+#define ICON_FA_CIRCLE_CHEVRON_RIGHT "\xef\x84\xb8" // U+f138
+#define ICON_FA_CIRCLE_CHEVRON_UP "\xef\x84\xb9" // U+f139
+#define ICON_FA_CIRCLE_DOLLAR_TO_SLOT "\xef\x92\xb9" // U+f4b9
+#define ICON_FA_CIRCLE_DOT "\xef\x86\x92" // U+f192
+#define ICON_FA_CIRCLE_DOWN "\xef\x8d\x98" // U+f358
+#define ICON_FA_CIRCLE_EXCLAMATION "\xef\x81\xaa" // U+f06a
+#define ICON_FA_CIRCLE_H "\xef\x91\xbe" // U+f47e
+#define ICON_FA_CIRCLE_HALF_STROKE "\xef\x81\x82" // U+f042
+#define ICON_FA_CIRCLE_INFO "\xef\x81\x9a" // U+f05a
+#define ICON_FA_CIRCLE_LEFT "\xef\x8d\x99" // U+f359
+#define ICON_FA_CIRCLE_MINUS "\xef\x81\x96" // U+f056
+#define ICON_FA_CIRCLE_NODES "\xee\x93\xa2" // U+e4e2
+#define ICON_FA_CIRCLE_NOTCH "\xef\x87\x8e" // U+f1ce
+#define ICON_FA_CIRCLE_PAUSE "\xef\x8a\x8b" // U+f28b
+#define ICON_FA_CIRCLE_PLAY "\xef\x85\x84" // U+f144
+#define ICON_FA_CIRCLE_PLUS "\xef\x81\x95" // U+f055
+#define ICON_FA_CIRCLE_QUESTION "\xef\x81\x99" // U+f059
+#define ICON_FA_CIRCLE_RADIATION "\xef\x9e\xba" // U+f7ba
+#define ICON_FA_CIRCLE_RIGHT "\xef\x8d\x9a" // U+f35a
+#define ICON_FA_CIRCLE_STOP "\xef\x8a\x8d" // U+f28d
+#define ICON_FA_CIRCLE_UP "\xef\x8d\x9b" // U+f35b
+#define ICON_FA_CIRCLE_USER "\xef\x8a\xbd" // U+f2bd
+#define ICON_FA_CIRCLE_XMARK "\xef\x81\x97" // U+f057
+#define ICON_FA_CITY "\xef\x99\x8f" // U+f64f
+#define ICON_FA_CLAPPERBOARD "\xee\x84\xb1" // U+e131
+#define ICON_FA_CLIPBOARD "\xef\x8c\xa8" // U+f328
+#define ICON_FA_CLIPBOARD_CHECK "\xef\x91\xac" // U+f46c
+#define ICON_FA_CLIPBOARD_LIST "\xef\x91\xad" // U+f46d
+#define ICON_FA_CLIPBOARD_QUESTION "\xee\x93\xa3" // U+e4e3
+#define ICON_FA_CLIPBOARD_USER "\xef\x9f\xb3" // U+f7f3
+#define ICON_FA_CLOCK "\xef\x80\x97" // U+f017
+#define ICON_FA_CLOCK_ROTATE_LEFT "\xef\x87\x9a" // U+f1da
+#define ICON_FA_CLONE "\xef\x89\x8d" // U+f24d
+#define ICON_FA_CLOSED_CAPTIONING "\xef\x88\x8a" // U+f20a
+#define ICON_FA_CLOUD "\xef\x83\x82" // U+f0c2
+#define ICON_FA_CLOUD_ARROW_DOWN "\xef\x83\xad" // U+f0ed
+#define ICON_FA_CLOUD_ARROW_UP "\xef\x83\xae" // U+f0ee
+#define ICON_FA_CLOUD_BOLT "\xef\x9d\xac" // U+f76c
+#define ICON_FA_CLOUD_MEATBALL "\xef\x9c\xbb" // U+f73b
+#define ICON_FA_CLOUD_MOON "\xef\x9b\x83" // U+f6c3
+#define ICON_FA_CLOUD_MOON_RAIN "\xef\x9c\xbc" // U+f73c
+#define ICON_FA_CLOUD_RAIN "\xef\x9c\xbd" // U+f73d
+#define ICON_FA_CLOUD_SHOWERS_HEAVY "\xef\x9d\x80" // U+f740
+#define ICON_FA_CLOUD_SHOWERS_WATER "\xee\x93\xa4" // U+e4e4
+#define ICON_FA_CLOUD_SUN "\xef\x9b\x84" // U+f6c4
+#define ICON_FA_CLOUD_SUN_RAIN "\xef\x9d\x83" // U+f743
+#define ICON_FA_CLOVER "\xee\x84\xb9" // U+e139
+#define ICON_FA_CODE "\xef\x84\xa1" // U+f121
+#define ICON_FA_CODE_BRANCH "\xef\x84\xa6" // U+f126
+#define ICON_FA_CODE_COMMIT "\xef\x8e\x86" // U+f386
+#define ICON_FA_CODE_COMPARE "\xee\x84\xba" // U+e13a
+#define ICON_FA_CODE_FORK "\xee\x84\xbb" // U+e13b
+#define ICON_FA_CODE_MERGE "\xef\x8e\x87" // U+f387
+#define ICON_FA_CODE_PULL_REQUEST "\xee\x84\xbc" // U+e13c
+#define ICON_FA_COINS "\xef\x94\x9e" // U+f51e
+#define ICON_FA_COLON_SIGN "\xee\x85\x80" // U+e140
+#define ICON_FA_COMMENT "\xef\x81\xb5" // U+f075
+#define ICON_FA_COMMENT_DOLLAR "\xef\x99\x91" // U+f651
+#define ICON_FA_COMMENT_DOTS "\xef\x92\xad" // U+f4ad
+#define ICON_FA_COMMENT_MEDICAL "\xef\x9f\xb5" // U+f7f5
+#define ICON_FA_COMMENT_SLASH "\xef\x92\xb3" // U+f4b3
+#define ICON_FA_COMMENT_SMS "\xef\x9f\x8d" // U+f7cd
+#define ICON_FA_COMMENTS "\xef\x82\x86" // U+f086
+#define ICON_FA_COMMENTS_DOLLAR "\xef\x99\x93" // U+f653
+#define ICON_FA_COMPACT_DISC "\xef\x94\x9f" // U+f51f
+#define ICON_FA_COMPASS "\xef\x85\x8e" // U+f14e
+#define ICON_FA_COMPASS_DRAFTING "\xef\x95\xa8" // U+f568
+#define ICON_FA_COMPRESS "\xef\x81\xa6" // U+f066
+#define ICON_FA_COMPUTER "\xee\x93\xa5" // U+e4e5
+#define ICON_FA_COMPUTER_MOUSE "\xef\xa3\x8c" // U+f8cc
+#define ICON_FA_COOKIE "\xef\x95\xa3" // U+f563
+#define ICON_FA_COOKIE_BITE "\xef\x95\xa4" // U+f564
+#define ICON_FA_COPY "\xef\x83\x85" // U+f0c5
+#define ICON_FA_COPYRIGHT "\xef\x87\xb9" // U+f1f9
+#define ICON_FA_COUCH "\xef\x92\xb8" // U+f4b8
+#define ICON_FA_COW "\xef\x9b\x88" // U+f6c8
+#define ICON_FA_CREDIT_CARD "\xef\x82\x9d" // U+f09d
+#define ICON_FA_CROP "\xef\x84\xa5" // U+f125
+#define ICON_FA_CROP_SIMPLE "\xef\x95\xa5" // U+f565
+#define ICON_FA_CROSS "\xef\x99\x94" // U+f654
+#define ICON_FA_CROSSHAIRS "\xef\x81\x9b" // U+f05b
+#define ICON_FA_CROW "\xef\x94\xa0" // U+f520
+#define ICON_FA_CROWN "\xef\x94\xa1" // U+f521
+#define ICON_FA_CRUTCH "\xef\x9f\xb7" // U+f7f7
+#define ICON_FA_CRUZEIRO_SIGN "\xee\x85\x92" // U+e152
+#define ICON_FA_CUBE "\xef\x86\xb2" // U+f1b2
+#define ICON_FA_CUBES "\xef\x86\xb3" // U+f1b3
+#define ICON_FA_CUBES_STACKED "\xee\x93\xa6" // U+e4e6
+#define ICON_FA_D "D" // U+44
+#define ICON_FA_DATABASE "\xef\x87\x80" // U+f1c0
+#define ICON_FA_DELETE_LEFT "\xef\x95\x9a" // U+f55a
+#define ICON_FA_DEMOCRAT "\xef\x9d\x87" // U+f747
+#define ICON_FA_DESKTOP "\xef\x8e\x90" // U+f390
+#define ICON_FA_DHARMACHAKRA "\xef\x99\x95" // U+f655
+#define ICON_FA_DIAGRAM_NEXT "\xee\x91\xb6" // U+e476
+#define ICON_FA_DIAGRAM_PREDECESSOR "\xee\x91\xb7" // U+e477
+#define ICON_FA_DIAGRAM_PROJECT "\xef\x95\x82" // U+f542
+#define ICON_FA_DIAGRAM_SUCCESSOR "\xee\x91\xba" // U+e47a
+#define ICON_FA_DIAMOND "\xef\x88\x99" // U+f219
+#define ICON_FA_DIAMOND_TURN_RIGHT "\xef\x97\xab" // U+f5eb
+#define ICON_FA_DICE "\xef\x94\xa2" // U+f522
+#define ICON_FA_DICE_D20 "\xef\x9b\x8f" // U+f6cf
+#define ICON_FA_DICE_D6 "\xef\x9b\x91" // U+f6d1
+#define ICON_FA_DICE_FIVE "\xef\x94\xa3" // U+f523
+#define ICON_FA_DICE_FOUR "\xef\x94\xa4" // U+f524
+#define ICON_FA_DICE_ONE "\xef\x94\xa5" // U+f525
+#define ICON_FA_DICE_SIX "\xef\x94\xa6" // U+f526
+#define ICON_FA_DICE_THREE "\xef\x94\xa7" // U+f527
+#define ICON_FA_DICE_TWO "\xef\x94\xa8" // U+f528
+#define ICON_FA_DISEASE "\xef\x9f\xba" // U+f7fa
+#define ICON_FA_DISPLAY "\xee\x85\xa3" // U+e163
+#define ICON_FA_DIVIDE "\xef\x94\xa9" // U+f529
+#define ICON_FA_DNA "\xef\x91\xb1" // U+f471
+#define ICON_FA_DOG "\xef\x9b\x93" // U+f6d3
+#define ICON_FA_DOLLAR_SIGN "$" // U+24
+#define ICON_FA_DOLLY "\xef\x91\xb2" // U+f472
+#define ICON_FA_DONG_SIGN "\xee\x85\xa9" // U+e169
+#define ICON_FA_DOOR_CLOSED "\xef\x94\xaa" // U+f52a
+#define ICON_FA_DOOR_OPEN "\xef\x94\xab" // U+f52b
+#define ICON_FA_DOVE "\xef\x92\xba" // U+f4ba
+#define ICON_FA_DOWN_LEFT_AND_UP_RIGHT_TO_CENTER "\xef\x90\xa2" // U+f422
+#define ICON_FA_DOWN_LONG "\xef\x8c\x89" // U+f309
+#define ICON_FA_DOWNLOAD "\xef\x80\x99" // U+f019
+#define ICON_FA_DRAGON "\xef\x9b\x95" // U+f6d5
+#define ICON_FA_DRAW_POLYGON "\xef\x97\xae" // U+f5ee
+#define ICON_FA_DROPLET "\xef\x81\x83" // U+f043
+#define ICON_FA_DROPLET_SLASH "\xef\x97\x87" // U+f5c7
+#define ICON_FA_DRUM "\xef\x95\xa9" // U+f569
+#define ICON_FA_DRUM_STEELPAN "\xef\x95\xaa" // U+f56a
+#define ICON_FA_DRUMSTICK_BITE "\xef\x9b\x97" // U+f6d7
+#define ICON_FA_DUMBBELL "\xef\x91\x8b" // U+f44b
+#define ICON_FA_DUMPSTER "\xef\x9e\x93" // U+f793
+#define ICON_FA_DUMPSTER_FIRE "\xef\x9e\x94" // U+f794
+#define ICON_FA_DUNGEON "\xef\x9b\x99" // U+f6d9
+#define ICON_FA_E "E" // U+45
+#define ICON_FA_EAR_DEAF "\xef\x8a\xa4" // U+f2a4
+#define ICON_FA_EAR_LISTEN "\xef\x8a\xa2" // U+f2a2
+#define ICON_FA_EARTH_AFRICA "\xef\x95\xbc" // U+f57c
+#define ICON_FA_EARTH_AMERICAS "\xef\x95\xbd" // U+f57d
+#define ICON_FA_EARTH_ASIA "\xef\x95\xbe" // U+f57e
+#define ICON_FA_EARTH_EUROPE "\xef\x9e\xa2" // U+f7a2
+#define ICON_FA_EARTH_OCEANIA "\xee\x91\xbb" // U+e47b
+#define ICON_FA_EGG "\xef\x9f\xbb" // U+f7fb
+#define ICON_FA_EJECT "\xef\x81\x92" // U+f052
+#define ICON_FA_ELEVATOR "\xee\x85\xad" // U+e16d
+#define ICON_FA_ELLIPSIS "\xef\x85\x81" // U+f141
+#define ICON_FA_ELLIPSIS_VERTICAL "\xef\x85\x82" // U+f142
+#define ICON_FA_ENVELOPE "\xef\x83\xa0" // U+f0e0
+#define ICON_FA_ENVELOPE_CIRCLE_CHECK "\xee\x93\xa8" // U+e4e8
+#define ICON_FA_ENVELOPE_OPEN "\xef\x8a\xb6" // U+f2b6
+#define ICON_FA_ENVELOPE_OPEN_TEXT "\xef\x99\x98" // U+f658
+#define ICON_FA_ENVELOPES_BULK "\xef\x99\xb4" // U+f674
+#define ICON_FA_EQUALS "=" // U+3d
+#define ICON_FA_ERASER "\xef\x84\xad" // U+f12d
+#define ICON_FA_ETHERNET "\xef\x9e\x96" // U+f796
+#define ICON_FA_EURO_SIGN "\xef\x85\x93" // U+f153
+#define ICON_FA_EXCLAMATION "!" // U+21
+#define ICON_FA_EXPAND "\xef\x81\xa5" // U+f065
+#define ICON_FA_EXPLOSION "\xee\x93\xa9" // U+e4e9
+#define ICON_FA_EYE "\xef\x81\xae" // U+f06e
+#define ICON_FA_EYE_DROPPER "\xef\x87\xbb" // U+f1fb
+#define ICON_FA_EYE_LOW_VISION "\xef\x8a\xa8" // U+f2a8
+#define ICON_FA_EYE_SLASH "\xef\x81\xb0" // U+f070
+#define ICON_FA_F "F" // U+46
+#define ICON_FA_FACE_ANGRY "\xef\x95\x96" // U+f556
+#define ICON_FA_FACE_DIZZY "\xef\x95\xa7" // U+f567
+#define ICON_FA_FACE_FLUSHED "\xef\x95\xb9" // U+f579
+#define ICON_FA_FACE_FROWN "\xef\x84\x99" // U+f119
+#define ICON_FA_FACE_FROWN_OPEN "\xef\x95\xba" // U+f57a
+#define ICON_FA_FACE_GRIMACE "\xef\x95\xbf" // U+f57f
+#define ICON_FA_FACE_GRIN "\xef\x96\x80" // U+f580
+#define ICON_FA_FACE_GRIN_BEAM "\xef\x96\x82" // U+f582
+#define ICON_FA_FACE_GRIN_BEAM_SWEAT "\xef\x96\x83" // U+f583
+#define ICON_FA_FACE_GRIN_HEARTS "\xef\x96\x84" // U+f584
+#define ICON_FA_FACE_GRIN_SQUINT "\xef\x96\x85" // U+f585
+#define ICON_FA_FACE_GRIN_SQUINT_TEARS "\xef\x96\x86" // U+f586
+#define ICON_FA_FACE_GRIN_STARS "\xef\x96\x87" // U+f587
+#define ICON_FA_FACE_GRIN_TEARS "\xef\x96\x88" // U+f588
+#define ICON_FA_FACE_GRIN_TONGUE "\xef\x96\x89" // U+f589
+#define ICON_FA_FACE_GRIN_TONGUE_SQUINT "\xef\x96\x8a" // U+f58a
+#define ICON_FA_FACE_GRIN_TONGUE_WINK "\xef\x96\x8b" // U+f58b
+#define ICON_FA_FACE_GRIN_WIDE "\xef\x96\x81" // U+f581
+#define ICON_FA_FACE_GRIN_WINK "\xef\x96\x8c" // U+f58c
+#define ICON_FA_FACE_KISS "\xef\x96\x96" // U+f596
+#define ICON_FA_FACE_KISS_BEAM "\xef\x96\x97" // U+f597
+#define ICON_FA_FACE_KISS_WINK_HEART "\xef\x96\x98" // U+f598
+#define ICON_FA_FACE_LAUGH "\xef\x96\x99" // U+f599
+#define ICON_FA_FACE_LAUGH_BEAM "\xef\x96\x9a" // U+f59a
+#define ICON_FA_FACE_LAUGH_SQUINT "\xef\x96\x9b" // U+f59b
+#define ICON_FA_FACE_LAUGH_WINK "\xef\x96\x9c" // U+f59c
+#define ICON_FA_FACE_MEH "\xef\x84\x9a" // U+f11a
+#define ICON_FA_FACE_MEH_BLANK "\xef\x96\xa4" // U+f5a4
+#define ICON_FA_FACE_ROLLING_EYES "\xef\x96\xa5" // U+f5a5
+#define ICON_FA_FACE_SAD_CRY "\xef\x96\xb3" // U+f5b3
+#define ICON_FA_FACE_SAD_TEAR "\xef\x96\xb4" // U+f5b4
+#define ICON_FA_FACE_SMILE "\xef\x84\x98" // U+f118
+#define ICON_FA_FACE_SMILE_BEAM "\xef\x96\xb8" // U+f5b8
+#define ICON_FA_FACE_SMILE_WINK "\xef\x93\x9a" // U+f4da
+#define ICON_FA_FACE_SURPRISE "\xef\x97\x82" // U+f5c2
+#define ICON_FA_FACE_TIRED "\xef\x97\x88" // U+f5c8
+#define ICON_FA_FAN "\xef\xa1\xa3" // U+f863
+#define ICON_FA_FAUCET "\xee\x80\x85" // U+e005
+#define ICON_FA_FAUCET_DRIP "\xee\x80\x86" // U+e006
+#define ICON_FA_FAX "\xef\x86\xac" // U+f1ac
+#define ICON_FA_FEATHER "\xef\x94\xad" // U+f52d
+#define ICON_FA_FEATHER_POINTED "\xef\x95\xab" // U+f56b
+#define ICON_FA_FERRY "\xee\x93\xaa" // U+e4ea
+#define ICON_FA_FILE "\xef\x85\x9b" // U+f15b
+#define ICON_FA_FILE_ARROW_DOWN "\xef\x95\xad" // U+f56d
+#define ICON_FA_FILE_ARROW_UP "\xef\x95\xb4" // U+f574
+#define ICON_FA_FILE_AUDIO "\xef\x87\x87" // U+f1c7
+#define ICON_FA_FILE_CIRCLE_CHECK "\xee\x92\x93" // U+e493
+#define ICON_FA_FILE_CIRCLE_EXCLAMATION "\xee\x93\xab" // U+e4eb
+#define ICON_FA_FILE_CIRCLE_MINUS "\xee\x93\xad" // U+e4ed
+#define ICON_FA_FILE_CIRCLE_PLUS "\xee\x93\xae" // U+e4ee
+#define ICON_FA_FILE_CIRCLE_QUESTION "\xee\x93\xaf" // U+e4ef
+#define ICON_FA_FILE_CIRCLE_XMARK "\xee\x92\x94" // U+e494
+#define ICON_FA_FILE_CODE "\xef\x87\x89" // U+f1c9
+#define ICON_FA_FILE_CONTRACT "\xef\x95\xac" // U+f56c
+#define ICON_FA_FILE_CSV "\xef\x9b\x9d" // U+f6dd
+#define ICON_FA_FILE_EXCEL "\xef\x87\x83" // U+f1c3
+#define ICON_FA_FILE_EXPORT "\xef\x95\xae" // U+f56e
+#define ICON_FA_FILE_IMAGE "\xef\x87\x85" // U+f1c5
+#define ICON_FA_FILE_IMPORT "\xef\x95\xaf" // U+f56f
+#define ICON_FA_FILE_INVOICE "\xef\x95\xb0" // U+f570
+#define ICON_FA_FILE_INVOICE_DOLLAR "\xef\x95\xb1" // U+f571
+#define ICON_FA_FILE_LINES "\xef\x85\x9c" // U+f15c
+#define ICON_FA_FILE_MEDICAL "\xef\x91\xb7" // U+f477
+#define ICON_FA_FILE_PDF "\xef\x87\x81" // U+f1c1
+#define ICON_FA_FILE_PEN "\xef\x8c\x9c" // U+f31c
+#define ICON_FA_FILE_POWERPOINT "\xef\x87\x84" // U+f1c4
+#define ICON_FA_FILE_PRESCRIPTION "\xef\x95\xb2" // U+f572
+#define ICON_FA_FILE_SHIELD "\xee\x93\xb0" // U+e4f0
+#define ICON_FA_FILE_SIGNATURE "\xef\x95\xb3" // U+f573
+#define ICON_FA_FILE_VIDEO "\xef\x87\x88" // U+f1c8
+#define ICON_FA_FILE_WAVEFORM "\xef\x91\xb8" // U+f478
+#define ICON_FA_FILE_WORD "\xef\x87\x82" // U+f1c2
+#define ICON_FA_FILE_ZIPPER "\xef\x87\x86" // U+f1c6
+#define ICON_FA_FILL "\xef\x95\xb5" // U+f575
+#define ICON_FA_FILL_DRIP "\xef\x95\xb6" // U+f576
+#define ICON_FA_FILM "\xef\x80\x88" // U+f008
+#define ICON_FA_FILTER "\xef\x82\xb0" // U+f0b0
+#define ICON_FA_FILTER_CIRCLE_DOLLAR "\xef\x99\xa2" // U+f662
+#define ICON_FA_FILTER_CIRCLE_XMARK "\xee\x85\xbb" // U+e17b
+#define ICON_FA_FINGERPRINT "\xef\x95\xb7" // U+f577
+#define ICON_FA_FIRE "\xef\x81\xad" // U+f06d
+#define ICON_FA_FIRE_BURNER "\xee\x93\xb1" // U+e4f1
+#define ICON_FA_FIRE_EXTINGUISHER "\xef\x84\xb4" // U+f134
+#define ICON_FA_FIRE_FLAME_CURVED "\xef\x9f\xa4" // U+f7e4
+#define ICON_FA_FIRE_FLAME_SIMPLE "\xef\x91\xaa" // U+f46a
+#define ICON_FA_FISH "\xef\x95\xb8" // U+f578
+#define ICON_FA_FISH_FINS "\xee\x93\xb2" // U+e4f2
+#define ICON_FA_FLAG "\xef\x80\xa4" // U+f024
+#define ICON_FA_FLAG_CHECKERED "\xef\x84\x9e" // U+f11e
+#define ICON_FA_FLAG_USA "\xef\x9d\x8d" // U+f74d
+#define ICON_FA_FLASK "\xef\x83\x83" // U+f0c3
+#define ICON_FA_FLASK_VIAL "\xee\x93\xb3" // U+e4f3
+#define ICON_FA_FLOPPY_DISK "\xef\x83\x87" // U+f0c7
+#define ICON_FA_FLORIN_SIGN "\xee\x86\x84" // U+e184
+#define ICON_FA_FOLDER "\xef\x81\xbb" // U+f07b
+#define ICON_FA_FOLDER_CLOSED "\xee\x86\x85" // U+e185
+#define ICON_FA_FOLDER_MINUS "\xef\x99\x9d" // U+f65d
+#define ICON_FA_FOLDER_OPEN "\xef\x81\xbc" // U+f07c
+#define ICON_FA_FOLDER_PLUS "\xef\x99\x9e" // U+f65e
+#define ICON_FA_FOLDER_TREE "\xef\xa0\x82" // U+f802
+#define ICON_FA_FONT "\xef\x80\xb1" // U+f031
+#define ICON_FA_FONT_AWESOME "\xef\x8a\xb4" // U+f2b4
+#define ICON_FA_FOOTBALL "\xef\x91\x8e" // U+f44e
+#define ICON_FA_FORWARD "\xef\x81\x8e" // U+f04e
+#define ICON_FA_FORWARD_FAST "\xef\x81\x90" // U+f050
+#define ICON_FA_FORWARD_STEP "\xef\x81\x91" // U+f051
+#define ICON_FA_FRANC_SIGN "\xee\x86\x8f" // U+e18f
+#define ICON_FA_FROG "\xef\x94\xae" // U+f52e
+#define ICON_FA_FUTBOL "\xef\x87\xa3" // U+f1e3
+#define ICON_FA_G "G" // U+47
+#define ICON_FA_GAMEPAD "\xef\x84\x9b" // U+f11b
+#define ICON_FA_GAS_PUMP "\xef\x94\xaf" // U+f52f
+#define ICON_FA_GAUGE "\xef\x98\xa4" // U+f624
+#define ICON_FA_GAUGE_HIGH "\xef\x98\xa5" // U+f625
+#define ICON_FA_GAUGE_SIMPLE "\xef\x98\xa9" // U+f629
+#define ICON_FA_GAUGE_SIMPLE_HIGH "\xef\x98\xaa" // U+f62a
+#define ICON_FA_GAVEL "\xef\x83\xa3" // U+f0e3
+#define ICON_FA_GEAR "\xef\x80\x93" // U+f013
+#define ICON_FA_GEARS "\xef\x82\x85" // U+f085
+#define ICON_FA_GEM "\xef\x8e\xa5" // U+f3a5
+#define ICON_FA_GENDERLESS "\xef\x88\xad" // U+f22d
+#define ICON_FA_GHOST "\xef\x9b\xa2" // U+f6e2
+#define ICON_FA_GIFT "\xef\x81\xab" // U+f06b
+#define ICON_FA_GIFTS "\xef\x9e\x9c" // U+f79c
+#define ICON_FA_GLASS_WATER "\xee\x93\xb4" // U+e4f4
+#define ICON_FA_GLASS_WATER_DROPLET "\xee\x93\xb5" // U+e4f5
+#define ICON_FA_GLASSES "\xef\x94\xb0" // U+f530
+#define ICON_FA_GLOBE "\xef\x82\xac" // U+f0ac
+#define ICON_FA_GOLF_BALL_TEE "\xef\x91\x90" // U+f450
+#define ICON_FA_GOPURAM "\xef\x99\xa4" // U+f664
+#define ICON_FA_GRADUATION_CAP "\xef\x86\x9d" // U+f19d
+#define ICON_FA_GREATER_THAN ">" // U+3e
+#define ICON_FA_GREATER_THAN_EQUAL "\xef\x94\xb2" // U+f532
+#define ICON_FA_GRIP "\xef\x96\x8d" // U+f58d
+#define ICON_FA_GRIP_LINES "\xef\x9e\xa4" // U+f7a4
+#define ICON_FA_GRIP_LINES_VERTICAL "\xef\x9e\xa5" // U+f7a5
+#define ICON_FA_GRIP_VERTICAL "\xef\x96\x8e" // U+f58e
+#define ICON_FA_GROUP_ARROWS_ROTATE "\xee\x93\xb6" // U+e4f6
+#define ICON_FA_GUARANI_SIGN "\xee\x86\x9a" // U+e19a
+#define ICON_FA_GUITAR "\xef\x9e\xa6" // U+f7a6
+#define ICON_FA_GUN "\xee\x86\x9b" // U+e19b
+#define ICON_FA_H "H" // U+48
+#define ICON_FA_HAMMER "\xef\x9b\xa3" // U+f6e3
+#define ICON_FA_HAMSA "\xef\x99\xa5" // U+f665
+#define ICON_FA_HAND "\xef\x89\x96" // U+f256
+#define ICON_FA_HAND_BACK_FIST "\xef\x89\x95" // U+f255
+#define ICON_FA_HAND_DOTS "\xef\x91\xa1" // U+f461
+#define ICON_FA_HAND_FIST "\xef\x9b\x9e" // U+f6de
+#define ICON_FA_HAND_HOLDING "\xef\x92\xbd" // U+f4bd
+#define ICON_FA_HAND_HOLDING_DOLLAR "\xef\x93\x80" // U+f4c0
+#define ICON_FA_HAND_HOLDING_DROPLET "\xef\x93\x81" // U+f4c1
+#define ICON_FA_HAND_HOLDING_HAND "\xee\x93\xb7" // U+e4f7
+#define ICON_FA_HAND_HOLDING_HEART "\xef\x92\xbe" // U+f4be
+#define ICON_FA_HAND_HOLDING_MEDICAL "\xee\x81\x9c" // U+e05c
+#define ICON_FA_HAND_LIZARD "\xef\x89\x98" // U+f258
+#define ICON_FA_HAND_MIDDLE_FINGER "\xef\xa0\x86" // U+f806
+#define ICON_FA_HAND_PEACE "\xef\x89\x9b" // U+f25b
+#define ICON_FA_HAND_POINT_DOWN "\xef\x82\xa7" // U+f0a7
+#define ICON_FA_HAND_POINT_LEFT "\xef\x82\xa5" // U+f0a5
+#define ICON_FA_HAND_POINT_RIGHT "\xef\x82\xa4" // U+f0a4
+#define ICON_FA_HAND_POINT_UP "\xef\x82\xa6" // U+f0a6
+#define ICON_FA_HAND_POINTER "\xef\x89\x9a" // U+f25a
+#define ICON_FA_HAND_SCISSORS "\xef\x89\x97" // U+f257
+#define ICON_FA_HAND_SPARKLES "\xee\x81\x9d" // U+e05d
+#define ICON_FA_HAND_SPOCK "\xef\x89\x99" // U+f259
+#define ICON_FA_HANDCUFFS "\xee\x93\xb8" // U+e4f8
+#define ICON_FA_HANDS "\xef\x8a\xa7" // U+f2a7
+#define ICON_FA_HANDS_ASL_INTERPRETING "\xef\x8a\xa3" // U+f2a3
+#define ICON_FA_HANDS_BOUND "\xee\x93\xb9" // U+e4f9
+#define ICON_FA_HANDS_BUBBLES "\xee\x81\x9e" // U+e05e
+#define ICON_FA_HANDS_CLAPPING "\xee\x86\xa8" // U+e1a8
+#define ICON_FA_HANDS_HOLDING "\xef\x93\x82" // U+f4c2
+#define ICON_FA_HANDS_HOLDING_CHILD "\xee\x93\xba" // U+e4fa
+#define ICON_FA_HANDS_HOLDING_CIRCLE "\xee\x93\xbb" // U+e4fb
+#define ICON_FA_HANDS_PRAYING "\xef\x9a\x84" // U+f684
+#define ICON_FA_HANDSHAKE "\xef\x8a\xb5" // U+f2b5
+#define ICON_FA_HANDSHAKE_ANGLE "\xef\x93\x84" // U+f4c4
+#define ICON_FA_HANDSHAKE_SIMPLE "\xef\x93\x86" // U+f4c6
+#define ICON_FA_HANDSHAKE_SIMPLE_SLASH "\xee\x81\x9f" // U+e05f
+#define ICON_FA_HANDSHAKE_SLASH "\xee\x81\xa0" // U+e060
+#define ICON_FA_HANUKIAH "\xef\x9b\xa6" // U+f6e6
+#define ICON_FA_HARD_DRIVE "\xef\x82\xa0" // U+f0a0
+#define ICON_FA_HASHTAG "#" // U+23
+#define ICON_FA_HAT_COWBOY "\xef\xa3\x80" // U+f8c0
+#define ICON_FA_HAT_COWBOY_SIDE "\xef\xa3\x81" // U+f8c1
+#define ICON_FA_HAT_WIZARD "\xef\x9b\xa8" // U+f6e8
+#define ICON_FA_HEAD_SIDE_COUGH "\xee\x81\xa1" // U+e061
+#define ICON_FA_HEAD_SIDE_COUGH_SLASH "\xee\x81\xa2" // U+e062
+#define ICON_FA_HEAD_SIDE_MASK "\xee\x81\xa3" // U+e063
+#define ICON_FA_HEAD_SIDE_VIRUS "\xee\x81\xa4" // U+e064
+#define ICON_FA_HEADING "\xef\x87\x9c" // U+f1dc
+#define ICON_FA_HEADPHONES "\xef\x80\xa5" // U+f025
+#define ICON_FA_HEADPHONES_SIMPLE "\xef\x96\x8f" // U+f58f
+#define ICON_FA_HEADSET "\xef\x96\x90" // U+f590
+#define ICON_FA_HEART "\xef\x80\x84" // U+f004
+#define ICON_FA_HEART_CIRCLE_BOLT "\xee\x93\xbc" // U+e4fc
+#define ICON_FA_HEART_CIRCLE_CHECK "\xee\x93\xbd" // U+e4fd
+#define ICON_FA_HEART_CIRCLE_EXCLAMATION "\xee\x93\xbe" // U+e4fe
+#define ICON_FA_HEART_CIRCLE_MINUS "\xee\x93\xbf" // U+e4ff
+#define ICON_FA_HEART_CIRCLE_PLUS "\xee\x94\x80" // U+e500
+#define ICON_FA_HEART_CIRCLE_XMARK "\xee\x94\x81" // U+e501
+#define ICON_FA_HEART_CRACK "\xef\x9e\xa9" // U+f7a9
+#define ICON_FA_HEART_PULSE "\xef\x88\x9e" // U+f21e
+#define ICON_FA_HELICOPTER "\xef\x94\xb3" // U+f533
+#define ICON_FA_HELICOPTER_SYMBOL "\xee\x94\x82" // U+e502
+#define ICON_FA_HELMET_SAFETY "\xef\xa0\x87" // U+f807
+#define ICON_FA_HELMET_UN "\xee\x94\x83" // U+e503
+#define ICON_FA_HIGHLIGHTER "\xef\x96\x91" // U+f591
+#define ICON_FA_HILL_AVALANCHE "\xee\x94\x87" // U+e507
+#define ICON_FA_HILL_ROCKSLIDE "\xee\x94\x88" // U+e508
+#define ICON_FA_HIPPO "\xef\x9b\xad" // U+f6ed
+#define ICON_FA_HOCKEY_PUCK "\xef\x91\x93" // U+f453
+#define ICON_FA_HOLLY_BERRY "\xef\x9e\xaa" // U+f7aa
+#define ICON_FA_HORSE "\xef\x9b\xb0" // U+f6f0
+#define ICON_FA_HORSE_HEAD "\xef\x9e\xab" // U+f7ab
+#define ICON_FA_HOSPITAL "\xef\x83\xb8" // U+f0f8
+#define ICON_FA_HOSPITAL_USER "\xef\xa0\x8d" // U+f80d
+#define ICON_FA_HOT_TUB_PERSON "\xef\x96\x93" // U+f593
+#define ICON_FA_HOTDOG "\xef\xa0\x8f" // U+f80f
+#define ICON_FA_HOTEL "\xef\x96\x94" // U+f594
+#define ICON_FA_HOURGLASS "\xef\x89\x94" // U+f254
+#define ICON_FA_HOURGLASS_EMPTY "\xef\x89\x92" // U+f252
+#define ICON_FA_HOURGLASS_END "\xef\x89\x93" // U+f253
+#define ICON_FA_HOURGLASS_START "\xef\x89\x91" // U+f251
+#define ICON_FA_HOUSE "\xef\x80\x95" // U+f015
+#define ICON_FA_HOUSE_CHIMNEY "\xee\x8e\xaf" // U+e3af
+#define ICON_FA_HOUSE_CHIMNEY_CRACK "\xef\x9b\xb1" // U+f6f1
+#define ICON_FA_HOUSE_CHIMNEY_MEDICAL "\xef\x9f\xb2" // U+f7f2
+#define ICON_FA_HOUSE_CHIMNEY_USER "\xee\x81\xa5" // U+e065
+#define ICON_FA_HOUSE_CHIMNEY_WINDOW "\xee\x80\x8d" // U+e00d
+#define ICON_FA_HOUSE_CIRCLE_CHECK "\xee\x94\x89" // U+e509
+#define ICON_FA_HOUSE_CIRCLE_EXCLAMATION "\xee\x94\x8a" // U+e50a
+#define ICON_FA_HOUSE_CIRCLE_XMARK "\xee\x94\x8b" // U+e50b
+#define ICON_FA_HOUSE_CRACK "\xee\x8e\xb1" // U+e3b1
+#define ICON_FA_HOUSE_FIRE "\xee\x94\x8c" // U+e50c
+#define ICON_FA_HOUSE_FLAG "\xee\x94\x8d" // U+e50d
+#define ICON_FA_HOUSE_FLOOD_WATER "\xee\x94\x8e" // U+e50e
+#define ICON_FA_HOUSE_FLOOD_WATER_CIRCLE_ARROW_RIGHT "\xee\x94\x8f" // U+e50f
+#define ICON_FA_HOUSE_LAPTOP "\xee\x81\xa6" // U+e066
+#define ICON_FA_HOUSE_LOCK "\xee\x94\x90" // U+e510
+#define ICON_FA_HOUSE_MEDICAL "\xee\x8e\xb2" // U+e3b2
+#define ICON_FA_HOUSE_MEDICAL_CIRCLE_CHECK "\xee\x94\x91" // U+e511
+#define ICON_FA_HOUSE_MEDICAL_CIRCLE_EXCLAMATION "\xee\x94\x92" // U+e512
+#define ICON_FA_HOUSE_MEDICAL_CIRCLE_XMARK "\xee\x94\x93" // U+e513
+#define ICON_FA_HOUSE_MEDICAL_FLAG "\xee\x94\x94" // U+e514
+#define ICON_FA_HOUSE_SIGNAL "\xee\x80\x92" // U+e012
+#define ICON_FA_HOUSE_TSUNAMI "\xee\x94\x95" // U+e515
+#define ICON_FA_HOUSE_USER "\xee\x86\xb0" // U+e1b0
+#define ICON_FA_HRYVNIA_SIGN "\xef\x9b\xb2" // U+f6f2
+#define ICON_FA_HURRICANE "\xef\x9d\x91" // U+f751
+#define ICON_FA_I "I" // U+49
+#define ICON_FA_I_CURSOR "\xef\x89\x86" // U+f246
+#define ICON_FA_ICE_CREAM "\xef\xa0\x90" // U+f810
+#define ICON_FA_ICICLES "\xef\x9e\xad" // U+f7ad
+#define ICON_FA_ICONS "\xef\xa1\xad" // U+f86d
+#define ICON_FA_ID_BADGE "\xef\x8b\x81" // U+f2c1
+#define ICON_FA_ID_CARD "\xef\x8b\x82" // U+f2c2
+#define ICON_FA_ID_CARD_CLIP "\xef\x91\xbf" // U+f47f
+#define ICON_FA_IGLOO "\xef\x9e\xae" // U+f7ae
+#define ICON_FA_IMAGE "\xef\x80\xbe" // U+f03e
+#define ICON_FA_IMAGE_PORTRAIT "\xef\x8f\xa0" // U+f3e0
+#define ICON_FA_IMAGES "\xef\x8c\x82" // U+f302
+#define ICON_FA_INBOX "\xef\x80\x9c" // U+f01c
+#define ICON_FA_INDENT "\xef\x80\xbc" // U+f03c
+#define ICON_FA_INDIAN_RUPEE_SIGN "\xee\x86\xbc" // U+e1bc
+#define ICON_FA_INDUSTRY "\xef\x89\xb5" // U+f275
+#define ICON_FA_INFINITY "\xef\x94\xb4" // U+f534
+#define ICON_FA_INFO "\xef\x84\xa9" // U+f129
+#define ICON_FA_ITALIC "\xef\x80\xb3" // U+f033
+#define ICON_FA_J "J" // U+4a
+#define ICON_FA_JAR "\xee\x94\x96" // U+e516
+#define ICON_FA_JAR_WHEAT "\xee\x94\x97" // U+e517
+#define ICON_FA_JEDI "\xef\x99\xa9" // U+f669
+#define ICON_FA_JET_FIGHTER "\xef\x83\xbb" // U+f0fb
+#define ICON_FA_JET_FIGHTER_UP "\xee\x94\x98" // U+e518
+#define ICON_FA_JOINT "\xef\x96\x95" // U+f595
+#define ICON_FA_JUG_DETERGENT "\xee\x94\x99" // U+e519
+#define ICON_FA_K "K" // U+4b
+#define ICON_FA_KAABA "\xef\x99\xab" // U+f66b
+#define ICON_FA_KEY "\xef\x82\x84" // U+f084
+#define ICON_FA_KEYBOARD "\xef\x84\x9c" // U+f11c
+#define ICON_FA_KHANDA "\xef\x99\xad" // U+f66d
+#define ICON_FA_KIP_SIGN "\xee\x87\x84" // U+e1c4
+#define ICON_FA_KIT_MEDICAL "\xef\x91\xb9" // U+f479
+#define ICON_FA_KITCHEN_SET "\xee\x94\x9a" // U+e51a
+#define ICON_FA_KIWI_BIRD "\xef\x94\xb5" // U+f535
+#define ICON_FA_L "L" // U+4c
+#define ICON_FA_LAND_MINE_ON "\xee\x94\x9b" // U+e51b
+#define ICON_FA_LANDMARK "\xef\x99\xaf" // U+f66f
+#define ICON_FA_LANDMARK_DOME "\xef\x9d\x92" // U+f752
+#define ICON_FA_LANDMARK_FLAG "\xee\x94\x9c" // U+e51c
+#define ICON_FA_LANGUAGE "\xef\x86\xab" // U+f1ab
+#define ICON_FA_LAPTOP "\xef\x84\x89" // U+f109
+#define ICON_FA_LAPTOP_CODE "\xef\x97\xbc" // U+f5fc
+#define ICON_FA_LAPTOP_FILE "\xee\x94\x9d" // U+e51d
+#define ICON_FA_LAPTOP_MEDICAL "\xef\xa0\x92" // U+f812
+#define ICON_FA_LARI_SIGN "\xee\x87\x88" // U+e1c8
+#define ICON_FA_LAYER_GROUP "\xef\x97\xbd" // U+f5fd
+#define ICON_FA_LEAF "\xef\x81\xac" // U+f06c
+#define ICON_FA_LEFT_LONG "\xef\x8c\x8a" // U+f30a
+#define ICON_FA_LEFT_RIGHT "\xef\x8c\xb7" // U+f337
+#define ICON_FA_LEMON "\xef\x82\x94" // U+f094
+#define ICON_FA_LESS_THAN "<" // U+3c
+#define ICON_FA_LESS_THAN_EQUAL "\xef\x94\xb7" // U+f537
+#define ICON_FA_LIFE_RING "\xef\x87\x8d" // U+f1cd
+#define ICON_FA_LIGHTBULB "\xef\x83\xab" // U+f0eb
+#define ICON_FA_LINES_LEANING "\xee\x94\x9e" // U+e51e
+#define ICON_FA_LINK "\xef\x83\x81" // U+f0c1
+#define ICON_FA_LINK_SLASH "\xef\x84\xa7" // U+f127
+#define ICON_FA_LIRA_SIGN "\xef\x86\x95" // U+f195
+#define ICON_FA_LIST "\xef\x80\xba" // U+f03a
+#define ICON_FA_LIST_CHECK "\xef\x82\xae" // U+f0ae
+#define ICON_FA_LIST_OL "\xef\x83\x8b" // U+f0cb
+#define ICON_FA_LIST_UL "\xef\x83\x8a" // U+f0ca
+#define ICON_FA_LITECOIN_SIGN "\xee\x87\x93" // U+e1d3
+#define ICON_FA_LOCATION_ARROW "\xef\x84\xa4" // U+f124
+#define ICON_FA_LOCATION_CROSSHAIRS "\xef\x98\x81" // U+f601
+#define ICON_FA_LOCATION_DOT "\xef\x8f\x85" // U+f3c5
+#define ICON_FA_LOCATION_PIN "\xef\x81\x81" // U+f041
+#define ICON_FA_LOCATION_PIN_LOCK "\xee\x94\x9f" // U+e51f
+#define ICON_FA_LOCK "\xef\x80\xa3" // U+f023
+#define ICON_FA_LOCK_OPEN "\xef\x8f\x81" // U+f3c1
+#define ICON_FA_LOCUST "\xee\x94\xa0" // U+e520
+#define ICON_FA_LUNGS "\xef\x98\x84" // U+f604
+#define ICON_FA_LUNGS_VIRUS "\xee\x81\xa7" // U+e067
+#define ICON_FA_M "M" // U+4d
+#define ICON_FA_MAGNET "\xef\x81\xb6" // U+f076
+#define ICON_FA_MAGNIFYING_GLASS "\xef\x80\x82" // U+f002
+#define ICON_FA_MAGNIFYING_GLASS_ARROW_RIGHT "\xee\x94\xa1" // U+e521
+#define ICON_FA_MAGNIFYING_GLASS_CHART "\xee\x94\xa2" // U+e522
+#define ICON_FA_MAGNIFYING_GLASS_DOLLAR "\xef\x9a\x88" // U+f688
+#define ICON_FA_MAGNIFYING_GLASS_LOCATION "\xef\x9a\x89" // U+f689
+#define ICON_FA_MAGNIFYING_GLASS_MINUS "\xef\x80\x90" // U+f010
+#define ICON_FA_MAGNIFYING_GLASS_PLUS "\xef\x80\x8e" // U+f00e
+#define ICON_FA_MANAT_SIGN "\xee\x87\x95" // U+e1d5
+#define ICON_FA_MAP "\xef\x89\xb9" // U+f279
+#define ICON_FA_MAP_LOCATION "\xef\x96\x9f" // U+f59f
+#define ICON_FA_MAP_LOCATION_DOT "\xef\x96\xa0" // U+f5a0
+#define ICON_FA_MAP_PIN "\xef\x89\xb6" // U+f276
+#define ICON_FA_MARKER "\xef\x96\xa1" // U+f5a1
+#define ICON_FA_MARS "\xef\x88\xa2" // U+f222
+#define ICON_FA_MARS_AND_VENUS "\xef\x88\xa4" // U+f224
+#define ICON_FA_MARS_AND_VENUS_BURST "\xee\x94\xa3" // U+e523
+#define ICON_FA_MARS_DOUBLE "\xef\x88\xa7" // U+f227
+#define ICON_FA_MARS_STROKE "\xef\x88\xa9" // U+f229
+#define ICON_FA_MARS_STROKE_RIGHT "\xef\x88\xab" // U+f22b
+#define ICON_FA_MARS_STROKE_UP "\xef\x88\xaa" // U+f22a
+#define ICON_FA_MARTINI_GLASS "\xef\x95\xbb" // U+f57b
+#define ICON_FA_MARTINI_GLASS_CITRUS "\xef\x95\xa1" // U+f561
+#define ICON_FA_MARTINI_GLASS_EMPTY "\xef\x80\x80" // U+f000
+#define ICON_FA_MASK "\xef\x9b\xba" // U+f6fa
+#define ICON_FA_MASK_FACE "\xee\x87\x97" // U+e1d7
+#define ICON_FA_MASK_VENTILATOR "\xee\x94\xa4" // U+e524
+#define ICON_FA_MASKS_THEATER "\xef\x98\xb0" // U+f630
+#define ICON_FA_MATTRESS_PILLOW "\xee\x94\xa5" // U+e525
+#define ICON_FA_MAXIMIZE "\xef\x8c\x9e" // U+f31e
+#define ICON_FA_MEDAL "\xef\x96\xa2" // U+f5a2
+#define ICON_FA_MEMORY "\xef\x94\xb8" // U+f538
+#define ICON_FA_MENORAH "\xef\x99\xb6" // U+f676
+#define ICON_FA_MERCURY "\xef\x88\xa3" // U+f223
+#define ICON_FA_MESSAGE "\xef\x89\xba" // U+f27a
+#define ICON_FA_METEOR "\xef\x9d\x93" // U+f753
+#define ICON_FA_MICROCHIP "\xef\x8b\x9b" // U+f2db
+#define ICON_FA_MICROPHONE "\xef\x84\xb0" // U+f130
+#define ICON_FA_MICROPHONE_LINES "\xef\x8f\x89" // U+f3c9
+#define ICON_FA_MICROPHONE_LINES_SLASH "\xef\x94\xb9" // U+f539
+#define ICON_FA_MICROPHONE_SLASH "\xef\x84\xb1" // U+f131
+#define ICON_FA_MICROSCOPE "\xef\x98\x90" // U+f610
+#define ICON_FA_MILL_SIGN "\xee\x87\xad" // U+e1ed
+#define ICON_FA_MINIMIZE "\xef\x9e\x8c" // U+f78c
+#define ICON_FA_MINUS "\xef\x81\xa8" // U+f068
+#define ICON_FA_MITTEN "\xef\x9e\xb5" // U+f7b5
+#define ICON_FA_MOBILE "\xef\x8f\x8e" // U+f3ce
+#define ICON_FA_MOBILE_BUTTON "\xef\x84\x8b" // U+f10b
+#define ICON_FA_MOBILE_RETRO "\xee\x94\xa7" // U+e527
+#define ICON_FA_MOBILE_SCREEN "\xef\x8f\x8f" // U+f3cf
+#define ICON_FA_MOBILE_SCREEN_BUTTON "\xef\x8f\x8d" // U+f3cd
+#define ICON_FA_MONEY_BILL "\xef\x83\x96" // U+f0d6
+#define ICON_FA_MONEY_BILL_1 "\xef\x8f\x91" // U+f3d1
+#define ICON_FA_MONEY_BILL_1_WAVE "\xef\x94\xbb" // U+f53b
+#define ICON_FA_MONEY_BILL_TRANSFER "\xee\x94\xa8" // U+e528
+#define ICON_FA_MONEY_BILL_TREND_UP "\xee\x94\xa9" // U+e529
+#define ICON_FA_MONEY_BILL_WAVE "\xef\x94\xba" // U+f53a
+#define ICON_FA_MONEY_BILL_WHEAT "\xee\x94\xaa" // U+e52a
+#define ICON_FA_MONEY_BILLS "\xee\x87\xb3" // U+e1f3
+#define ICON_FA_MONEY_CHECK "\xef\x94\xbc" // U+f53c
+#define ICON_FA_MONEY_CHECK_DOLLAR "\xef\x94\xbd" // U+f53d
+#define ICON_FA_MONUMENT "\xef\x96\xa6" // U+f5a6
+#define ICON_FA_MOON "\xef\x86\x86" // U+f186
+#define ICON_FA_MORTAR_PESTLE "\xef\x96\xa7" // U+f5a7
+#define ICON_FA_MOSQUE "\xef\x99\xb8" // U+f678
+#define ICON_FA_MOSQUITO "\xee\x94\xab" // U+e52b
+#define ICON_FA_MOSQUITO_NET "\xee\x94\xac" // U+e52c
+#define ICON_FA_MOTORCYCLE "\xef\x88\x9c" // U+f21c
+#define ICON_FA_MOUND "\xee\x94\xad" // U+e52d
+#define ICON_FA_MOUNTAIN "\xef\x9b\xbc" // U+f6fc
+#define ICON_FA_MOUNTAIN_CITY "\xee\x94\xae" // U+e52e
+#define ICON_FA_MOUNTAIN_SUN "\xee\x94\xaf" // U+e52f
+#define ICON_FA_MUG_HOT "\xef\x9e\xb6" // U+f7b6
+#define ICON_FA_MUG_SAUCER "\xef\x83\xb4" // U+f0f4
+#define ICON_FA_MUSIC "\xef\x80\x81" // U+f001
+#define ICON_FA_N "N" // U+4e
+#define ICON_FA_NAIRA_SIGN "\xee\x87\xb6" // U+e1f6
+#define ICON_FA_NETWORK_WIRED "\xef\x9b\xbf" // U+f6ff
+#define ICON_FA_NEUTER "\xef\x88\xac" // U+f22c
+#define ICON_FA_NEWSPAPER "\xef\x87\xaa" // U+f1ea
+#define ICON_FA_NOT_EQUAL "\xef\x94\xbe" // U+f53e
+#define ICON_FA_NOTE_STICKY "\xef\x89\x89" // U+f249
+#define ICON_FA_NOTES_MEDICAL "\xef\x92\x81" // U+f481
+#define ICON_FA_O "O" // U+4f
+#define ICON_FA_OBJECT_GROUP "\xef\x89\x87" // U+f247
+#define ICON_FA_OBJECT_UNGROUP "\xef\x89\x88" // U+f248
+#define ICON_FA_OIL_CAN "\xef\x98\x93" // U+f613
+#define ICON_FA_OIL_WELL "\xee\x94\xb2" // U+e532
+#define ICON_FA_OM "\xef\x99\xb9" // U+f679
+#define ICON_FA_OTTER "\xef\x9c\x80" // U+f700
+#define ICON_FA_OUTDENT "\xef\x80\xbb" // U+f03b
+#define ICON_FA_P "P" // U+50
+#define ICON_FA_PAGER "\xef\xa0\x95" // U+f815
+#define ICON_FA_PAINT_ROLLER "\xef\x96\xaa" // U+f5aa
+#define ICON_FA_PAINTBRUSH "\xef\x87\xbc" // U+f1fc
+#define ICON_FA_PALETTE "\xef\x94\xbf" // U+f53f
+#define ICON_FA_PALLET "\xef\x92\x82" // U+f482
+#define ICON_FA_PANORAMA "\xee\x88\x89" // U+e209
+#define ICON_FA_PAPER_PLANE "\xef\x87\x98" // U+f1d8
+#define ICON_FA_PAPERCLIP "\xef\x83\x86" // U+f0c6
+#define ICON_FA_PARACHUTE_BOX "\xef\x93\x8d" // U+f4cd
+#define ICON_FA_PARAGRAPH "\xef\x87\x9d" // U+f1dd
+#define ICON_FA_PASSPORT "\xef\x96\xab" // U+f5ab
+#define ICON_FA_PASTE "\xef\x83\xaa" // U+f0ea
+#define ICON_FA_PAUSE "\xef\x81\x8c" // U+f04c
+#define ICON_FA_PAW "\xef\x86\xb0" // U+f1b0
+#define ICON_FA_PEACE "\xef\x99\xbc" // U+f67c
+#define ICON_FA_PEN "\xef\x8c\x84" // U+f304
+#define ICON_FA_PEN_CLIP "\xef\x8c\x85" // U+f305
+#define ICON_FA_PEN_FANCY "\xef\x96\xac" // U+f5ac
+#define ICON_FA_PEN_NIB "\xef\x96\xad" // U+f5ad
+#define ICON_FA_PEN_RULER "\xef\x96\xae" // U+f5ae
+#define ICON_FA_PEN_TO_SQUARE "\xef\x81\x84" // U+f044
+#define ICON_FA_PENCIL "\xef\x8c\x83" // U+f303
+#define ICON_FA_PEOPLE_ARROWS_LEFT_RIGHT "\xee\x81\xa8" // U+e068
+#define ICON_FA_PEOPLE_CARRY_BOX "\xef\x93\x8e" // U+f4ce
+#define ICON_FA_PEOPLE_GROUP "\xee\x94\xb3" // U+e533
+#define ICON_FA_PEOPLE_LINE "\xee\x94\xb4" // U+e534
+#define ICON_FA_PEOPLE_PULLING "\xee\x94\xb5" // U+e535
+#define ICON_FA_PEOPLE_ROBBERY "\xee\x94\xb6" // U+e536
+#define ICON_FA_PEOPLE_ROOF "\xee\x94\xb7" // U+e537
+#define ICON_FA_PEPPER_HOT "\xef\xa0\x96" // U+f816
+#define ICON_FA_PERCENT "%" // U+25
+#define ICON_FA_PERSON "\xef\x86\x83" // U+f183
+#define ICON_FA_PERSON_ARROW_DOWN_TO_LINE "\xee\x94\xb8" // U+e538
+#define ICON_FA_PERSON_ARROW_UP_FROM_LINE "\xee\x94\xb9" // U+e539
+#define ICON_FA_PERSON_BIKING "\xef\xa1\x8a" // U+f84a
+#define ICON_FA_PERSON_BOOTH "\xef\x9d\x96" // U+f756
+#define ICON_FA_PERSON_BREASTFEEDING "\xee\x94\xba" // U+e53a
+#define ICON_FA_PERSON_BURST "\xee\x94\xbb" // U+e53b
+#define ICON_FA_PERSON_CANE "\xee\x94\xbc" // U+e53c
+#define ICON_FA_PERSON_CHALKBOARD "\xee\x94\xbd" // U+e53d
+#define ICON_FA_PERSON_CIRCLE_CHECK "\xee\x94\xbe" // U+e53e
+#define ICON_FA_PERSON_CIRCLE_EXCLAMATION "\xee\x94\xbf" // U+e53f
+#define ICON_FA_PERSON_CIRCLE_MINUS "\xee\x95\x80" // U+e540
+#define ICON_FA_PERSON_CIRCLE_PLUS "\xee\x95\x81" // U+e541
+#define ICON_FA_PERSON_CIRCLE_QUESTION "\xee\x95\x82" // U+e542
+#define ICON_FA_PERSON_CIRCLE_XMARK "\xee\x95\x83" // U+e543
+#define ICON_FA_PERSON_DIGGING "\xef\xa1\x9e" // U+f85e
+#define ICON_FA_PERSON_DOTS_FROM_LINE "\xef\x91\xb0" // U+f470
+#define ICON_FA_PERSON_DRESS "\xef\x86\x82" // U+f182
+#define ICON_FA_PERSON_DRESS_BURST "\xee\x95\x84" // U+e544
+#define ICON_FA_PERSON_DROWNING "\xee\x95\x85" // U+e545
+#define ICON_FA_PERSON_FALLING "\xee\x95\x86" // U+e546
+#define ICON_FA_PERSON_FALLING_BURST "\xee\x95\x87" // U+e547
+#define ICON_FA_PERSON_HALF_DRESS "\xee\x95\x88" // U+e548
+#define ICON_FA_PERSON_HARASSING "\xee\x95\x89" // U+e549
+#define ICON_FA_PERSON_HIKING "\xef\x9b\xac" // U+f6ec
+#define ICON_FA_PERSON_MILITARY_POINTING "\xee\x95\x8a" // U+e54a
+#define ICON_FA_PERSON_MILITARY_RIFLE "\xee\x95\x8b" // U+e54b
+#define ICON_FA_PERSON_MILITARY_TO_PERSON "\xee\x95\x8c" // U+e54c
+#define ICON_FA_PERSON_PRAYING "\xef\x9a\x83" // U+f683
+#define ICON_FA_PERSON_PREGNANT "\xee\x8c\x9e" // U+e31e
+#define ICON_FA_PERSON_RAYS "\xee\x95\x8d" // U+e54d
+#define ICON_FA_PERSON_RIFLE "\xee\x95\x8e" // U+e54e
+#define ICON_FA_PERSON_RUNNING "\xef\x9c\x8c" // U+f70c
+#define ICON_FA_PERSON_SHELTER "\xee\x95\x8f" // U+e54f
+#define ICON_FA_PERSON_SKATING "\xef\x9f\x85" // U+f7c5
+#define ICON_FA_PERSON_SKIING "\xef\x9f\x89" // U+f7c9
+#define ICON_FA_PERSON_SKIING_NORDIC "\xef\x9f\x8a" // U+f7ca
+#define ICON_FA_PERSON_SNOWBOARDING "\xef\x9f\x8e" // U+f7ce
+#define ICON_FA_PERSON_SWIMMING "\xef\x97\x84" // U+f5c4
+#define ICON_FA_PERSON_THROUGH_WINDOW "\xee\x90\xb3" // U+e433
+#define ICON_FA_PERSON_WALKING "\xef\x95\x94" // U+f554
+#define ICON_FA_PERSON_WALKING_ARROW_LOOP_LEFT "\xee\x95\x91" // U+e551
+#define ICON_FA_PERSON_WALKING_ARROW_RIGHT "\xee\x95\x92" // U+e552
+#define ICON_FA_PERSON_WALKING_DASHED_LINE_ARROW_RIGHT "\xee\x95\x93" // U+e553
+#define ICON_FA_PERSON_WALKING_LUGGAGE "\xee\x95\x94" // U+e554
+#define ICON_FA_PERSON_WALKING_WITH_CANE "\xef\x8a\x9d" // U+f29d
+#define ICON_FA_PESETA_SIGN "\xee\x88\xa1" // U+e221
+#define ICON_FA_PESO_SIGN "\xee\x88\xa2" // U+e222
+#define ICON_FA_PHONE "\xef\x82\x95" // U+f095
+#define ICON_FA_PHONE_FLIP "\xef\xa1\xb9" // U+f879
+#define ICON_FA_PHONE_SLASH "\xef\x8f\x9d" // U+f3dd
+#define ICON_FA_PHONE_VOLUME "\xef\x8a\xa0" // U+f2a0
+#define ICON_FA_PHOTO_FILM "\xef\xa1\xbc" // U+f87c
+#define ICON_FA_PIGGY_BANK "\xef\x93\x93" // U+f4d3
+#define ICON_FA_PILLS "\xef\x92\x84" // U+f484
+#define ICON_FA_PIZZA_SLICE "\xef\xa0\x98" // U+f818
+#define ICON_FA_PLACE_OF_WORSHIP "\xef\x99\xbf" // U+f67f
+#define ICON_FA_PLANE "\xef\x81\xb2" // U+f072
+#define ICON_FA_PLANE_ARRIVAL "\xef\x96\xaf" // U+f5af
+#define ICON_FA_PLANE_CIRCLE_CHECK "\xee\x95\x95" // U+e555
+#define ICON_FA_PLANE_CIRCLE_EXCLAMATION "\xee\x95\x96" // U+e556
+#define ICON_FA_PLANE_CIRCLE_XMARK "\xee\x95\x97" // U+e557
+#define ICON_FA_PLANE_DEPARTURE "\xef\x96\xb0" // U+f5b0
+#define ICON_FA_PLANE_LOCK "\xee\x95\x98" // U+e558
+#define ICON_FA_PLANE_SLASH "\xee\x81\xa9" // U+e069
+#define ICON_FA_PLANE_UP "\xee\x88\xad" // U+e22d
+#define ICON_FA_PLANT_WILT "\xee\x90\xbb" // U+e43b
+#define ICON_FA_PLATE_WHEAT "\xee\x95\x9a" // U+e55a
+#define ICON_FA_PLAY "\xef\x81\x8b" // U+f04b
+#define ICON_FA_PLUG "\xef\x87\xa6" // U+f1e6
+#define ICON_FA_PLUG_CIRCLE_BOLT "\xee\x95\x9b" // U+e55b
+#define ICON_FA_PLUG_CIRCLE_CHECK "\xee\x95\x9c" // U+e55c
+#define ICON_FA_PLUG_CIRCLE_EXCLAMATION "\xee\x95\x9d" // U+e55d
+#define ICON_FA_PLUG_CIRCLE_MINUS "\xee\x95\x9e" // U+e55e
+#define ICON_FA_PLUG_CIRCLE_PLUS "\xee\x95\x9f" // U+e55f
+#define ICON_FA_PLUG_CIRCLE_XMARK "\xee\x95\xa0" // U+e560
+#define ICON_FA_PLUS "+" // U+2b
+#define ICON_FA_PLUS_MINUS "\xee\x90\xbc" // U+e43c
+#define ICON_FA_PODCAST "\xef\x8b\x8e" // U+f2ce
+#define ICON_FA_POO "\xef\x8b\xbe" // U+f2fe
+#define ICON_FA_POO_STORM "\xef\x9d\x9a" // U+f75a
+#define ICON_FA_POOP "\xef\x98\x99" // U+f619
+#define ICON_FA_POWER_OFF "\xef\x80\x91" // U+f011
+#define ICON_FA_PRESCRIPTION "\xef\x96\xb1" // U+f5b1
+#define ICON_FA_PRESCRIPTION_BOTTLE "\xef\x92\x85" // U+f485
+#define ICON_FA_PRESCRIPTION_BOTTLE_MEDICAL "\xef\x92\x86" // U+f486
+#define ICON_FA_PRINT "\xef\x80\xaf" // U+f02f
+#define ICON_FA_PUMP_MEDICAL "\xee\x81\xaa" // U+e06a
+#define ICON_FA_PUMP_SOAP "\xee\x81\xab" // U+e06b
+#define ICON_FA_PUZZLE_PIECE "\xef\x84\xae" // U+f12e
+#define ICON_FA_Q "Q" // U+51
+#define ICON_FA_QRCODE "\xef\x80\xa9" // U+f029
+#define ICON_FA_QUESTION "?" // U+3f
+#define ICON_FA_QUOTE_LEFT "\xef\x84\x8d" // U+f10d
+#define ICON_FA_QUOTE_RIGHT "\xef\x84\x8e" // U+f10e
+#define ICON_FA_R "R" // U+52
+#define ICON_FA_RADIATION "\xef\x9e\xb9" // U+f7b9
+#define ICON_FA_RADIO "\xef\xa3\x97" // U+f8d7
+#define ICON_FA_RAINBOW "\xef\x9d\x9b" // U+f75b
+#define ICON_FA_RANKING_STAR "\xee\x95\xa1" // U+e561
+#define ICON_FA_RECEIPT "\xef\x95\x83" // U+f543
+#define ICON_FA_RECORD_VINYL "\xef\xa3\x99" // U+f8d9
+#define ICON_FA_RECTANGLE_AD "\xef\x99\x81" // U+f641
+#define ICON_FA_RECTANGLE_LIST "\xef\x80\xa2" // U+f022
+#define ICON_FA_RECTANGLE_XMARK "\xef\x90\x90" // U+f410
+#define ICON_FA_RECYCLE "\xef\x86\xb8" // U+f1b8
+#define ICON_FA_REGISTERED "\xef\x89\x9d" // U+f25d
+#define ICON_FA_REPEAT "\xef\x8d\xa3" // U+f363
+#define ICON_FA_REPLY "\xef\x8f\xa5" // U+f3e5
+#define ICON_FA_REPLY_ALL "\xef\x84\xa2" // U+f122
+#define ICON_FA_REPUBLICAN "\xef\x9d\x9e" // U+f75e
+#define ICON_FA_RESTROOM "\xef\x9e\xbd" // U+f7bd
+#define ICON_FA_RETWEET "\xef\x81\xb9" // U+f079
+#define ICON_FA_RIBBON "\xef\x93\x96" // U+f4d6
+#define ICON_FA_RIGHT_FROM_BRACKET "\xef\x8b\xb5" // U+f2f5
+#define ICON_FA_RIGHT_LEFT "\xef\x8d\xa2" // U+f362
+#define ICON_FA_RIGHT_LONG "\xef\x8c\x8b" // U+f30b
+#define ICON_FA_RIGHT_TO_BRACKET "\xef\x8b\xb6" // U+f2f6
+#define ICON_FA_RING "\xef\x9c\x8b" // U+f70b
+#define ICON_FA_ROAD "\xef\x80\x98" // U+f018
+#define ICON_FA_ROAD_BARRIER "\xee\x95\xa2" // U+e562
+#define ICON_FA_ROAD_BRIDGE "\xee\x95\xa3" // U+e563
+#define ICON_FA_ROAD_CIRCLE_CHECK "\xee\x95\xa4" // U+e564
+#define ICON_FA_ROAD_CIRCLE_EXCLAMATION "\xee\x95\xa5" // U+e565
+#define ICON_FA_ROAD_CIRCLE_XMARK "\xee\x95\xa6" // U+e566
+#define ICON_FA_ROAD_LOCK "\xee\x95\xa7" // U+e567
+#define ICON_FA_ROAD_SPIKES "\xee\x95\xa8" // U+e568
+#define ICON_FA_ROBOT "\xef\x95\x84" // U+f544
+#define ICON_FA_ROCKET "\xef\x84\xb5" // U+f135
+#define ICON_FA_ROTATE "\xef\x8b\xb1" // U+f2f1
+#define ICON_FA_ROTATE_LEFT "\xef\x8b\xaa" // U+f2ea
+#define ICON_FA_ROTATE_RIGHT "\xef\x8b\xb9" // U+f2f9
+#define ICON_FA_ROUTE "\xef\x93\x97" // U+f4d7
+#define ICON_FA_RSS "\xef\x82\x9e" // U+f09e
+#define ICON_FA_RUBLE_SIGN "\xef\x85\x98" // U+f158
+#define ICON_FA_RUG "\xee\x95\xa9" // U+e569
+#define ICON_FA_RULER "\xef\x95\x85" // U+f545
+#define ICON_FA_RULER_COMBINED "\xef\x95\x86" // U+f546
+#define ICON_FA_RULER_HORIZONTAL "\xef\x95\x87" // U+f547
+#define ICON_FA_RULER_VERTICAL "\xef\x95\x88" // U+f548
+#define ICON_FA_RUPEE_SIGN "\xef\x85\x96" // U+f156
+#define ICON_FA_RUPIAH_SIGN "\xee\x88\xbd" // U+e23d
+#define ICON_FA_S "S" // U+53
+#define ICON_FA_SACK_DOLLAR "\xef\xa0\x9d" // U+f81d
+#define ICON_FA_SACK_XMARK "\xee\x95\xaa" // U+e56a
+#define ICON_FA_SAILBOAT "\xee\x91\x85" // U+e445
+#define ICON_FA_SATELLITE "\xef\x9e\xbf" // U+f7bf
+#define ICON_FA_SATELLITE_DISH "\xef\x9f\x80" // U+f7c0
+#define ICON_FA_SCALE_BALANCED "\xef\x89\x8e" // U+f24e
+#define ICON_FA_SCALE_UNBALANCED "\xef\x94\x95" // U+f515
+#define ICON_FA_SCALE_UNBALANCED_FLIP "\xef\x94\x96" // U+f516
+#define ICON_FA_SCHOOL "\xef\x95\x89" // U+f549
+#define ICON_FA_SCHOOL_CIRCLE_CHECK "\xee\x95\xab" // U+e56b
+#define ICON_FA_SCHOOL_CIRCLE_EXCLAMATION "\xee\x95\xac" // U+e56c
+#define ICON_FA_SCHOOL_CIRCLE_XMARK "\xee\x95\xad" // U+e56d
+#define ICON_FA_SCHOOL_FLAG "\xee\x95\xae" // U+e56e
+#define ICON_FA_SCHOOL_LOCK "\xee\x95\xaf" // U+e56f
+#define ICON_FA_SCISSORS "\xef\x83\x84" // U+f0c4
+#define ICON_FA_SCREWDRIVER "\xef\x95\x8a" // U+f54a
+#define ICON_FA_SCREWDRIVER_WRENCH "\xef\x9f\x99" // U+f7d9
+#define ICON_FA_SCROLL "\xef\x9c\x8e" // U+f70e
+#define ICON_FA_SCROLL_TORAH "\xef\x9a\xa0" // U+f6a0
+#define ICON_FA_SD_CARD "\xef\x9f\x82" // U+f7c2
+#define ICON_FA_SECTION "\xee\x91\x87" // U+e447
+#define ICON_FA_SEEDLING "\xef\x93\x98" // U+f4d8
+#define ICON_FA_SERVER "\xef\x88\xb3" // U+f233
+#define ICON_FA_SHAPES "\xef\x98\x9f" // U+f61f
+#define ICON_FA_SHARE "\xef\x81\xa4" // U+f064
+#define ICON_FA_SHARE_FROM_SQUARE "\xef\x85\x8d" // U+f14d
+#define ICON_FA_SHARE_NODES "\xef\x87\xa0" // U+f1e0
+#define ICON_FA_SHEET_PLASTIC "\xee\x95\xb1" // U+e571
+#define ICON_FA_SHEKEL_SIGN "\xef\x88\x8b" // U+f20b
+#define ICON_FA_SHIELD "\xef\x84\xb2" // U+f132
+#define ICON_FA_SHIELD_CAT "\xee\x95\xb2" // U+e572
+#define ICON_FA_SHIELD_DOG "\xee\x95\xb3" // U+e573
+#define ICON_FA_SHIELD_HALVED "\xef\x8f\xad" // U+f3ed
+#define ICON_FA_SHIELD_HEART "\xee\x95\xb4" // U+e574
+#define ICON_FA_SHIELD_VIRUS "\xee\x81\xac" // U+e06c
+#define ICON_FA_SHIP "\xef\x88\x9a" // U+f21a
+#define ICON_FA_SHIRT "\xef\x95\x93" // U+f553
+#define ICON_FA_SHOE_PRINTS "\xef\x95\x8b" // U+f54b
+#define ICON_FA_SHOP "\xef\x95\x8f" // U+f54f
+#define ICON_FA_SHOP_LOCK "\xee\x92\xa5" // U+e4a5
+#define ICON_FA_SHOP_SLASH "\xee\x81\xb0" // U+e070
+#define ICON_FA_SHOWER "\xef\x8b\x8c" // U+f2cc
+#define ICON_FA_SHRIMP "\xee\x91\x88" // U+e448
+#define ICON_FA_SHUFFLE "\xef\x81\xb4" // U+f074
+#define ICON_FA_SHUTTLE_SPACE "\xef\x86\x97" // U+f197
+#define ICON_FA_SIGN_HANGING "\xef\x93\x99" // U+f4d9
+#define ICON_FA_SIGNAL "\xef\x80\x92" // U+f012
+#define ICON_FA_SIGNATURE "\xef\x96\xb7" // U+f5b7
+#define ICON_FA_SIGNS_POST "\xef\x89\xb7" // U+f277
+#define ICON_FA_SIM_CARD "\xef\x9f\x84" // U+f7c4
+#define ICON_FA_SINK "\xee\x81\xad" // U+e06d
+#define ICON_FA_SITEMAP "\xef\x83\xa8" // U+f0e8
+#define ICON_FA_SKULL "\xef\x95\x8c" // U+f54c
+#define ICON_FA_SKULL_CROSSBONES "\xef\x9c\x94" // U+f714
+#define ICON_FA_SLASH "\xef\x9c\x95" // U+f715
+#define ICON_FA_SLEIGH "\xef\x9f\x8c" // U+f7cc
+#define ICON_FA_SLIDERS "\xef\x87\x9e" // U+f1de
+#define ICON_FA_SMOG "\xef\x9d\x9f" // U+f75f
+#define ICON_FA_SMOKING "\xef\x92\x8d" // U+f48d
+#define ICON_FA_SNOWFLAKE "\xef\x8b\x9c" // U+f2dc
+#define ICON_FA_SNOWMAN "\xef\x9f\x90" // U+f7d0
+#define ICON_FA_SNOWPLOW "\xef\x9f\x92" // U+f7d2
+#define ICON_FA_SOAP "\xee\x81\xae" // U+e06e
+#define ICON_FA_SOCKS "\xef\x9a\x96" // U+f696
+#define ICON_FA_SOLAR_PANEL "\xef\x96\xba" // U+f5ba
+#define ICON_FA_SORT "\xef\x83\x9c" // U+f0dc
+#define ICON_FA_SORT_DOWN "\xef\x83\x9d" // U+f0dd
+#define ICON_FA_SORT_UP "\xef\x83\x9e" // U+f0de
+#define ICON_FA_SPA "\xef\x96\xbb" // U+f5bb
+#define ICON_FA_SPAGHETTI_MONSTER_FLYING "\xef\x99\xbb" // U+f67b
+#define ICON_FA_SPELL_CHECK "\xef\xa2\x91" // U+f891
+#define ICON_FA_SPIDER "\xef\x9c\x97" // U+f717
+#define ICON_FA_SPINNER "\xef\x84\x90" // U+f110
+#define ICON_FA_SPLOTCH "\xef\x96\xbc" // U+f5bc
+#define ICON_FA_SPOON "\xef\x8b\xa5" // U+f2e5
+#define ICON_FA_SPRAY_CAN "\xef\x96\xbd" // U+f5bd
+#define ICON_FA_SPRAY_CAN_SPARKLES "\xef\x97\x90" // U+f5d0
+#define ICON_FA_SQUARE "\xef\x83\x88" // U+f0c8
+#define ICON_FA_SQUARE_ARROW_UP_RIGHT "\xef\x85\x8c" // U+f14c
+#define ICON_FA_SQUARE_CARET_DOWN "\xef\x85\x90" // U+f150
+#define ICON_FA_SQUARE_CARET_LEFT "\xef\x86\x91" // U+f191
+#define ICON_FA_SQUARE_CARET_RIGHT "\xef\x85\x92" // U+f152
+#define ICON_FA_SQUARE_CARET_UP "\xef\x85\x91" // U+f151
+#define ICON_FA_SQUARE_CHECK "\xef\x85\x8a" // U+f14a
+#define ICON_FA_SQUARE_ENVELOPE "\xef\x86\x99" // U+f199
+#define ICON_FA_SQUARE_FULL "\xef\x91\x9c" // U+f45c
+#define ICON_FA_SQUARE_H "\xef\x83\xbd" // U+f0fd
+#define ICON_FA_SQUARE_MINUS "\xef\x85\x86" // U+f146
+#define ICON_FA_SQUARE_NFI "\xee\x95\xb6" // U+e576
+#define ICON_FA_SQUARE_PARKING "\xef\x95\x80" // U+f540
+#define ICON_FA_SQUARE_PEN "\xef\x85\x8b" // U+f14b
+#define ICON_FA_SQUARE_PERSON_CONFINED "\xee\x95\xb7" // U+e577
+#define ICON_FA_SQUARE_PHONE "\xef\x82\x98" // U+f098
+#define ICON_FA_SQUARE_PHONE_FLIP "\xef\xa1\xbb" // U+f87b
+#define ICON_FA_SQUARE_PLUS "\xef\x83\xbe" // U+f0fe
+#define ICON_FA_SQUARE_POLL_HORIZONTAL "\xef\x9a\x82" // U+f682
+#define ICON_FA_SQUARE_POLL_VERTICAL "\xef\x9a\x81" // U+f681
+#define ICON_FA_SQUARE_ROOT_VARIABLE "\xef\x9a\x98" // U+f698
+#define ICON_FA_SQUARE_RSS "\xef\x85\x83" // U+f143
+#define ICON_FA_SQUARE_SHARE_NODES "\xef\x87\xa1" // U+f1e1
+#define ICON_FA_SQUARE_UP_RIGHT "\xef\x8d\xa0" // U+f360
+#define ICON_FA_SQUARE_VIRUS "\xee\x95\xb8" // U+e578
+#define ICON_FA_SQUARE_XMARK "\xef\x8b\x93" // U+f2d3
+#define ICON_FA_STAFF_AESCULAPIUS "\xee\x95\xb9" // U+e579
+#define ICON_FA_STAIRS "\xee\x8a\x89" // U+e289
+#define ICON_FA_STAMP "\xef\x96\xbf" // U+f5bf
+#define ICON_FA_STAR "\xef\x80\x85" // U+f005
+#define ICON_FA_STAR_AND_CRESCENT "\xef\x9a\x99" // U+f699
+#define ICON_FA_STAR_HALF "\xef\x82\x89" // U+f089
+#define ICON_FA_STAR_HALF_STROKE "\xef\x97\x80" // U+f5c0
+#define ICON_FA_STAR_OF_DAVID "\xef\x9a\x9a" // U+f69a
+#define ICON_FA_STAR_OF_LIFE "\xef\x98\xa1" // U+f621
+#define ICON_FA_STERLING_SIGN "\xef\x85\x94" // U+f154
+#define ICON_FA_STETHOSCOPE "\xef\x83\xb1" // U+f0f1
+#define ICON_FA_STOP "\xef\x81\x8d" // U+f04d
+#define ICON_FA_STOPWATCH "\xef\x8b\xb2" // U+f2f2
+#define ICON_FA_STOPWATCH_20 "\xee\x81\xaf" // U+e06f
+#define ICON_FA_STORE "\xef\x95\x8e" // U+f54e
+#define ICON_FA_STORE_SLASH "\xee\x81\xb1" // U+e071
+#define ICON_FA_STREET_VIEW "\xef\x88\x9d" // U+f21d
+#define ICON_FA_STRIKETHROUGH "\xef\x83\x8c" // U+f0cc
+#define ICON_FA_STROOPWAFEL "\xef\x95\x91" // U+f551
+#define ICON_FA_SUBSCRIPT "\xef\x84\xac" // U+f12c
+#define ICON_FA_SUITCASE "\xef\x83\xb2" // U+f0f2
+#define ICON_FA_SUITCASE_MEDICAL "\xef\x83\xba" // U+f0fa
+#define ICON_FA_SUITCASE_ROLLING "\xef\x97\x81" // U+f5c1
+#define ICON_FA_SUN "\xef\x86\x85" // U+f185
+#define ICON_FA_SUN_PLANT_WILT "\xee\x95\xba" // U+e57a
+#define ICON_FA_SUPERSCRIPT "\xef\x84\xab" // U+f12b
+#define ICON_FA_SWATCHBOOK "\xef\x97\x83" // U+f5c3
+#define ICON_FA_SYNAGOGUE "\xef\x9a\x9b" // U+f69b
+#define ICON_FA_SYRINGE "\xef\x92\x8e" // U+f48e
+#define ICON_FA_T "T" // U+54
+#define ICON_FA_TABLE "\xef\x83\x8e" // U+f0ce
+#define ICON_FA_TABLE_CELLS "\xef\x80\x8a" // U+f00a
+#define ICON_FA_TABLE_CELLS_LARGE "\xef\x80\x89" // U+f009
+#define ICON_FA_TABLE_COLUMNS "\xef\x83\x9b" // U+f0db
+#define ICON_FA_TABLE_LIST "\xef\x80\x8b" // U+f00b
+#define ICON_FA_TABLE_TENNIS_PADDLE_BALL "\xef\x91\x9d" // U+f45d
+#define ICON_FA_TABLET "\xef\x8f\xbb" // U+f3fb
+#define ICON_FA_TABLET_BUTTON "\xef\x84\x8a" // U+f10a
+#define ICON_FA_TABLET_SCREEN_BUTTON "\xef\x8f\xba" // U+f3fa
+#define ICON_FA_TABLETS "\xef\x92\x90" // U+f490
+#define ICON_FA_TACHOGRAPH_DIGITAL "\xef\x95\xa6" // U+f566
+#define ICON_FA_TAG "\xef\x80\xab" // U+f02b
+#define ICON_FA_TAGS "\xef\x80\xac" // U+f02c
+#define ICON_FA_TAPE "\xef\x93\x9b" // U+f4db
+#define ICON_FA_TARP "\xee\x95\xbb" // U+e57b
+#define ICON_FA_TARP_DROPLET "\xee\x95\xbc" // U+e57c
+#define ICON_FA_TAXI "\xef\x86\xba" // U+f1ba
+#define ICON_FA_TEETH "\xef\x98\xae" // U+f62e
+#define ICON_FA_TEETH_OPEN "\xef\x98\xaf" // U+f62f
+#define ICON_FA_TEMPERATURE_ARROW_DOWN "\xee\x80\xbf" // U+e03f
+#define ICON_FA_TEMPERATURE_ARROW_UP "\xee\x81\x80" // U+e040
+#define ICON_FA_TEMPERATURE_EMPTY "\xef\x8b\x8b" // U+f2cb
+#define ICON_FA_TEMPERATURE_FULL "\xef\x8b\x87" // U+f2c7
+#define ICON_FA_TEMPERATURE_HALF "\xef\x8b\x89" // U+f2c9
+#define ICON_FA_TEMPERATURE_HIGH "\xef\x9d\xa9" // U+f769
+#define ICON_FA_TEMPERATURE_LOW "\xef\x9d\xab" // U+f76b
+#define ICON_FA_TEMPERATURE_QUARTER "\xef\x8b\x8a" // U+f2ca
+#define ICON_FA_TEMPERATURE_THREE_QUARTERS "\xef\x8b\x88" // U+f2c8
+#define ICON_FA_TENGE_SIGN "\xef\x9f\x97" // U+f7d7
+#define ICON_FA_TENT "\xee\x95\xbd" // U+e57d
+#define ICON_FA_TENT_ARROW_DOWN_TO_LINE "\xee\x95\xbe" // U+e57e
+#define ICON_FA_TENT_ARROW_LEFT_RIGHT "\xee\x95\xbf" // U+e57f
+#define ICON_FA_TENT_ARROW_TURN_LEFT "\xee\x96\x80" // U+e580
+#define ICON_FA_TENT_ARROWS_DOWN "\xee\x96\x81" // U+e581
+#define ICON_FA_TENTS "\xee\x96\x82" // U+e582
+#define ICON_FA_TERMINAL "\xef\x84\xa0" // U+f120
+#define ICON_FA_TEXT_HEIGHT "\xef\x80\xb4" // U+f034
+#define ICON_FA_TEXT_SLASH "\xef\xa1\xbd" // U+f87d
+#define ICON_FA_TEXT_WIDTH "\xef\x80\xb5" // U+f035
+#define ICON_FA_THERMOMETER "\xef\x92\x91" // U+f491
+#define ICON_FA_THUMBS_DOWN "\xef\x85\xa5" // U+f165
+#define ICON_FA_THUMBS_UP "\xef\x85\xa4" // U+f164
+#define ICON_FA_THUMBTACK "\xef\x82\x8d" // U+f08d
+#define ICON_FA_TICKET "\xef\x85\x85" // U+f145
+#define ICON_FA_TICKET_SIMPLE "\xef\x8f\xbf" // U+f3ff
+#define ICON_FA_TIMELINE "\xee\x8a\x9c" // U+e29c
+#define ICON_FA_TOGGLE_OFF "\xef\x88\x84" // U+f204
+#define ICON_FA_TOGGLE_ON "\xef\x88\x85" // U+f205
+#define ICON_FA_TOILET "\xef\x9f\x98" // U+f7d8
+#define ICON_FA_TOILET_PAPER "\xef\x9c\x9e" // U+f71e
+#define ICON_FA_TOILET_PAPER_SLASH "\xee\x81\xb2" // U+e072
+#define ICON_FA_TOILET_PORTABLE "\xee\x96\x83" // U+e583
+#define ICON_FA_TOILETS_PORTABLE "\xee\x96\x84" // U+e584
+#define ICON_FA_TOOLBOX "\xef\x95\x92" // U+f552
+#define ICON_FA_TOOTH "\xef\x97\x89" // U+f5c9
+#define ICON_FA_TORII_GATE "\xef\x9a\xa1" // U+f6a1
+#define ICON_FA_TORNADO "\xef\x9d\xaf" // U+f76f
+#define ICON_FA_TOWER_BROADCAST "\xef\x94\x99" // U+f519
+#define ICON_FA_TOWER_CELL "\xee\x96\x85" // U+e585
+#define ICON_FA_TOWER_OBSERVATION "\xee\x96\x86" // U+e586
+#define ICON_FA_TRACTOR "\xef\x9c\xa2" // U+f722
+#define ICON_FA_TRADEMARK "\xef\x89\x9c" // U+f25c
+#define ICON_FA_TRAFFIC_LIGHT "\xef\x98\xb7" // U+f637
+#define ICON_FA_TRAILER "\xee\x81\x81" // U+e041
+#define ICON_FA_TRAIN "\xef\x88\xb8" // U+f238
+#define ICON_FA_TRAIN_SUBWAY "\xef\x88\xb9" // U+f239
+#define ICON_FA_TRAIN_TRAM "\xef\x9f\x9a" // U+f7da
+#define ICON_FA_TRANSGENDER "\xef\x88\xa5" // U+f225
+#define ICON_FA_TRASH "\xef\x87\xb8" // U+f1f8
+#define ICON_FA_TRASH_ARROW_UP "\xef\xa0\xa9" // U+f829
+#define ICON_FA_TRASH_CAN "\xef\x8b\xad" // U+f2ed
+#define ICON_FA_TRASH_CAN_ARROW_UP "\xef\xa0\xaa" // U+f82a
+#define ICON_FA_TREE "\xef\x86\xbb" // U+f1bb
+#define ICON_FA_TREE_CITY "\xee\x96\x87" // U+e587
+#define ICON_FA_TRIANGLE_EXCLAMATION "\xef\x81\xb1" // U+f071
+#define ICON_FA_TROPHY "\xef\x82\x91" // U+f091
+#define ICON_FA_TROWEL "\xee\x96\x89" // U+e589
+#define ICON_FA_TROWEL_BRICKS "\xee\x96\x8a" // U+e58a
+#define ICON_FA_TRUCK "\xef\x83\x91" // U+f0d1
+#define ICON_FA_TRUCK_ARROW_RIGHT "\xee\x96\x8b" // U+e58b
+#define ICON_FA_TRUCK_DROPLET "\xee\x96\x8c" // U+e58c
+#define ICON_FA_TRUCK_FAST "\xef\x92\x8b" // U+f48b
+#define ICON_FA_TRUCK_FIELD "\xee\x96\x8d" // U+e58d
+#define ICON_FA_TRUCK_FIELD_UN "\xee\x96\x8e" // U+e58e
+#define ICON_FA_TRUCK_FRONT "\xee\x8a\xb7" // U+e2b7
+#define ICON_FA_TRUCK_MEDICAL "\xef\x83\xb9" // U+f0f9
+#define ICON_FA_TRUCK_MONSTER "\xef\x98\xbb" // U+f63b
+#define ICON_FA_TRUCK_MOVING "\xef\x93\x9f" // U+f4df
+#define ICON_FA_TRUCK_PICKUP "\xef\x98\xbc" // U+f63c
+#define ICON_FA_TRUCK_PLANE "\xee\x96\x8f" // U+e58f
+#define ICON_FA_TRUCK_RAMP_BOX "\xef\x93\x9e" // U+f4de
+#define ICON_FA_TTY "\xef\x87\xa4" // U+f1e4
+#define ICON_FA_TURKISH_LIRA_SIGN "\xee\x8a\xbb" // U+e2bb
+#define ICON_FA_TURN_DOWN "\xef\x8e\xbe" // U+f3be
+#define ICON_FA_TURN_UP "\xef\x8e\xbf" // U+f3bf
+#define ICON_FA_TV "\xef\x89\xac" // U+f26c
+#define ICON_FA_U "U" // U+55
+#define ICON_FA_UMBRELLA "\xef\x83\xa9" // U+f0e9
+#define ICON_FA_UMBRELLA_BEACH "\xef\x97\x8a" // U+f5ca
+#define ICON_FA_UNDERLINE "\xef\x83\x8d" // U+f0cd
+#define ICON_FA_UNIVERSAL_ACCESS "\xef\x8a\x9a" // U+f29a
+#define ICON_FA_UNLOCK "\xef\x82\x9c" // U+f09c
+#define ICON_FA_UNLOCK_KEYHOLE "\xef\x84\xbe" // U+f13e
+#define ICON_FA_UP_DOWN "\xef\x8c\xb8" // U+f338
+#define ICON_FA_UP_DOWN_LEFT_RIGHT "\xef\x82\xb2" // U+f0b2
+#define ICON_FA_UP_LONG "\xef\x8c\x8c" // U+f30c
+#define ICON_FA_UP_RIGHT_AND_DOWN_LEFT_FROM_CENTER "\xef\x90\xa4" // U+f424
+#define ICON_FA_UP_RIGHT_FROM_SQUARE "\xef\x8d\x9d" // U+f35d
+#define ICON_FA_UPLOAD "\xef\x82\x93" // U+f093
+#define ICON_FA_USER "\xef\x80\x87" // U+f007
+#define ICON_FA_USER_ASTRONAUT "\xef\x93\xbb" // U+f4fb
+#define ICON_FA_USER_CHECK "\xef\x93\xbc" // U+f4fc
+#define ICON_FA_USER_CLOCK "\xef\x93\xbd" // U+f4fd
+#define ICON_FA_USER_DOCTOR "\xef\x83\xb0" // U+f0f0
+#define ICON_FA_USER_GEAR "\xef\x93\xbe" // U+f4fe
+#define ICON_FA_USER_GRADUATE "\xef\x94\x81" // U+f501
+#define ICON_FA_USER_GROUP "\xef\x94\x80" // U+f500
+#define ICON_FA_USER_INJURED "\xef\x9c\xa8" // U+f728
+#define ICON_FA_USER_LARGE "\xef\x90\x86" // U+f406
+#define ICON_FA_USER_LARGE_SLASH "\xef\x93\xba" // U+f4fa
+#define ICON_FA_USER_LOCK "\xef\x94\x82" // U+f502
+#define ICON_FA_USER_MINUS "\xef\x94\x83" // U+f503
+#define ICON_FA_USER_NINJA "\xef\x94\x84" // U+f504
+#define ICON_FA_USER_NURSE "\xef\xa0\xaf" // U+f82f
+#define ICON_FA_USER_PEN "\xef\x93\xbf" // U+f4ff
+#define ICON_FA_USER_PLUS "\xef\x88\xb4" // U+f234
+#define ICON_FA_USER_SECRET "\xef\x88\x9b" // U+f21b
+#define ICON_FA_USER_SHIELD "\xef\x94\x85" // U+f505
+#define ICON_FA_USER_SLASH "\xef\x94\x86" // U+f506
+#define ICON_FA_USER_TAG "\xef\x94\x87" // U+f507
+#define ICON_FA_USER_TIE "\xef\x94\x88" // U+f508
+#define ICON_FA_USER_XMARK "\xef\x88\xb5" // U+f235
+#define ICON_FA_USERS "\xef\x83\x80" // U+f0c0
+#define ICON_FA_USERS_BETWEEN_LINES "\xee\x96\x91" // U+e591
+#define ICON_FA_USERS_GEAR "\xef\x94\x89" // U+f509
+#define ICON_FA_USERS_LINE "\xee\x96\x92" // U+e592
+#define ICON_FA_USERS_RAYS "\xee\x96\x93" // U+e593
+#define ICON_FA_USERS_RECTANGLE "\xee\x96\x94" // U+e594
+#define ICON_FA_USERS_SLASH "\xee\x81\xb3" // U+e073
+#define ICON_FA_USERS_VIEWFINDER "\xee\x96\x95" // U+e595
+#define ICON_FA_UTENSILS "\xef\x8b\xa7" // U+f2e7
+#define ICON_FA_V "V" // U+56
+#define ICON_FA_VAN_SHUTTLE "\xef\x96\xb6" // U+f5b6
+#define ICON_FA_VAULT "\xee\x8b\x85" // U+e2c5
+#define ICON_FA_VECTOR_SQUARE "\xef\x97\x8b" // U+f5cb
+#define ICON_FA_VENUS "\xef\x88\xa1" // U+f221
+#define ICON_FA_VENUS_DOUBLE "\xef\x88\xa6" // U+f226
+#define ICON_FA_VENUS_MARS "\xef\x88\xa8" // U+f228
+#define ICON_FA_VEST "\xee\x82\x85" // U+e085
+#define ICON_FA_VEST_PATCHES "\xee\x82\x86" // U+e086
+#define ICON_FA_VIAL "\xef\x92\x92" // U+f492
+#define ICON_FA_VIAL_CIRCLE_CHECK "\xee\x96\x96" // U+e596
+#define ICON_FA_VIAL_VIRUS "\xee\x96\x97" // U+e597
+#define ICON_FA_VIALS "\xef\x92\x93" // U+f493
+#define ICON_FA_VIDEO "\xef\x80\xbd" // U+f03d
+#define ICON_FA_VIDEO_SLASH "\xef\x93\xa2" // U+f4e2
+#define ICON_FA_VIHARA "\xef\x9a\xa7" // U+f6a7
+#define ICON_FA_VIRUS "\xee\x81\xb4" // U+e074
+#define ICON_FA_VIRUS_COVID "\xee\x92\xa8" // U+e4a8
+#define ICON_FA_VIRUS_COVID_SLASH "\xee\x92\xa9" // U+e4a9
+#define ICON_FA_VIRUS_SLASH "\xee\x81\xb5" // U+e075
+#define ICON_FA_VIRUSES "\xee\x81\xb6" // U+e076
+#define ICON_FA_VOICEMAIL "\xef\xa2\x97" // U+f897
+#define ICON_FA_VOLCANO "\xef\x9d\xb0" // U+f770
+#define ICON_FA_VOLLEYBALL "\xef\x91\x9f" // U+f45f
+#define ICON_FA_VOLUME_HIGH "\xef\x80\xa8" // U+f028
+#define ICON_FA_VOLUME_LOW "\xef\x80\xa7" // U+f027
+#define ICON_FA_VOLUME_OFF "\xef\x80\xa6" // U+f026
+#define ICON_FA_VOLUME_XMARK "\xef\x9a\xa9" // U+f6a9
+#define ICON_FA_VR_CARDBOARD "\xef\x9c\xa9" // U+f729
+#define ICON_FA_W "W" // U+57
+#define ICON_FA_WALKIE_TALKIE "\xef\xa3\xaf" // U+f8ef
+#define ICON_FA_WALLET "\xef\x95\x95" // U+f555
+#define ICON_FA_WAND_MAGIC "\xef\x83\x90" // U+f0d0
+#define ICON_FA_WAND_MAGIC_SPARKLES "\xee\x8b\x8a" // U+e2ca
+#define ICON_FA_WAND_SPARKLES "\xef\x9c\xab" // U+f72b
+#define ICON_FA_WAREHOUSE "\xef\x92\x94" // U+f494
+#define ICON_FA_WATER "\xef\x9d\xb3" // U+f773
+#define ICON_FA_WATER_LADDER "\xef\x97\x85" // U+f5c5
+#define ICON_FA_WAVE_SQUARE "\xef\xa0\xbe" // U+f83e
+#define ICON_FA_WEIGHT_HANGING "\xef\x97\x8d" // U+f5cd
+#define ICON_FA_WEIGHT_SCALE "\xef\x92\x96" // U+f496
+#define ICON_FA_WHEAT_AWN "\xee\x8b\x8d" // U+e2cd
+#define ICON_FA_WHEAT_AWN_CIRCLE_EXCLAMATION "\xee\x96\x98" // U+e598
+#define ICON_FA_WHEELCHAIR "\xef\x86\x93" // U+f193
+#define ICON_FA_WHEELCHAIR_MOVE "\xee\x8b\x8e" // U+e2ce
+#define ICON_FA_WHISKEY_GLASS "\xef\x9e\xa0" // U+f7a0
+#define ICON_FA_WIFI "\xef\x87\xab" // U+f1eb
+#define ICON_FA_WIND "\xef\x9c\xae" // U+f72e
+#define ICON_FA_WINDOW_MAXIMIZE "\xef\x8b\x90" // U+f2d0
+#define ICON_FA_WINDOW_MINIMIZE "\xef\x8b\x91" // U+f2d1
+#define ICON_FA_WINDOW_RESTORE "\xef\x8b\x92" // U+f2d2
+#define ICON_FA_WINE_BOTTLE "\xef\x9c\xaf" // U+f72f
+#define ICON_FA_WINE_GLASS "\xef\x93\xa3" // U+f4e3
+#define ICON_FA_WINE_GLASS_EMPTY "\xef\x97\x8e" // U+f5ce
+#define ICON_FA_WON_SIGN "\xef\x85\x99" // U+f159
+#define ICON_FA_WORM "\xee\x96\x99" // U+e599
+#define ICON_FA_WRENCH "\xef\x82\xad" // U+f0ad
+#define ICON_FA_X "X" // U+58
+#define ICON_FA_X_RAY "\xef\x92\x97" // U+f497
+#define ICON_FA_XMARK "\xef\x80\x8d" // U+f00d
+#define ICON_FA_XMARKS_LINES "\xee\x96\x9a" // U+e59a
+#define ICON_FA_Y "Y" // U+59
+#define ICON_FA_YEN_SIGN "\xef\x85\x97" // U+f157
+#define ICON_FA_YIN_YANG "\xef\x9a\xad" // U+f6ad
+#define ICON_FA_Z "Z" // U+5a
diff --git a/ui/thirdparty/fpng/HEAD b/ui/thirdparty/fpng/HEAD
new file mode 100644
index 0000000000..5adb30a452
--- /dev/null
+++ b/ui/thirdparty/fpng/HEAD
@@ -0,0 +1 @@
+645d49cf6b2e82ce25b5b59f6a2e2df30e6f5fa6
diff --git a/ui/thirdparty/fpng/fpng.cpp b/ui/thirdparty/fpng/fpng.cpp
new file mode 100644
index 0000000000..0b34e1cfcb
--- /dev/null
+++ b/ui/thirdparty/fpng/fpng.cpp
@@ -0,0 +1,3222 @@
+// fpng.cpp 1.0.6 - Fast 24/32bpp .PNG image writer/reader. See unlicense at the end of this file.
+// PNG's generated by this code have been tested to load successfully with stb_image.h, lodepng.cpp, wuffs, libpng, and pngcheck.
+//
+// Uses code from the simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299
+// Some low-level Deflate/Huffman functions derived from the original 2011 Google Code version of miniz (public domain by R. Geldreich, Jr.): https://code.google.com/archive/p/miniz/
+// Low-level Huffman code size function: public domain, originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996.
+//
+// Optional config macros:
+// FPNG_NO_SSE - Set to 1 to completely disable SSE usage, even on x86/x64. By default, on x86/x64 it's enabled.
+// FPNG_DISABLE_DECODE_CRC32_CHECKS - Set to 1 to disable PNG chunk CRC-32 tests, for improved fuzzing. Defaults to 0.
+// FPNG_USE_UNALIGNED_LOADS - Set to 1 to indicate it's OK to read/write unaligned 32-bit/64-bit values. Defaults to 0, unless x86/x64.
+//
+// With gcc/clang on x86, compile with -msse4.1 -mpclmul -fno-strict-aliasing
+// Only tested with -fno-strict-aliasing (which the Linux kernel uses, and MSVC's default).
+//
+#include "fpng.h"
+#include
+#include
+
+#ifdef _MSC_VER
+ #pragma warning (disable:4127) // conditional expression is constant
+#endif
+
+// Set FPNG_NO_SSE to 1 to completely disable SSE usage.
+#ifndef FPNG_NO_SSE
+ #define FPNG_NO_SSE (0)
+#endif
+
+// Detect if we're compiling on x86/x64
+#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || defined(__i386) || defined(__i486__) || defined(__i486) || defined(i386) || defined(__ia64__) || defined(__x86_64__)
+ #define FPNG_X86_OR_X64_CPU (1)
+#else
+ #define FPNG_X86_OR_X64_CPU (0)
+#endif
+
+#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE
+ #ifdef _MSC_VER
+ #include
+ #endif
+ #include // SSE
+ #include // SSE2
+ #include // SSE4.1
+ #include // pclmul
+#endif
+
+#ifndef FPNG_NO_STDIO
+ #include
+#endif
+
+// Allow the disabling of the chunk data CRC32 checks, for fuzz testing of the decoder
+#ifndef FPNG_DISABLE_DECODE_CRC32_CHECKS
+ #define FPNG_DISABLE_DECODE_CRC32_CHECKS (0)
+#endif
+
+// Using unaligned loads and stores causes errors when using UBSan. Jam it off.
+#if defined(__has_feature)
+ #if __has_feature(undefined_behavior_sanitizer)
+ #undef FPNG_USE_UNALIGNED_LOADS
+ #define FPNG_USE_UNALIGNED_LOADS (0)
+ #endif
+#endif
+
+// Set to 0 if your platform doesn't support unaligned 32-bit/64-bit reads/writes.
+#ifndef FPNG_USE_UNALIGNED_LOADS
+ #if FPNG_X86_OR_X64_CPU
+ // On x86/x64 we default to enabled, for a noticeable perf gain.
+ #define FPNG_USE_UNALIGNED_LOADS (1)
+ #else
+ #define FPNG_USE_UNALIGNED_LOADS (0)
+ #endif
+#endif
+
+#if defined(_MSC_VER) || defined(__MINGW32__) || FPNG_X86_OR_X64_CPU
+ #ifndef __LITTLE_ENDIAN
+ #define __LITTLE_ENDIAN 1234
+ #endif
+ #ifndef __BIG_ENDIAN
+ #define __BIG_ENDIAN 4321
+ #endif
+
+ // Assume little endian on Windows/x86/x64.
+ #define __BYTE_ORDER __LITTLE_ENDIAN
+#elif defined(__APPLE__)
+ #define __BYTE_ORDER __BYTE_ORDER__
+ #define __LITTLE_ENDIAN __LITTLE_ENDIAN__
+ #define __BIG_ENDIAN __BIG_ENDIAN__
+#else
+ // for __BYTE_ORDER (__LITTLE_ENDIAN or __BIG_ENDIAN)
+ #include
+
+ #ifndef __LITTLE_ENDIAN
+ #define __LITTLE_ENDIAN 1234
+ #endif
+ #ifndef __BIG_ENDIAN
+ #define __BIG_ENDIAN 4321
+ #endif
+#endif
+
+#if !defined(__BYTE_ORDER)
+ #error __BYTE_ORDER undefined. Compile with -D__BYTE_ORDER=1234 for little endian or -D__BYTE_ORDER=4321 for big endian.
+#endif
+
+namespace fpng
+{
+ static const int FPNG_FALSE = 0;
+ static const uint8_t FPNG_FDEC_VERSION = 0;
+ static const uint32_t FPNG_MAX_SUPPORTED_DIM = 1 << 24;
+
+ template static inline S maximum(S a, S b) { return (a > b) ? a : b; }
+ template static inline S minimum(S a, S b) { return (a < b) ? a : b; }
+
+ static inline uint32_t simple_swap32(uint32_t x) { return (x >> 24) | ((x >> 8) & 0x0000FF00) | ((x << 8) & 0x00FF0000) | (x << 24); }
+ static inline uint64_t simple_swap64(uint64_t x) { return (((uint64_t)simple_swap32((uint32_t)x)) << 32U) | simple_swap32((uint32_t)(x >> 32U)); }
+
+ static inline uint32_t swap32(uint32_t x)
+ {
+#if defined(__GNUC__) || defined(__clang__)
+ return __builtin_bswap32(x);
+#else
+ return simple_swap32(x);
+#endif
+ }
+
+ static inline uint64_t swap64(uint64_t x)
+ {
+#if defined(__GNUC__) || defined(__clang__)
+ return __builtin_bswap64(x);
+#else
+ return simple_swap64(x);
+#endif
+ }
+
+#if FPNG_USE_UNALIGNED_LOADS
+ #if __BYTE_ORDER == __BIG_ENDIAN
+ #define READ_LE32(p) swap32(*reinterpret_cast(p))
+ #define WRITE_LE32(p, v) *reinterpret_cast(p) = swap32((uint32_t)(v))
+ #define WRITE_LE64(p, v) *reinterpret_cast(p) = swap64((uint64_t)(v))
+
+ #define READ_BE32(p) *reinterpret_cast(p)
+ #else
+ #define READ_LE32(p) (*reinterpret_cast(p))
+ #define WRITE_LE32(p, v) *reinterpret_cast(p) = (uint32_t)(v)
+ #define WRITE_LE64(p, v) *reinterpret_cast(p) = (uint64_t)(v)
+
+ #define READ_BE32(p) swap32(*reinterpret_cast(p))
+ #endif
+#else
+ // A good compiler should be able to optimize these routines - hopefully. They are crucial for performance.
+ static inline uint32_t READ_LE32(const void* p)
+ {
+ const uint8_t* pBytes = (const uint8_t*)p;
+ return ((uint32_t)pBytes[0]) | (((uint32_t)pBytes[1]) << 8U) | (((uint32_t)pBytes[2]) << 16U) | (((uint32_t)pBytes[3]) << 24U);
+ }
+
+ static inline uint32_t READ_BE32(const void* p)
+ {
+ const uint8_t* pBytes = (const uint8_t*)p;
+ return ((uint32_t)pBytes[3]) | (((uint32_t)pBytes[2]) << 8U) | (((uint32_t)pBytes[1]) << 16U) | (((uint32_t)pBytes[0]) << 24U);
+ }
+
+ static inline void WRITE_LE32(const void* p, uint32_t v)
+ {
+ uint8_t* pBytes = (uint8_t*)p;
+ pBytes[0] = (uint8_t)(v);
+ pBytes[1] = (uint8_t)(v >> 8);
+ pBytes[2] = (uint8_t)(v >> 16);
+ pBytes[3] = (uint8_t)(v >> 24);
+ }
+
+ static inline void WRITE_LE64(const void* p, uint64_t v)
+ {
+ uint8_t* pBytes = (uint8_t*)p;
+ pBytes[0] = (uint8_t)(v);
+ pBytes[1] = (uint8_t)(v >> 8);
+ pBytes[2] = (uint8_t)(v >> 16);
+ pBytes[3] = (uint8_t)(v >> 24);
+ pBytes[4] = (uint8_t)(v >> 32);
+ pBytes[5] = (uint8_t)(v >> 40);
+ pBytes[6] = (uint8_t)(v >> 48);
+ pBytes[7] = (uint8_t)(v >> 56);
+ }
+#endif
+
+ // Customized the very common case of reading a 24bpp pixel from memory
+ static inline uint32_t READ_RGB_PIXEL(const void* p)
+ {
+#if FPNG_USE_UNALIGNED_LOADS
+ return READ_LE32(p) & 0xFFFFFF;
+#else
+ const uint8_t* pBytes = (const uint8_t*)p;
+ return ((uint32_t)pBytes[0]) | (((uint32_t)pBytes[1]) << 8U) | (((uint32_t)pBytes[2]) << 16U);
+#endif
+ }
+
+ // See "Slicing by 4" CRC-32 algorithm here:
+ // https://create.stephan-brumme.com/crc32/
+
+ // Precomputed 4KB of CRC-32 tables
+ static const uint32_t g_crc32_4[4][256] = {
+ {00, 016701630226, 035603460454, 023102250672, 0733342031, 016032572217, 035130722465, 023631112643, 01666704062, 017167134244, 034065364436, 022764554610, 01155446053, 017654276275, 034756026407, 022057616621, 03555610144, 015254020362, 036356270510, 020457440736, 03266552175, 015567362353, 036465132521, 020364702707, 02333114126, 014432724300, 037530574572, 021231344754, 02400256117, 014301466331, 037203636543, 021502006765,
+ 07333420310, 011432210136, 032530040744, 024231670562, 07400762321, 011301152107, 032203302775, 024502532553, 06555324372, 010254514154, 033356744726, 025457174500, 06266066343, 010567656165, 033465406717, 025364236531, 04666230254, 012167400072, 031065650600, 027764060426, 04155172265, 012654742043, 031756512631, 027057322417, 05000534236, 013701304010, 030603154662, 026102764444, 05733676207, 013032046021, 030130216653, 026631426475,
+ 016667040620, 0166670406, 023064420274, 035765210052, 016154302611, 0655532437, 023757762245, 035056152063, 017001744642, 01700174464, 022602324216, 034103514030, 017732406673, 01033236455, 022131066227, 034630656001, 015332650764, 03433060542, 020531230330, 036230400116, 015401512755, 03300322573, 020202172301, 036503742127, 014554154706, 02255764520, 021357534352, 037456304174, 014267216737, 02566426511, 021464676363, 037365046145,
+ 011554460530, 07255250716, 024357000164, 032456630342, 011267722501, 07566112727, 024464342155, 032365572373, 010332364552, 06433554774, 025531704106, 033230134320, 010401026563, 06300616745, 025202446137, 033503276311, 012001270474, 04700440652, 027602610020, 031103020206, 012732132445, 04033702663, 027131552011, 031630362237, 013667574416, 05166344630, 026064114042, 030765724264, 013154636427, 05655006601, 026757256073, 030056466255,
+ 035556101440, 023257731666, 0355561014, 016454351232, 035265243471, 023564473657, 0466623025, 016367013203, 034330605422, 022431035604, 01533265076, 017232455250, 034403547413, 022302377635, 01200127047, 017501717261, 036003711504, 020702121722, 03600371150, 015101541376, 036730453535, 020031263713, 03133033161, 015632603347, 037665015566, 021164625740, 02066475132, 014767245314, 037156357557, 021657567771, 02755737103, 014054107325,
+ 032665521750, 024164311576, 07066141304, 011767771122, 032156663761, 024657053547, 07755203335, 011054433113, 033003225732, 025702415514, 06600645366, 010101075140, 033730167703, 025031757525, 06133507357, 010632337171, 031330331614, 027431501432, 04533751240, 012232161066, 031403073625, 027302643403, 04200413271, 012501223057, 030556435676, 026257205450, 05355055222, 013454665004, 030265777647, 026564147461, 05466317213, 013367527035,
+ 023331141260, 035430771046, 016532521634, 0233311412, 023402203251, 035303433077, 016201663605, 0500053423, 022557645202, 034256075024, 017354225656, 01455415470, 022264507233, 034565337015, 017467167667, 01366757441, 020664751324, 036165161102, 015067331770, 03766501556, 020157413315, 036656223133, 015754073741, 03055643567, 021002055346, 037703665160, 014601435712, 02100205534, 021731317377, 037030527151, 014132777723, 02633147505,
+ 024002561170, 032703351356, 011601101524, 07100731702, 024731623141, 032030013367, 011132243515, 07633473733, 025664265112, 033165455334, 010067605546, 06766035760, 025157127123, 033656717305, 010754547577, 06055377751, 027557371034, 031256541212, 012354711460, 04455121646, 027264033005, 031565603223, 012467453451, 04366263677, 026331475056, 030430245270, 013532015402, 05233625624, 026402737067, 030303107241, 013201357433, 05500567615,
+ }, { 00,03106630501,06215461202,05313251703,014433142404,017535772105,012626523606,011720313307,031066305010,032160535511,037273764212,034375154713,025455247414,026553477115,023640626616,020746016317,011260411121,012366221420,017075070323,014173640622,05653553525,06755363024,03446132727,0540702226,020206714131,023300124430,026013375333,025115545632,034635656535,037733066034,032420237737,031526407236,
+ 022541022242,021447612743,024754443040,027652273541,036172160646,035074750347,030367501444,033261331145,013527327252,010421517753,015732746050,016634176551,07114265656,04012455357,01301604454,02207034155,033721433363,030627203662,035534052161,036432662460,027312571767,024214341266,021107110565,022001720064,02747736373,01641106672,04552357171,07454567470,016374674777,015272044276,010161215575,013067425074,
+ 036036247405,035130477104,030223626607,033325016306,022405305001,021503535500,024610764203,027716154702,07050142415,04156772114,01245523617,02343313316,013463000011,010565630510,015676461213,016770251712,027256656524,024350066025,021043237726,022145407227,033665714120,030763124421,035470375322,036576545623,016230553534,015336363035,010025132736,013123702237,02603411130,01705221431,04416070332,07510640633,
+ 014577265647,017471455346,012762604445,011664034144,0144327243,03042517742,06351746041,05257176540,025511160657,026417750356,023704501455,020602331154,031122022253,032024612752,037337443051,034231273550,05717674766,06611044267,03502215564,0404425065,011324736362,012222106663,017131357160,014037567461,034771571776,037677341277,032564110574,031462720075,020342433372,023244203673,026157052170,025051662471,
+ 07340714113,04246124412,01155375311,02053545610,013773656517,010675066016,015566237715,016460407214,036326411103,035220221402,030133070301,033035640600,022715553507,021613363006,024500132705,027406702204,016120305032,015026535533,010335764230,013233154731,02513247436,01415477137,04706626634,07600016335,027146000022,024040630523,021353461220,022255251721,033575142426,030473772127,035760523624,036666313325,
+ 025601736351,026707106650,023414357153,020512567452,031232674755,032334044254,037027215557,034121425056,014667433341,017761203640,012472052143,011574662442,0254571745,03352341244,06041110547,05147720046,034461327270,037567517771,032674746072,031772176573,020052265674,023154455375,026247604476,025341034177,05407022260,06501612761,03612443062,0714273563,011034160664,012132750365,017221501466,014327331167,
+ 031376553516,032270363017,037163132714,034065702215,025745411112,026643221413,023550070310,020456640611,0310656506,03216066007,06105237704,05003407205,014723714102,017625124403,012536375300,011430545601,020116142437,023010772136,026303523635,025205313334,034525000033,037423630532,032730461231,031636251730,011170247427,012076477126,017365626625,014263016324,05543305023,06445535522,03756764221,0650154720,
+ 013637571754,010731341255,015422110556,016524720057,07204433350,04302203651,01011052152,02117662453,022651674744,021757044245,024444215546,027542425047,036262736340,035364106641,030077357142,033171567443,02457160675,01551750374,04642501477,07744331176,016064022271,015162612770,010271443073,013377273572,033431265665,030537455364,035624604467,036722034166,027002327261,024104517760,021217746063,022311176562,
+ }, { 00,0160465067,0341152156,0221537131,0702324334,0662741353,0443276262,0523613205,01604650670,01764235617,01545702726,01425367741,01106574544,01066111523,01247426412,01327043475,03411521560,03571144507,03750473436,03630016451,03313605654,03273260633,03052757702,03132332765,02215371310,02375714377,02154223246,02034646221,02517055024,02477430043,02656107172,02736562115,
+ 07023243340,07143626327,07362311216,07202774271,07721167074,07641502013,07460035122,07500450145,06627413530,06747076557,06566541466,06406124401,06125737604,06045352663,06264665752,06304200735,04432762620,04552307647,04773630776,04613255711,04330446514,04250023573,04071514442,04111171425,05236132050,05356557037,05177060106,05017405161,05534216364,05454673303,05675344232,05715721255,
+ 016046506700,016126163767,016307454656,016267031631,016744622434,016624247453,016405770562,016565315505,017642356170,017722733117,017503204026,017463661041,017140072244,017020417223,017201120312,017361545375,015457027260,015537442207,015716175336,015676510351,015355303154,015235766133,015014251002,015174634065,014253677410,014333212477,014112725546,014072340521,014551553724,014431136743,014610401672,014770064615,
+ 011065745440,011105320427,011324617516,011244272571,011767461774,011607004713,011426533622,011546156645,010661115230,010701570257,010520047366,010440422301,010163231104,010003654163,010222363052,010342706035,012474264120,012514601147,012735336076,012655753011,012376140214,012216525273,012037012342,012157477325,013270434750,013310051737,013131566606,013051103661,013572710464,013412375403,013633642532,013753227555,
+ 034115215600,034075670667,034254347756,034334722731,034617131534,034777554553,034556063462,034436406405,035711445070,035671020017,035450517126,035530172141,035013761344,035173304323,035352633212,035232256275,037504734360,037464351307,037645666236,037725203251,037206410054,037366075033,037147542102,037027127165,036300164510,036260501577,036041036446,036121453421,036402240624,036562625643,036743312772,036623777715,
+ 033136056540,033056433527,033277104416,033317561471,033634372674,033754717613,033575220722,033415645745,032732606330,032652263357,032473754266,032513331201,032030522004,032150147063,032371470152,032211015135,030527577020,030447112047,030666425176,030706040111,030225653314,030345236373,030164701242,030004364225,031323327650,031243742637,031062275706,031102610761,031421003564,031541466503,031760151432,031600534455,
+ 022153713100,022033376167,022212641056,022372224031,022651437234,022731052253,022510565362,022470100305,023757143770,023637526717,023416011626,023576474641,023055267444,023135602423,023314335512,023274750575,021542232460,021422657407,021603360536,021763705551,021240116754,021320573733,021101044602,021061421665,020346462210,020226007277,020007530346,020167155321,020444746124,020524323143,020705614072,020665271015,
+ 025170550240,025010135227,025231402316,025351067371,025672674174,025712211113,025533726022,025453343045,024774300430,024614765457,024435252566,024555637501,024076024704,024116441763,024337176652,024257513635,026561071720,026401414747,026620123676,026740546611,026263355414,026303730473,026122207542,026042662525,027365621150,027205244137,027024773006,027144316061,027467505264,027507160203,027726457332,027646032355,
+ }, { 00,027057063545,025202344213,02255327756,021730513527,06767570062,04532657734,023565634271,030555024357,017502047612,015757360144,032700303401,011265537670,036232554335,034067673463,013030610126,012006253637,035051230372,037204117424,010253174161,033736740310,014761723655,016534404103,031563467446,022553277560,05504214025,07751133773,020706150236,03263764047,024234707502,026061420254,01036443711,
+ 024014527476,03043544133,01216663665,026241600320,05724034151,022773057414,020526370342,07571313607,014541503721,033516560264,031743647532,016714624077,035271010206,012226073743,010073354015,037024337550,036012774241,011045717704,013210430052,034247453517,017722267766,030775204223,032520123575,015577140030,06547750116,021510733453,023745414305,04712477640,027277243431,0220220174,02075107622,025022164367,
+ 023305054075,04352037530,06107310266,021150373723,02435547552,025462524017,027637603741,0660660204,013650070322,034607013667,036452334131,011405357474,032160563605,015137500340,017362627416,030335644153,031303207642,016354264307,014101143451,033156120114,010433714365,037464777620,035631450176,012666433433,01656223515,026601240050,024454167706,03403104243,020166730032,07131753577,05364474221,022333417764,
+ 07311573403,020346510146,022113637610,05144654355,026421060124,01476003461,03623324337,024674347672,037644557754,010613534211,012446613547,035411670002,016174044273,031123027736,033376300060,014321363525,015317720234,032340743771,030115464027,017142407562,034427233713,013470250256,011625177500,036672114045,025642704163,02615767426,0440440370,027417423635,04172217444,023125274101,021370153657,06327130312,
+ 035526333073,012571350536,010724077260,037773014725,014216620554,033241643011,031014564747,016043507202,05073317324,022024374661,020271053137,07226030472,024743604603,03714667346,01541540410,026516523155,027520160644,0577103301,02722224457,025775247112,06210473363,021247410626,023012737170,04045754435,017075144513,030022127056,032277200700,015220263245,036745457034,011712434571,013547713227,034510770762,
+ 011532614405,036565677140,034730550616,013767533353,030202307122,017255364467,015000043331,032057020674,021067630752,06030653217,04265574541,023232517004,0757323275,027700340730,025555067066,02502004523,03534447232,024563424777,026736703021,01761760564,022204154715,05253137250,07006210506,020051273043,033061463165,014036400420,016263727376,031234744633,012751170442,035706113107,037553234651,010504257314,
+ 016623367006,031674304543,033421023215,014476040750,037113674521,010144617064,012311530732,035346553277,026376343351,01321320614,03174007142,024123064407,07446650676,020411633333,022644514465,05613577120,04625134631,023672157374,021427270422,06470213167,025115427316,02142444653,0317763105,027340700440,034370110566,013327173023,011172254775,036125237230,015440403041,032417460504,030642747252,017615724717,
+ 032637640470,015660623135,017435504663,030462567326,013107353157,034150330412,036305017344,011352074601,02362664727,025335607262,027160520534,0137543071,023452377200,04405314745,06650033013,021607050556,020631413247,07666470702,05433757054,022464734511,01101100760,026156163225,024303244573,03354227036,010364437110,037333454455,035166773303,012131710646,031454124437,016403147172,014656260624,033601203361,
+ } };
+
+ static uint32_t crc32_slice_by_4(const void* pData, size_t data_len, uint32_t cur_crc32 = 0)
+ {
+ uint32_t crc = ~cur_crc32;
+ const uint32_t* pData32 = static_cast(pData);
+
+ for (; data_len >= sizeof(uint32_t); ++pData32, data_len -= 4)
+ {
+ uint32_t v = READ_LE32(pData32) ^ crc;
+ crc = g_crc32_4[0][v >> 24] ^ g_crc32_4[1][(v >> 16) & 0xFF] ^ g_crc32_4[2][(v >> 8) & 0xFF] ^ g_crc32_4[3][v & 0xFF];
+ }
+
+ for (const uint8_t* pData8 = reinterpret_cast(pData32); data_len; --data_len)
+ crc = (crc >> 8) ^ g_crc32_4[0][(crc & 0xFF) ^ *pData8++];
+
+ return ~crc;
+ }
+
+#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE
+ // See Fast CRC Computation for Generic Polynomials Using PCLMULQDQ Instruction":
+ // https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf
+ // Requires PCLMUL and SSE 4.1. This function skips Step 1 (fold by 4) for simplicity/less code.
+ static uint32_t crc32_pclmul(const uint8_t* p, size_t size, uint32_t crc)
+ {
+ assert(size >= 16);
+
+ // See page 22 (bit reflected constants for gzip)
+#ifdef _MSC_VER
+ static const uint64_t __declspec(align(16))
+#else
+ static const uint64_t __attribute__((aligned(16)))
+#endif
+ s_u[2] = { 0x1DB710641, 0x1F7011641 }, s_k5k0[2] = { 0x163CD6124, 0 }, s_k3k4[2] = { 0x1751997D0, 0xCCAA009E };
+
+ // Load first 16 bytes, apply initial CRC32
+ __m128i b = _mm_xor_si128(_mm_cvtsi32_si128(~crc), _mm_loadu_si128(reinterpret_cast(p)));
+
+ // We're skipping directly to Step 2 page 12 - iteratively folding by 1 (by 4 is overkill for our needs)
+ const __m128i k3k4 = _mm_load_si128(reinterpret_cast(s_k3k4));
+
+ for (size -= 16, p += 16; size >= 16; size -= 16, p += 16)
+ b = _mm_xor_si128(_mm_xor_si128(_mm_clmulepi64_si128(b, k3k4, 17), _mm_loadu_si128(reinterpret_cast(p))), _mm_clmulepi64_si128(b, k3k4, 0));
+
+ // Final stages: fold to 64-bits, 32-bit Barrett reduction
+ const __m128i z = _mm_set_epi32(0, ~0, 0, ~0), u = _mm_load_si128(reinterpret_cast(s_u));
+ b = _mm_xor_si128(_mm_srli_si128(b, 8), _mm_clmulepi64_si128(b, k3k4, 16));
+ b = _mm_xor_si128(_mm_clmulepi64_si128(_mm_and_si128(b, z), _mm_loadl_epi64(reinterpret_cast(s_k5k0)), 0), _mm_srli_si128(b, 4));
+ return ~_mm_extract_epi32(_mm_xor_si128(b, _mm_clmulepi64_si128(_mm_and_si128(_mm_clmulepi64_si128(_mm_and_si128(b, z), u, 16), z), u, 0)), 1);
+ }
+
+ static uint32_t crc32_sse41_simd(const unsigned char* buf, size_t len, uint32_t prev_crc32)
+ {
+ if (len < 16)
+ return crc32_slice_by_4(buf, len, prev_crc32);
+
+ uint32_t simd_len = len & ~15;
+ uint32_t c = crc32_pclmul(buf, simd_len, prev_crc32);
+ return crc32_slice_by_4(buf + simd_len, len - simd_len, c);
+ }
+#endif
+
+#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE
+
+#ifndef _MSC_VER
+ static void do_cpuid(uint32_t eax, uint32_t ecx, uint32_t* regs)
+ {
+ uint32_t ebx = 0, edx = 0;
+
+#if defined(__PIC__) && defined(__i386__)
+ __asm__("movl %%ebx, %%edi;"
+ "cpuid;"
+ "xchgl %%ebx, %%edi;"
+ : "=D"(ebx), "+a"(eax), "+c"(ecx), "=d"(edx));
+#else
+ __asm__("cpuid;" : "+b"(ebx), "+a"(eax), "+c"(ecx), "=d"(edx));
+#endif
+
+ regs[0] = eax; regs[1] = ebx; regs[2] = ecx; regs[3] = edx;
+ }
+#endif
+
+ struct cpu_info
+ {
+ cpu_info() { memset(this, 0, sizeof(*this)); }
+
+ bool m_initialized, m_has_fpu, m_has_mmx, m_has_sse, m_has_sse2, m_has_sse3, m_has_ssse3, m_has_sse41, m_has_sse42, m_has_avx, m_has_avx2, m_has_pclmulqdq;
+
+ void init()
+ {
+ if (m_initialized)
+ return;
+
+ int regs[4];
+
+#ifdef _MSC_VER
+ __cpuid(regs, 0);
+#else
+ do_cpuid(0, 0, (uint32_t*)regs);
+#endif
+
+ const uint32_t max_eax = regs[0];
+ if (max_eax >= 1U)
+ {
+#ifdef _MSC_VER
+ __cpuid(regs, 1);
+#else
+ do_cpuid(1, 0, (uint32_t*)regs);
+#endif
+ extract_x86_flags(regs[2], regs[3]);
+ }
+
+ if (max_eax >= 7U)
+ {
+#ifdef _MSC_VER
+ __cpuidex(regs, 7, 0);
+#else
+ do_cpuid(7, 0, (uint32_t*)regs);
+#endif
+ extract_x86_extended_flags(regs[1]);
+ }
+
+ m_initialized = true;
+ }
+
+ bool can_use_sse41() const { return m_has_sse && m_has_sse2 && m_has_sse3 && m_has_ssse3 && m_has_sse41; }
+ bool can_use_pclmul() const { return m_has_pclmulqdq && can_use_sse41(); }
+
+ private:
+ void extract_x86_flags(uint32_t ecx, uint32_t edx)
+ {
+ m_has_fpu = (edx & (1 << 0)) != 0; m_has_mmx = (edx & (1 << 23)) != 0; m_has_sse = (edx & (1 << 25)) != 0; m_has_sse2 = (edx & (1 << 26)) != 0;
+ m_has_sse3 = (ecx & (1 << 0)) != 0; m_has_ssse3 = (ecx & (1 << 9)) != 0; m_has_sse41 = (ecx & (1 << 19)) != 0; m_has_sse42 = (ecx & (1 << 20)) != 0;
+ m_has_pclmulqdq = (ecx & (1 << 1)) != 0; m_has_avx = (ecx & (1 << 28)) != 0;
+ }
+
+ void extract_x86_extended_flags(uint32_t ebx) { m_has_avx2 = (ebx & (1 << 5)) != 0; }
+ };
+
+ cpu_info g_cpu_info;
+
+ void fpng_init()
+ {
+ g_cpu_info.init();
+ }
+#else
+ void fpng_init()
+ {
+ }
+#endif
+
+ bool fpng_cpu_supports_sse41()
+ {
+#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE
+ assert(g_cpu_info.m_initialized);
+ return g_cpu_info.can_use_sse41();
+#else
+ return false;
+#endif
+ }
+
+ uint32_t fpng_crc32(const void* pData, size_t size, uint32_t prev_crc32)
+ {
+#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE
+ if (g_cpu_info.can_use_pclmul())
+ return crc32_sse41_simd(static_cast(pData), size, prev_crc32);
+#endif
+
+ return crc32_slice_by_4(pData, size, prev_crc32);
+ }
+
+#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE
+ // See "Fast Computation of Adler32 Checksums":
+ // https://www.intel.com/content/www/us/en/developer/articles/technical/fast-computation-of-adler32-checksums.html
+ // SSE 4.1, 16 bytes per iteration
+ static uint32_t adler32_sse_16(const uint8_t* p, size_t len, uint32_t initial)
+ {
+ uint32_t s1 = initial & 0xFFFF, s2 = initial >> 16;
+ const uint32_t K = 65521;
+
+ while (len >= 16)
+ {
+ __m128i a = _mm_setr_epi32(s1, 0, 0, 0), b = _mm_setzero_si128(), c = _mm_setzero_si128(), d = _mm_setzero_si128(),
+ e = _mm_setzero_si128(), f = _mm_setzero_si128(), g = _mm_setzero_si128(), h = _mm_setzero_si128();
+
+ const size_t n = minimum(len >> 4, 5552);
+
+ for (size_t i = 0; i < n; i++)
+ {
+ const __m128i v = _mm_loadu_si128((const __m128i*)(p + i * 16));
+ a = _mm_add_epi32(a, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(0, 0, 0, 0)))); b = _mm_add_epi32(b, a);
+ c = _mm_add_epi32(c, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(1, 1, 1, 1)))); d = _mm_add_epi32(d, c);
+ e = _mm_add_epi32(e, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(2, 2, 2, 2)))); f = _mm_add_epi32(f, e);
+ g = _mm_add_epi32(g, _mm_cvtepu8_epi32(_mm_shuffle_epi32(v, _MM_SHUFFLE(3, 3, 3, 3)))); h = _mm_add_epi32(h, g);
+ }
+
+ uint32_t sa[16], sb[16];
+ _mm_storeu_si128((__m128i*)sa, a); _mm_storeu_si128((__m128i*)(sa + 4), c);
+ _mm_storeu_si128((__m128i*)sb, b); _mm_storeu_si128((__m128i*)(sb + 4), d);
+ _mm_storeu_si128((__m128i*)(sa + 8), e); _mm_storeu_si128((__m128i*)(sa + 12), g);
+ _mm_storeu_si128((__m128i*)(sb + 8), f); _mm_storeu_si128((__m128i*)(sb + 12), h);
+
+ // This could be vectorized, but it's only executed every 5552*16 iterations.
+ uint64_t vs1 = 0;
+ for (uint32_t i = 0; i < 16; i++)
+ vs1 += sa[i];
+
+ uint64_t vs2_a = 0;
+ for (uint32_t i = 0; i < 16; i++)
+ vs2_a += sa[i] * (uint64_t)i;
+ uint64_t vs2_b = 0;
+ for (uint32_t i = 0; i < 16; i++)
+ vs2_b += sb[i];
+ vs2_b *= 16U;
+ uint64_t vs2 = vs2_b - vs2_a + s2;
+
+ s1 = (uint32_t)(vs1 % K);
+ s2 = (uint32_t)(vs2 % K);
+
+ p += n * 16;
+ len -= n * 16;
+ }
+
+ for (; len; len--)
+ {
+ s1 += *p++;
+ s2 += s1;
+ }
+
+ return (s1 % K) | ((s2 % K) << 16);
+ }
+#endif
+
+ static uint32_t fpng_adler32_scalar(const uint8_t* ptr, size_t buf_len, uint32_t adler)
+ {
+ uint32_t i, s1 = (uint32_t)(adler & 0xffff), s2 = (uint32_t)(adler >> 16); uint32_t block_len = (uint32_t)(buf_len % 5552);
+ if (!ptr) return FPNG_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;
+ }
+
+ uint32_t fpng_adler32(const void* pData, size_t size, uint32_t adler)
+ {
+#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE
+ if (g_cpu_info.can_use_sse41())
+ return adler32_sse_16((const uint8_t*)pData, size, adler);
+#endif
+ return fpng_adler32_scalar((const uint8_t*)pData, size, adler);
+ }
+
+ // Ensure we've been configured for endianness correctly.
+ static inline bool endian_check()
+ {
+ uint32_t endian_check = 0;
+ WRITE_LE32(&endian_check, 0x1234ABCD);
+ const uint32_t first_byte = reinterpret_cast(&endian_check)[0];
+ return first_byte == 0xCD;
+ }
+
+ static const uint16_t g_defl_len_sym[256] = {
+ 257,258,259,260,261,262,263,264,265,265,266,266,267,267,268,268,269,269,269,269,270,270,270,270,271,271,271,271,272,272,272,272,
+ 273,273,273,273,273,273,273,273,274,274,274,274,274,274,274,274,275,275,275,275,275,275,275,275,276,276,276,276,276,276,276,276,
+ 277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,277,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,278,
+ 279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,279,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,280,
+ 281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,281,
+ 282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,282,
+ 283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,283,
+ 284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,284,285 };
+
+ static const uint8_t g_defl_len_extra[256] = {
+ 0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,
+ 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,
+ 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,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,
+ 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,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,0 };
+
+ static const uint8_t g_defl_small_dist_sym[512] = {
+ 0,1,2,3,4,4,5,5,6,6,6,6,7,7,7,7,8,8,8,8,8,8,8,8,9,9,9,9,9,9,9,9,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,10,11,11,11,11,11,11,
+ 11,11,11,11,11,11,11,11,11,11,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,12,13,
+ 13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,13,14,14,14,14,14,14,14,14,14,14,14,14,
+ 14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,14,
+ 14,14,14,14,14,14,14,14,14,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,
+ 15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,15,16,16,16,16,16,16,16,16,16,16,16,16,16,
+ 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+ 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,
+ 16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,16,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+ 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+ 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,
+ 17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17,17 };
+
+ static const uint32_t g_bitmasks[17] = { 0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF };
+
+ // Huffman tables generated by fpng_test -t @filelist.txt. Total alpha files : 1440, Total opaque files : 5627.
+ // Feel free to retrain the encoder on your opaque/alpha PNG files by setting FPNG_TRAIN_HUFFMAN_TABLES and running fpng_test with the -t option.
+ static const uint8_t g_dyn_huff_3[] = {
+ 120, 1, 237, 195, 3, 176, 110, 89, 122, 128, 225, 247, 251, 214, 218, 248, 113, 124, 173, 190, 109, 12, 50, 201, 196, 182, 109, 219, 182, 109, 219, 182,
+ 109, 219, 201, 36, 147, 153, 105, 235, 246, 53, 142, 207, 143, 141, 181, 214, 151, 93, 117, 170, 78, 117, 117, 58, 206, 77, 210, 217, 169, 122 };
+ const uint32_t DYN_HUFF_3_BITBUF = 30, DYN_HUFF_3_BITBUF_SIZE = 7;
+ static const struct { uint8_t m_code_size; uint16_t m_code; } g_dyn_huff_3_codes[288] = {
+ {2,0},{4,2},{4,10},{5,14},{5,30},{6,25},{6,57},{6,5},{6,37},{7,3},{7,67},{7,35},{7,99},{8,11},{8,139},{8,75},{8,203},{8,43},{8,171},{8,107},{9,135},{9,391},{9,71},{9,327},{9,199},{9,455},{9,39},{9,295},{9,167},{9,423},{9,103},{10,183},
+ {9,359},{10,695},{10,439},{10,951},{10,119},{10,631},{10,375},{10,887},{10,247},{10,759},{10,503},{11,975},{11,1999},{11,47},{11,1071},{12,1199},{11,559},{12,3247},{12,687},{11,1583},{12,2735},{12,1711},{12,3759},{12,431},{12,2479},{12,1455},{12,3503},{12,943},{12,2991},{12,1967},{12,4015},{12,111},
+ {12,2159},{12,1135},{12,3183},{12,623},{12,2671},{12,1647},{12,3695},{12,367},{12,2415},{12,1391},{12,3439},{12,879},{12,2927},{12,1903},{12,3951},{12,239},{12,2287},{12,1263},{12,3311},{12,751},{12,2799},{12,1775},{12,3823},{12,495},{12,2543},{12,1519},{12,3567},{12,1007},{12,3055},{12,2031},{12,4079},{12,31},
+ {12,2079},{12,1055},{12,3103},{12,543},{12,2591},{12,1567},{12,3615},{12,287},{12,2335},{12,1311},{12,3359},{12,799},{12,2847},{12,1823},{12,3871},{12,159},{12,2207},{12,1183},{12,3231},{12,671},{12,2719},{12,1695},{12,3743},{12,415},{12,2463},{12,1439},{12,3487},{12,927},{12,2975},{12,1951},{12,3999},{12,95},
+ {12,2143},{12,1119},{12,3167},{12,607},{12,2655},{12,1631},{12,3679},{12,351},{12,2399},{12,1375},{12,3423},{12,863},{12,2911},{12,1887},{12,3935},{12,223},{12,2271},{12,1247},{12,3295},{12,735},{12,2783},{12,1759},{12,3807},{12,479},{12,2527},{12,1503},{12,3551},{12,991},{12,3039},{12,2015},{12,4063},{12,63},
+ {12,2111},{12,1087},{12,3135},{12,575},{12,2623},{12,1599},{12,3647},{12,319},{12,2367},{12,1343},{12,3391},{12,831},{12,2879},{12,1855},{12,3903},{12,191},{12,2239},{12,1215},{12,3263},{12,703},{12,2751},{12,1727},{12,3775},{12,447},{12,2495},{12,1471},{12,3519},{12,959},{12,3007},{12,1983},{12,4031},{12,127},
+ {12,2175},{12,1151},{12,3199},{12,639},{12,2687},{12,1663},{12,3711},{12,383},{12,2431},{12,1407},{12,3455},{12,895},{12,2943},{11,303},{12,1919},{12,3967},{11,1327},{12,255},{11,815},{11,1839},{11,175},{10,1015},{10,15},{10,527},{10,271},{10,783},{10,143},{10,655},{10,399},{10,911},{10,79},{10,591},
+ {9,231},{10,335},{9,487},{9,23},{9,279},{9,151},{9,407},{9,87},{9,343},{9,215},{9,471},{9,55},{8,235},{8,27},{8,155},{8,91},{8,219},{8,59},{8,187},{8,123},{7,19},{7,83},{7,51},{7,115},{6,21},{6,53},{6,13},{6,45},{5,1},{5,17},{5,9},{4,6},
+ {12,2303},{6,29},{0,0},{0,0},{8,251},{0,0},{0,0},{8,7},{0,0},{10,847},{0,0},{10,207},{12,1279},{10,719},{12,3327},{12,767},{12,2815},{12,1791},{12,3839},{12,511},{12,2559},{12,1535},{9,311},{12,3583},{12,1023},{12,3071},{10,463},{12,2047},{6,61},{12,4095},{0,0},{0,0}
+ };
+
+ static const uint8_t g_dyn_huff_4[] = {
+ 120, 1, 229, 196, 99, 180, 37, 103, 218, 128, 225, 251, 121, 171, 106, 243, 216, 231, 180, 109, 196, 182, 51, 51, 73, 6, 201, 216, 182, 109, 219, 182,
+ 17, 140, 98, 219, 102, 219, 60, 125, 172, 205, 170, 122, 159, 111, 213, 143, 179, 214, 94, 189, 58, 153, 104, 166, 103, 190, 247, 199, 117 };
+ const uint32_t DYN_HUFF_4_BITBUF = 1, DYN_HUFF_4_BITBUF_SIZE = 2;
+ static const struct { uint8_t m_code_size; uint16_t m_code; } g_dyn_huff_4_codes[288] = {
+ {2,0},{4,2},{5,6},{6,30},{6,62},{6,1},{7,41},{7,105},{7,25},{7,89},{7,57},{7,121},{8,117},{8,245},{8,13},{8,141},{8,77},{8,205},{8,45},{8,173},{8,109},{8,237},{8,29},{8,157},{8,93},{8,221},{8,61},{9,83},{9,339},{9,211},{9,467},{9,51},
+ {9,307},{9,179},{9,435},{9,115},{9,371},{9,243},{9,499},{9,11},{9,267},{9,139},{9,395},{9,75},{9,331},{9,203},{9,459},{9,43},{9,299},{10,7},{10,519},{10,263},{10,775},{10,135},{10,647},{10,391},{10,903},{10,71},{10,583},{10,327},{10,839},{10,199},{10,711},{10,455},
+ {10,967},{10,39},{10,551},{10,295},{10,807},{10,167},{10,679},{10,423},{10,935},{10,103},{10,615},{11,463},{11,1487},{11,975},{10,359},{10,871},{10,231},{11,1999},{11,47},{11,1071},{11,559},{10,743},{10,487},{11,1583},{11,303},{11,1327},{11,815},{11,1839},{11,175},{11,1199},{11,687},{11,1711},
+ {11,431},{11,1455},{11,943},{11,1967},{11,111},{11,1135},{11,623},{11,1647},{11,367},{11,1391},{11,879},{11,1903},{11,239},{11,1263},{11,751},{11,1775},{11,495},{11,1519},{11,1007},{11,2031},{11,31},{11,1055},{11,543},{11,1567},{11,287},{11,1311},{11,799},{11,1823},{11,159},{11,1183},{11,671},{11,1695},
+ {11,415},{11,1439},{11,927},{11,1951},{11,95},{11,1119},{11,607},{11,1631},{11,351},{11,1375},{11,863},{11,1887},{11,223},{11,1247},{11,735},{11,1759},{11,479},{11,1503},{11,991},{11,2015},{11,63},{11,1087},{11,575},{11,1599},{11,319},{11,1343},{11,831},{11,1855},{11,191},{11,1215},{11,703},{11,1727},
+ {11,447},{11,1471},{11,959},{11,1983},{11,127},{11,1151},{11,639},{11,1663},{11,383},{10,999},{10,23},{10,535},{10,279},{11,1407},{11,895},{11,1919},{11,255},{11,1279},{10,791},{10,151},{10,663},{10,407},{10,919},{10,87},{10,599},{10,343},{10,855},{10,215},{10,727},{10,471},{10,983},{10,55},
+ {10,567},{10,311},{10,823},{10,183},{10,695},{10,439},{10,951},{10,119},{10,631},{10,375},{10,887},{10,247},{10,759},{10,503},{10,1015},{10,15},{10,527},{10,271},{10,783},{10,143},{10,655},{10,399},{9,171},{9,427},{9,107},{9,363},{9,235},{9,491},{9,27},{9,283},{9,155},{9,411},
+ {9,91},{9,347},{9,219},{9,475},{9,59},{9,315},{9,187},{9,443},{8,189},{9,123},{8,125},{8,253},{8,3},{8,131},{8,67},{8,195},{8,35},{8,163},{8,99},{8,227},{8,19},{7,5},{7,69},{7,37},{7,101},{7,21},{7,85},{6,33},{6,17},{6,49},{5,22},{4,10},
+ {12,2047},{0,0},{6,9},{0,0},{0,0},{0,0},{8,147},{0,0},{0,0},{7,53},{0,0},{9,379},{0,0},{9,251},{10,911},{10,79},{11,767},{10,591},{10,335},{10,847},{10,207},{10,719},{11,1791},{11,511},{9,507},{11,1535},{11,1023},{12,4095},{5,14},{0,0},{0,0},{0,0}
+ };
+
+#define PUT_BITS(bb, ll) do { uint32_t b = bb, l = ll; assert((l) >= 0 && (l) <= 16); assert((b) < (1ULL << (l))); bit_buf |= (((uint64_t)(b)) << bit_buf_size); bit_buf_size += (l); assert(bit_buf_size <= 64); } while(0)
+#define PUT_BITS_CZ(bb, ll) do { uint32_t b = bb, l = ll; assert((l) >= 1 && (l) <= 16); assert((b) < (1ULL << (l))); bit_buf |= (((uint64_t)(b)) << bit_buf_size); bit_buf_size += (l); assert(bit_buf_size <= 64); } while(0)
+
+#define PUT_BITS_FLUSH do { \
+ if ((dst_ofs + 8) > dst_buf_size) \
+ return 0; \
+ WRITE_LE64(pDst + dst_ofs, bit_buf); \
+ uint32_t bits_to_shift = bit_buf_size & ~7; \
+ dst_ofs += (bits_to_shift >> 3); \
+ assert(bits_to_shift < 64); \
+ bit_buf = bit_buf >> bits_to_shift; \
+ bit_buf_size -= bits_to_shift; \
+} while(0)
+
+#define PUT_BITS_FORCE_FLUSH do { \
+ while (bit_buf_size > 0) \
+ { \
+ if ((dst_ofs + 1) > dst_buf_size) \
+ return 0; \
+ *(uint8_t*)(pDst + dst_ofs) = (uint8_t)bit_buf; \
+ dst_ofs++; \
+ bit_buf >>= 8; \
+ bit_buf_size -= 8; \
+ } \
+} while(0)
+
+ enum
+ {
+ DEFL_MAX_HUFF_TABLES = 3,
+ DEFL_MAX_HUFF_SYMBOLS = 288,
+ DEFL_MAX_HUFF_SYMBOLS_0 = 288,
+ DEFL_MAX_HUFF_SYMBOLS_1 = 32,
+ DEFL_MAX_HUFF_SYMBOLS_2 = 19,
+ DEFL_LZ_DICT_SIZE = 32768,
+ DEFL_LZ_DICT_SIZE_MASK = DEFL_LZ_DICT_SIZE - 1,
+ DEFL_MIN_MATCH_LEN = 3,
+ DEFL_MAX_MATCH_LEN = 258
+ };
+
+#if FPNG_TRAIN_HUFFMAN_TABLES
+ uint64_t g_huff_counts[HUFF_COUNTS_SIZE];
+#endif
+
+ struct defl_huff
+ {
+ uint16_t m_huff_count[DEFL_MAX_HUFF_TABLES][DEFL_MAX_HUFF_SYMBOLS];
+ uint16_t m_huff_codes[DEFL_MAX_HUFF_TABLES][DEFL_MAX_HUFF_SYMBOLS];
+ uint8_t m_huff_code_sizes[DEFL_MAX_HUFF_TABLES][DEFL_MAX_HUFF_SYMBOLS];
+ };
+
+ struct defl_sym_freq
+ {
+ uint16_t m_key;
+ uint16_t m_sym_index;
+ };
+
+#define DEFL_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj))
+
+ static defl_sym_freq* defl_radix_sort_syms(uint32_t num_syms, defl_sym_freq* pSyms0, defl_sym_freq* pSyms1)
+ {
+ uint32_t total_passes = 2, pass_shift, pass, i, hist[256 * 2]; defl_sym_freq* pCur_syms = pSyms0, * pNew_syms = pSyms1; DEFL_CLEAR_OBJ(hist);
+ for (i = 0; i < num_syms; i++) { uint32_t freq = pSyms0[i].m_key; hist[freq & 0xFF]++; hist[256 + ((freq >> 8) & 0xFF)]++; }
+ while ((total_passes > 1) && (num_syms == hist[(total_passes - 1) * 256])) total_passes--;
+ for (pass_shift = 0, pass = 0; pass < total_passes; pass++, pass_shift += 8)
+ {
+ const uint32_t* pHist = &hist[pass << 8];
+ uint32_t offsets[256], cur_ofs = 0;
+ for (i = 0; i < 256; i++) { offsets[i] = cur_ofs; cur_ofs += pHist[i]; }
+ for (i = 0; i < num_syms; i++) pNew_syms[offsets[(pCur_syms[i].m_key >> pass_shift) & 0xFF]++] = pCur_syms[i];
+ { defl_sym_freq* t = pCur_syms; pCur_syms = pNew_syms; pNew_syms = t; }
+ }
+ return pCur_syms;
+ }
+
+ // defl_calculate_minimum_redundancy() originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996.
+ static void defl_calculate_minimum_redundancy(defl_sym_freq* A, int n)
+ {
+ int root, leaf, next, avbl, used, dpth;
+ if (n == 0) return; else if (n == 1) { A[0].m_key = 1; return; }
+ A[0].m_key += A[1].m_key; root = 0; leaf = 2;
+ for (next = 1; next < n - 1; next++)
+ {
+ if (leaf >= n || A[root].m_key < A[leaf].m_key) { A[next].m_key = A[root].m_key; A[root++].m_key = (uint16_t)next; }
+ else A[next].m_key = A[leaf++].m_key;
+ if (leaf >= n || (root < next && A[root].m_key < A[leaf].m_key)) { A[next].m_key = (uint16_t)(A[next].m_key + A[root].m_key); A[root++].m_key = (uint16_t)next; }
+ else A[next].m_key = (uint16_t)(A[next].m_key + A[leaf++].m_key);
+ }
+ A[n - 2].m_key = 0; for (next = n - 3; next >= 0; next--) A[next].m_key = A[A[next].m_key].m_key + 1;
+ avbl = 1; used = dpth = 0; root = n - 2; next = n - 1;
+ while (avbl > 0)
+ {
+ while (root >= 0 && (int)A[root].m_key == dpth) { used++; root--; }
+ while (avbl > used) { A[next--].m_key = (uint16_t)(dpth); avbl--; }
+ avbl = 2 * used; dpth++; used = 0;
+ }
+ }
+
+ // Limits canonical Huffman code table's max code size.
+ enum { DEFL_MAX_SUPPORTED_HUFF_CODESIZE = 32 };
+ static void defl_huffman_enforce_max_code_size(int* pNum_codes, int code_list_len, int max_code_size)
+ {
+ int i; uint32_t total = 0; if (code_list_len <= 1) return;
+ for (i = max_code_size + 1; i <= DEFL_MAX_SUPPORTED_HUFF_CODESIZE; i++) pNum_codes[max_code_size] += pNum_codes[i];
+ for (i = max_code_size; i > 0; i--) total += (((uint32_t)pNum_codes[i]) << (max_code_size - i));
+ while (total != (1UL << max_code_size))
+ {
+ pNum_codes[max_code_size]--;
+ for (i = max_code_size - 1; i > 0; i--) if (pNum_codes[i]) { pNum_codes[i]--; pNum_codes[i + 1] += 2; break; }
+ total--;
+ }
+ }
+
+ static void defl_optimize_huffman_table(defl_huff* d, int table_num, int table_len, int code_size_limit, int static_table)
+ {
+ int i, j, l, num_codes[1 + DEFL_MAX_SUPPORTED_HUFF_CODESIZE]; uint32_t next_code[DEFL_MAX_SUPPORTED_HUFF_CODESIZE + 1]; DEFL_CLEAR_OBJ(num_codes);
+ if (static_table)
+ {
+ for (i = 0; i < table_len; i++) num_codes[d->m_huff_code_sizes[table_num][i]]++;
+ }
+ else
+ {
+ defl_sym_freq syms0[DEFL_MAX_HUFF_SYMBOLS], syms1[DEFL_MAX_HUFF_SYMBOLS], * pSyms;
+ int num_used_syms = 0;
+ const uint16_t* pSym_count = &d->m_huff_count[table_num][0];
+ for (i = 0; i < table_len; i++) if (pSym_count[i]) { syms0[num_used_syms].m_key = (uint16_t)pSym_count[i]; syms0[num_used_syms++].m_sym_index = (uint16_t)i; }
+
+ pSyms = defl_radix_sort_syms(num_used_syms, syms0, syms1); defl_calculate_minimum_redundancy(pSyms, num_used_syms);
+
+ for (i = 0; i < num_used_syms; i++) num_codes[pSyms[i].m_key]++;
+
+ defl_huffman_enforce_max_code_size(num_codes, num_used_syms, code_size_limit);
+
+ DEFL_CLEAR_OBJ(d->m_huff_code_sizes[table_num]); DEFL_CLEAR_OBJ(d->m_huff_codes[table_num]);
+ for (i = 1, j = num_used_syms; i <= code_size_limit; i++)
+ for (l = num_codes[i]; l > 0; l--) d->m_huff_code_sizes[table_num][pSyms[--j].m_sym_index] = (uint8_t)(i);
+ }
+
+ next_code[1] = 0; for (j = 0, i = 2; i <= code_size_limit; i++) next_code[i] = j = ((j + num_codes[i - 1]) << 1);
+
+ for (i = 0; i < table_len; i++)
+ {
+ uint32_t rev_code = 0, code, code_size; if ((code_size = d->m_huff_code_sizes[table_num][i]) == 0) continue;
+ code = next_code[code_size]++; for (l = code_size; l > 0; l--, code >>= 1) rev_code = (rev_code << 1) | (code & 1);
+ d->m_huff_codes[table_num][i] = (uint16_t)rev_code;
+ }
+ }
+
+#define DEFL_RLE_PREV_CODE_SIZE() { if (rle_repeat_count) { \
+ if (rle_repeat_count < 3) { \
+ d->m_huff_count[2][prev_code_size] = (uint16_t)(d->m_huff_count[2][prev_code_size] + rle_repeat_count); \
+ while (rle_repeat_count--) packed_code_sizes[num_packed_code_sizes++] = prev_code_size; \
+ } else { \
+ d->m_huff_count[2][16] = (uint16_t)(d->m_huff_count[2][16] + 1); packed_code_sizes[num_packed_code_sizes++] = 16; packed_code_sizes[num_packed_code_sizes++] = (uint8_t)(rle_repeat_count - 3); \
+} rle_repeat_count = 0; } }
+
+#define DEFL_RLE_ZERO_CODE_SIZE() { if (rle_z_count) { \
+ if (rle_z_count < 3) { \
+ d->m_huff_count[2][0] = (uint16_t)(d->m_huff_count[2][0] + rle_z_count); while (rle_z_count--) packed_code_sizes[num_packed_code_sizes++] = 0; \
+ } else if (rle_z_count <= 10) { \
+ d->m_huff_count[2][17] = (uint16_t)(d->m_huff_count[2][17] + 1); packed_code_sizes[num_packed_code_sizes++] = 17; packed_code_sizes[num_packed_code_sizes++] = (uint8_t)(rle_z_count - 3); \
+ } else { \
+ d->m_huff_count[2][18] = (uint16_t)(d->m_huff_count[2][18] + 1); packed_code_sizes[num_packed_code_sizes++] = 18; packed_code_sizes[num_packed_code_sizes++] = (uint8_t)(rle_z_count - 11); \
+} rle_z_count = 0; } }
+
+ static uint8_t g_defl_packed_code_size_syms_swizzle[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+
+#define DEFL_DYN_PUT_BITS(bb, ll) \
+do { \
+ uint32_t b = (bb), l = (ll); \
+ assert((l) >= 1 && (l) <= 16); assert((b) < (1ULL << (l))); \
+ bit_buf |= (((uint64_t)(b)) << bit_buf_size); bit_buf_size += (l); assert(bit_buf_size <= 64); \
+ while (bit_buf_size >= 8) \
+ { \
+ if ((dst_ofs + 1) > dst_buf_size) \
+ return false; \
+ *(uint8_t*)(pDst + dst_ofs) = (uint8_t)bit_buf; \
+ dst_ofs++; \
+ bit_buf >>= 8; \
+ bit_buf_size -= 8; \
+ } \
+} while(0)
+
+ static bool defl_start_dynamic_block(defl_huff* d, uint8_t* pDst, uint32_t& dst_ofs, uint32_t dst_buf_size, uint64_t& bit_buf, int& bit_buf_size)
+ {
+ int num_lit_codes, num_dist_codes, num_bit_lengths; uint32_t i, total_code_sizes_to_pack, num_packed_code_sizes, rle_z_count, rle_repeat_count, packed_code_sizes_index;
+ uint8_t code_sizes_to_pack[DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1], packed_code_sizes[DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1], prev_code_size = 0xFF;
+
+#if FPNG_TRAIN_HUFFMAN_TABLES
+ assert(HUFF_COUNTS_SIZE == DEFL_MAX_HUFF_SYMBOLS_0);
+ for (uint32_t i = 0; i < DEFL_MAX_HUFF_SYMBOLS_0; i++)
+ g_huff_counts[i] += d->m_huff_count[0][i];
+#endif
+
+ d->m_huff_count[0][256] = 1;
+
+ defl_optimize_huffman_table(d, 0, DEFL_MAX_HUFF_SYMBOLS_0, 12, FPNG_FALSE);
+ defl_optimize_huffman_table(d, 1, DEFL_MAX_HUFF_SYMBOLS_1, 12, FPNG_FALSE);
+
+ for (num_lit_codes = 286; num_lit_codes > 257; num_lit_codes--) if (d->m_huff_code_sizes[0][num_lit_codes - 1]) break;
+ for (num_dist_codes = 30; num_dist_codes > 1; num_dist_codes--) if (d->m_huff_code_sizes[1][num_dist_codes - 1]) break;
+
+ memcpy(code_sizes_to_pack, &d->m_huff_code_sizes[0][0], num_lit_codes);
+ memcpy(code_sizes_to_pack + num_lit_codes, &d->m_huff_code_sizes[1][0], num_dist_codes);
+ total_code_sizes_to_pack = num_lit_codes + num_dist_codes; num_packed_code_sizes = 0; rle_z_count = 0; rle_repeat_count = 0;
+
+ memset(&d->m_huff_count[2][0], 0, sizeof(d->m_huff_count[2][0]) * DEFL_MAX_HUFF_SYMBOLS_2);
+ for (i = 0; i < total_code_sizes_to_pack; i++)
+ {
+ uint8_t code_size = code_sizes_to_pack[i];
+ if (!code_size)
+ {
+ DEFL_RLE_PREV_CODE_SIZE();
+ if (++rle_z_count == 138) { DEFL_RLE_ZERO_CODE_SIZE(); }
+ }
+ else
+ {
+ DEFL_RLE_ZERO_CODE_SIZE();
+ if (code_size != prev_code_size)
+ {
+ DEFL_RLE_PREV_CODE_SIZE();
+ d->m_huff_count[2][code_size] = (uint16_t)(d->m_huff_count[2][code_size] + 1); packed_code_sizes[num_packed_code_sizes++] = code_size;
+ }
+ else if (++rle_repeat_count == 6)
+ {
+ DEFL_RLE_PREV_CODE_SIZE();
+ }
+ }
+ prev_code_size = code_size;
+ }
+ if (rle_repeat_count) { DEFL_RLE_PREV_CODE_SIZE(); }
+ else { DEFL_RLE_ZERO_CODE_SIZE(); }
+
+ defl_optimize_huffman_table(d, 2, DEFL_MAX_HUFF_SYMBOLS_2, 7, FPNG_FALSE);
+
+ // max of 2+5+5+4+18*3+(288+32)*7=2310 bits
+ DEFL_DYN_PUT_BITS(2, 2);
+
+ DEFL_DYN_PUT_BITS(num_lit_codes - 257, 5);
+ DEFL_DYN_PUT_BITS(num_dist_codes - 1, 5);
+
+ for (num_bit_lengths = 18; num_bit_lengths >= 0; num_bit_lengths--) if (d->m_huff_code_sizes[2][g_defl_packed_code_size_syms_swizzle[num_bit_lengths]]) break;
+ num_bit_lengths = maximum(4, (num_bit_lengths + 1)); DEFL_DYN_PUT_BITS(num_bit_lengths - 4, 4);
+ for (i = 0; (int)i < num_bit_lengths; i++) DEFL_DYN_PUT_BITS(d->m_huff_code_sizes[2][g_defl_packed_code_size_syms_swizzle[i]], 3);
+
+ for (packed_code_sizes_index = 0; packed_code_sizes_index < num_packed_code_sizes; )
+ {
+ uint32_t code = packed_code_sizes[packed_code_sizes_index++]; assert(code < DEFL_MAX_HUFF_SYMBOLS_2);
+ DEFL_DYN_PUT_BITS(d->m_huff_codes[2][code], d->m_huff_code_sizes[2][code]);
+ if (code >= 16) DEFL_DYN_PUT_BITS(packed_code_sizes[packed_code_sizes_index++], "\02\03\07"[code - 16]);
+ }
+
+ return true;
+ }
+
+ static uint32_t write_raw_block(const uint8_t* pSrc, uint32_t src_len, uint8_t* pDst, uint32_t dst_buf_size)
+ {
+ if (dst_buf_size < 2)
+ return 0;
+
+ pDst[0] = 0x78;
+ pDst[1] = 0x01;
+
+ uint32_t dst_ofs = 2;
+
+ uint32_t src_ofs = 0;
+ while (src_ofs < src_len)
+ {
+ const uint32_t src_remaining = src_len - src_ofs;
+ const uint32_t block_size = minimum(UINT16_MAX, src_remaining);
+ const bool final_block = (block_size == src_remaining);
+
+ if ((dst_ofs + 5 + block_size) > dst_buf_size)
+ return 0;
+
+ pDst[dst_ofs + 0] = final_block ? 1 : 0;
+
+ pDst[dst_ofs + 1] = block_size & 0xFF;
+ pDst[dst_ofs + 2] = (block_size >> 8) & 0xFF;
+
+ pDst[dst_ofs + 3] = (~block_size) & 0xFF;
+ pDst[dst_ofs + 4] = ((~block_size) >> 8) & 0xFF;
+
+ memcpy(pDst + dst_ofs + 5, pSrc + src_ofs, block_size);
+
+ src_ofs += block_size;
+ dst_ofs += 5 + block_size;
+ }
+
+ uint32_t src_adler32 = fpng_adler32(pSrc, src_len, FPNG_ADLER32_INIT);
+
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if (dst_ofs + 1 > dst_buf_size)
+ return 0;
+
+ pDst[dst_ofs] = (uint8_t)(src_adler32 >> 24);
+ dst_ofs++;
+
+ src_adler32 <<= 8;
+ }
+
+ return dst_ofs;
+ }
+
+ static void adjust_freq32(uint32_t num_freq, uint32_t* pFreq, uint16_t* pFreq16)
+ {
+ uint32_t total_freq = 0;
+ for (uint32_t i = 0; i < num_freq; i++)
+ total_freq += pFreq[i];
+
+ if (!total_freq)
+ {
+ memset(pFreq16, 0, num_freq * sizeof(uint16_t));
+ return;
+ }
+
+ uint32_t total_freq16 = 0;
+ for (uint32_t i = 0; i < num_freq; i++)
+ {
+ uint64_t f = pFreq[i];
+ if (!f)
+ {
+ pFreq16[i] = 0;
+ continue;
+ }
+
+ pFreq16[i] = (uint16_t)maximum(1, (uint32_t)((f * UINT16_MAX) / total_freq));
+
+ total_freq16 += pFreq16[i];
+ }
+
+ while (total_freq16 > UINT16_MAX)
+ {
+ total_freq16 = 0;
+ for (uint32_t i = 0; i < num_freq; i++)
+ {
+ if (pFreq[i])
+ {
+ pFreq[i] = maximum(1, pFreq[i] >> 1);
+ total_freq16 += pFreq[i];
+ }
+ }
+ }
+ }
+
+#if FPNG_TRAIN_HUFFMAN_TABLES
+ bool create_dynamic_block_prefix(uint64_t* pFreq, uint32_t num_chans, std::vector& prefix, uint64_t& bit_buf, int &bit_buf_size, uint32_t* pCodes, uint8_t* pCodesizes)
+ {
+ assert((num_chans == 3) || (num_chans == 4));
+ assert(HUFF_COUNTS_SIZE == DEFL_MAX_HUFF_SYMBOLS_0); // must be equal
+
+ defl_huff dh;
+ memset(&dh, 0, sizeof(dh));
+
+ uint32_t lit_freq[DEFL_MAX_HUFF_SYMBOLS_0];
+
+ uint32_t shift_len = 0;
+ for (; ; )
+ {
+ uint32_t i;
+ for (i = 0; i < DEFL_MAX_HUFF_SYMBOLS_0; i++)
+ {
+ uint64_t f = pFreq[i];
+ if (f)
+ f = maximum(1U, f >> shift_len);
+
+ if (f > UINT32_MAX)
+ break;
+
+ lit_freq[i] = (uint32_t)pFreq[i];
+ }
+
+ if (i == DEFL_MAX_HUFF_SYMBOLS_0)
+ break;
+
+ shift_len++;
+ }
+
+ // Ensure all valid Deflate literal/EOB/length syms are non-zero, so anything can be coded.
+ for (uint32_t i = 0; i <= 256; i++)
+ {
+ if (!lit_freq[i])
+ lit_freq[i] = 1;
+ }
+
+ for (uint32_t len = num_chans; len <= DEFL_MAX_MATCH_LEN; len += num_chans)
+ {
+ uint32_t sym = g_defl_len_sym[len - 3];
+ if (!lit_freq[sym])
+ lit_freq[sym] = 1;
+ }
+
+ adjust_freq32(DEFL_MAX_HUFF_SYMBOLS_0, lit_freq, &dh.m_huff_count[0][0]);
+
+ const uint32_t dist_sym = g_defl_small_dist_sym[num_chans - 1];
+ dh.m_huff_count[1][dist_sym] = 1;
+ dh.m_huff_count[1][dist_sym + 1] = 1; // to workaround a bug in wuffs decoder
+
+ prefix.resize(4096);
+ uint8_t* pDst = prefix.data();
+ uint32_t dst_buf_size = (uint32_t)prefix.size();
+
+ uint32_t dst_ofs = 0;
+
+ // zlib header
+ PUT_BITS(0x78, 8);
+ PUT_BITS(0x01, 8);
+
+ // write BFINAL bit
+ PUT_BITS(1, 1);
+
+ if (!defl_start_dynamic_block(&dh, pDst, dst_ofs, dst_buf_size, bit_buf, bit_buf_size))
+ return false;
+
+ prefix.resize(dst_ofs);
+
+ for (uint32_t i = 0; i < DEFL_MAX_HUFF_SYMBOLS_0; i++)
+ {
+ pCodes[i] = dh.m_huff_codes[0][i];
+ pCodesizes[i] = dh.m_huff_code_sizes[0][i];
+ }
+
+ return true;
+ }
+#endif
+
+ static uint32_t pixel_deflate_dyn_3_rle(
+ const uint8_t* pImg, uint32_t w, uint32_t h,
+ uint8_t* pDst, uint32_t dst_buf_size)
+ {
+ const uint32_t bpl = 1 + w * 3;
+
+ uint64_t bit_buf = 0;
+ int bit_buf_size = 0;
+
+ uint32_t dst_ofs = 0;
+
+ // zlib header
+ PUT_BITS(0x78, 8);
+ PUT_BITS(0x01, 8);
+
+ // write BFINAL bit
+ PUT_BITS(1, 1);
+
+ std::vector codes((w + 1) * h);
+ uint32_t* pDst_codes = codes.data();
+
+ uint32_t lit_freq[DEFL_MAX_HUFF_SYMBOLS_0];
+ memset(lit_freq, 0, sizeof(lit_freq));
+
+ const uint8_t* pSrc = pImg;
+ uint32_t src_ofs = 0;
+
+ uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT);
+
+ const uint32_t dist_sym = g_defl_small_dist_sym[3 - 1];
+
+ for (uint32_t y = 0; y < h; y++)
+ {
+ const uint32_t end_src_ofs = src_ofs + bpl;
+
+ const uint32_t filter_lit = pSrc[src_ofs++];
+ *pDst_codes++ = 1 | (filter_lit << 8);
+ lit_freq[filter_lit]++;
+
+ uint32_t prev_lits;
+
+ {
+ uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs);
+
+ *pDst_codes++ = lits << 8;
+
+ lit_freq[lits & 0xFF]++;
+ lit_freq[(lits >> 8) & 0xFF]++;
+ lit_freq[lits >> 16]++;
+
+ src_ofs += 3;
+
+ prev_lits = lits;
+ }
+
+ while (src_ofs < end_src_ofs)
+ {
+ uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs);
+
+ if (lits == prev_lits)
+ {
+ uint32_t match_len = 3;
+ uint32_t max_match_len = minimum(255, (int)(end_src_ofs - src_ofs));
+
+ while (match_len < max_match_len)
+ {
+ if (READ_RGB_PIXEL(pSrc + src_ofs + match_len) != lits)
+ break;
+ match_len += 3;
+ }
+
+ *pDst_codes++ = match_len - 1;
+
+ uint32_t adj_match_len = match_len - 3;
+
+ lit_freq[g_defl_len_sym[adj_match_len]]++;
+
+ src_ofs += match_len;
+ }
+ else
+ {
+ *pDst_codes++ = lits << 8;
+
+ lit_freq[lits & 0xFF]++;
+ lit_freq[(lits >> 8) & 0xFF]++;
+ lit_freq[lits >> 16]++;
+
+ prev_lits = lits;
+
+ src_ofs += 3;
+ }
+
+ } // while (src_ofs < end_src_ofs)
+
+ } // y
+
+ assert(src_ofs == h * bpl);
+ const uint32_t total_codes = (uint32_t)(pDst_codes - codes.data());
+ assert(total_codes <= codes.size());
+
+ defl_huff dh;
+
+ lit_freq[256] = 1;
+
+ adjust_freq32(DEFL_MAX_HUFF_SYMBOLS_0, lit_freq, &dh.m_huff_count[0][0]);
+
+ memset(&dh.m_huff_count[1][0], 0, sizeof(dh.m_huff_count[1][0]) * DEFL_MAX_HUFF_SYMBOLS_1);
+ dh.m_huff_count[1][dist_sym] = 1;
+ dh.m_huff_count[1][dist_sym + 1] = 1; // to workaround a bug in wuffs decoder
+
+ if (!defl_start_dynamic_block(&dh, pDst, dst_ofs, dst_buf_size, bit_buf, bit_buf_size))
+ return 0;
+
+ assert(bit_buf_size <= 7);
+ assert(dh.m_huff_codes[1][dist_sym] == 0 && dh.m_huff_code_sizes[1][dist_sym] == 1);
+
+ for (uint32_t i = 0; i < total_codes; i++)
+ {
+ uint32_t c = codes[i];
+
+ uint32_t c_type = c & 0xFF;
+ if (c_type == 0)
+ {
+ uint32_t lits = c >> 8;
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]);
+ lits >>= 8;
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]);
+ lits >>= 8;
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][lits], dh.m_huff_code_sizes[0][lits]);
+ }
+ else if (c_type == 1)
+ {
+ uint32_t lit = c >> 8;
+ PUT_BITS_CZ(dh.m_huff_codes[0][lit], dh.m_huff_code_sizes[0][lit]);
+ }
+ else
+ {
+ uint32_t match_len = c_type + 1;
+
+ uint32_t adj_match_len = match_len - 3;
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][g_defl_len_sym[adj_match_len]], dh.m_huff_code_sizes[0][g_defl_len_sym[adj_match_len]]);
+ PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], g_defl_len_extra[adj_match_len] + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0
+
+ // no need to write the distance code, it's always 0
+ //PUT_BITS_CZ(dh.m_huff_codes[1][dist_sym], dh.m_huff_code_sizes[1][dist_sym]);
+ }
+
+ // up to 55 bits
+ PUT_BITS_FLUSH;
+ }
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][256], dh.m_huff_code_sizes[0][256]);
+
+ PUT_BITS_FORCE_FLUSH;
+
+ // Write zlib adler32
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if ((dst_ofs + 1) > dst_buf_size)
+ return 0;
+ *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24);
+ dst_ofs++;
+
+ src_adler32 <<= 8;
+ }
+
+ return dst_ofs;
+ }
+
+ static uint32_t pixel_deflate_dyn_3_rle_one_pass(
+ const uint8_t* pImg, uint32_t w, uint32_t h,
+ uint8_t* pDst, uint32_t dst_buf_size)
+ {
+ const uint32_t bpl = 1 + w * 3;
+
+ if (dst_buf_size < sizeof(g_dyn_huff_3))
+ return false;
+ memcpy(pDst, g_dyn_huff_3, sizeof(g_dyn_huff_3));
+ uint32_t dst_ofs = sizeof(g_dyn_huff_3);
+
+ uint64_t bit_buf = DYN_HUFF_3_BITBUF;
+ int bit_buf_size = DYN_HUFF_3_BITBUF_SIZE;
+
+ const uint8_t* pSrc = pImg;
+ uint32_t src_ofs = 0;
+
+ uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT);
+
+ for (uint32_t y = 0; y < h; y++)
+ {
+ const uint32_t end_src_ofs = src_ofs + bpl;
+
+ const uint32_t filter_lit = pSrc[src_ofs++];
+ PUT_BITS_CZ(g_dyn_huff_3_codes[filter_lit].m_code, g_dyn_huff_3_codes[filter_lit].m_code_size);
+
+ uint32_t prev_lits;
+
+ {
+ uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs);
+
+ PUT_BITS_CZ(g_dyn_huff_3_codes[lits & 0xFF].m_code, g_dyn_huff_3_codes[lits & 0xFF].m_code_size);
+ PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code_size);
+ PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 16)].m_code, g_dyn_huff_3_codes[(lits >> 16)].m_code_size);
+
+ src_ofs += 3;
+
+ prev_lits = lits;
+ }
+
+ PUT_BITS_FLUSH;
+
+ while (src_ofs < end_src_ofs)
+ {
+ uint32_t lits = READ_RGB_PIXEL(pSrc + src_ofs);
+
+ if (lits == prev_lits)
+ {
+ uint32_t match_len = 3;
+ uint32_t max_match_len = minimum(255, (int)(end_src_ofs - src_ofs));
+
+ while (match_len < max_match_len)
+ {
+ if (READ_RGB_PIXEL(pSrc + src_ofs + match_len) != lits)
+ break;
+ match_len += 3;
+ }
+
+ uint32_t adj_match_len = match_len - 3;
+
+ PUT_BITS_CZ(g_dyn_huff_3_codes[g_defl_len_sym[adj_match_len]].m_code, g_dyn_huff_3_codes[g_defl_len_sym[adj_match_len]].m_code_size);
+ PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], g_defl_len_extra[adj_match_len] + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0
+
+ src_ofs += match_len;
+ }
+ else
+ {
+ PUT_BITS_CZ(g_dyn_huff_3_codes[lits & 0xFF].m_code, g_dyn_huff_3_codes[lits & 0xFF].m_code_size);
+ PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_3_codes[(lits >> 8) & 0xFF].m_code_size);
+ PUT_BITS_CZ(g_dyn_huff_3_codes[(lits >> 16)].m_code, g_dyn_huff_3_codes[(lits >> 16)].m_code_size);
+
+ prev_lits = lits;
+
+ src_ofs += 3;
+ }
+
+ PUT_BITS_FLUSH;
+
+ } // while (src_ofs < end_src_ofs)
+
+ } // y
+
+ assert(src_ofs == h * bpl);
+
+ assert(bit_buf_size <= 7);
+
+ PUT_BITS_CZ(g_dyn_huff_3_codes[256].m_code, g_dyn_huff_3_codes[256].m_code_size);
+
+ PUT_BITS_FORCE_FLUSH;
+
+ // Write zlib adler32
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if ((dst_ofs + 1) > dst_buf_size)
+ return 0;
+ *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24);
+ dst_ofs++;
+
+ src_adler32 <<= 8;
+ }
+
+ return dst_ofs;
+ }
+
+ static uint32_t pixel_deflate_dyn_4_rle(
+ const uint8_t* pImg, uint32_t w, uint32_t h,
+ uint8_t* pDst, uint32_t dst_buf_size)
+ {
+ const uint32_t bpl = 1 + w * 4;
+
+ uint64_t bit_buf = 0;
+ int bit_buf_size = 0;
+
+ uint32_t dst_ofs = 0;
+
+ // zlib header
+ PUT_BITS(0x78, 8);
+ PUT_BITS(0x01, 8);
+
+ // write BFINAL bit
+ PUT_BITS(1, 1);
+
+ std::vector codes;
+ codes.resize((w + 1) * h);
+ uint64_t* pDst_codes = codes.data();
+
+ uint32_t lit_freq[DEFL_MAX_HUFF_SYMBOLS_0];
+ memset(lit_freq, 0, sizeof(lit_freq));
+
+ const uint8_t* pSrc = pImg;
+ uint32_t src_ofs = 0;
+
+ uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT);
+
+ const uint32_t dist_sym = g_defl_small_dist_sym[4 - 1];
+
+ for (uint32_t y = 0; y < h; y++)
+ {
+ const uint32_t end_src_ofs = src_ofs + bpl;
+
+ const uint32_t filter_lit = pSrc[src_ofs++];
+ *pDst_codes++ = 1 | (filter_lit << 8);
+ lit_freq[filter_lit]++;
+
+ uint32_t prev_lits;
+ {
+ uint32_t lits = READ_LE32(pSrc + src_ofs);
+
+ *pDst_codes++ = (uint64_t)lits << 8;
+
+ lit_freq[lits & 0xFF]++;
+ lit_freq[(lits >> 8) & 0xFF]++;
+ lit_freq[(lits >> 16) & 0xFF]++;
+ lit_freq[lits >> 24]++;
+
+ src_ofs += 4;
+
+ prev_lits = lits;
+ }
+
+ while (src_ofs < end_src_ofs)
+ {
+ uint32_t lits = READ_LE32(pSrc + src_ofs);
+
+ if (lits == prev_lits)
+ {
+ uint32_t match_len = 4;
+ uint32_t max_match_len = minimum(252, (int)(end_src_ofs - src_ofs));
+
+ while (match_len < max_match_len)
+ {
+ if (READ_LE32(pSrc + src_ofs + match_len) != lits)
+ break;
+ match_len += 4;
+ }
+
+ *pDst_codes++ = match_len - 1;
+
+ uint32_t adj_match_len = match_len - 3;
+
+ lit_freq[g_defl_len_sym[adj_match_len]]++;
+
+ src_ofs += match_len;
+ }
+ else
+ {
+ *pDst_codes++ = (uint64_t)lits << 8;
+
+ lit_freq[lits & 0xFF]++;
+ lit_freq[(lits >> 8) & 0xFF]++;
+ lit_freq[(lits >> 16) & 0xFF]++;
+ lit_freq[lits >> 24]++;
+
+ prev_lits = lits;
+
+ src_ofs += 4;
+ }
+
+ } // while (src_ofs < end_src_ofs)
+
+ } // y
+
+ assert(src_ofs == h * bpl);
+ const uint32_t total_codes = (uint32_t)(pDst_codes - codes.data());
+ assert(total_codes <= codes.size());
+
+ defl_huff dh;
+
+ lit_freq[256] = 1;
+
+ adjust_freq32(DEFL_MAX_HUFF_SYMBOLS_0, lit_freq, &dh.m_huff_count[0][0]);
+
+ memset(&dh.m_huff_count[1][0], 0, sizeof(dh.m_huff_count[1][0]) * DEFL_MAX_HUFF_SYMBOLS_1);
+ dh.m_huff_count[1][dist_sym] = 1;
+ dh.m_huff_count[1][dist_sym + 1] = 1; // to workaround a bug in wuffs decoder
+
+ if (!defl_start_dynamic_block(&dh, pDst, dst_ofs, dst_buf_size, bit_buf, bit_buf_size))
+ return 0;
+
+ assert(bit_buf_size <= 7);
+ assert(dh.m_huff_codes[1][dist_sym] == 0 && dh.m_huff_code_sizes[1][dist_sym] == 1);
+
+ for (uint32_t i = 0; i < total_codes; i++)
+ {
+ uint64_t c = codes[i];
+
+ uint32_t c_type = (uint32_t)(c & 0xFF);
+ if (c_type == 0)
+ {
+ uint32_t lits = (uint32_t)(c >> 8);
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]);
+ lits >>= 8;
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]);
+ lits >>= 8;
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][lits & 0xFF], dh.m_huff_code_sizes[0][lits & 0xFF]);
+ lits >>= 8;
+
+ if (bit_buf_size >= 49)
+ {
+ PUT_BITS_FLUSH;
+ }
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][lits], dh.m_huff_code_sizes[0][lits]);
+ }
+ else if (c_type == 1)
+ {
+ uint32_t lit = (uint32_t)(c >> 8);
+ PUT_BITS_CZ(dh.m_huff_codes[0][lit], dh.m_huff_code_sizes[0][lit]);
+ }
+ else
+ {
+ uint32_t match_len = c_type + 1;
+
+ uint32_t adj_match_len = match_len - 3;
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][g_defl_len_sym[adj_match_len]], dh.m_huff_code_sizes[0][g_defl_len_sym[adj_match_len]]);
+ PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], g_defl_len_extra[adj_match_len] + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0
+
+ // no need to write the distance code, it's always 0
+ }
+
+ // up to 55 bits
+ PUT_BITS_FLUSH;
+ }
+
+ PUT_BITS_CZ(dh.m_huff_codes[0][256], dh.m_huff_code_sizes[0][256]);
+
+ PUT_BITS_FORCE_FLUSH;
+
+ // Write zlib adler32
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if ((dst_ofs + 1) > dst_buf_size)
+ return 0;
+ *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24);
+ dst_ofs++;
+
+ src_adler32 <<= 8;
+ }
+
+ return dst_ofs;
+ }
+
+ static uint32_t pixel_deflate_dyn_4_rle_one_pass(
+ const uint8_t* pImg, uint32_t w, uint32_t h,
+ uint8_t* pDst, uint32_t dst_buf_size)
+ {
+ const uint32_t bpl = 1 + w * 4;
+
+ if (dst_buf_size < sizeof(g_dyn_huff_4))
+ return false;
+ memcpy(pDst, g_dyn_huff_4, sizeof(g_dyn_huff_4));
+ uint32_t dst_ofs = sizeof(g_dyn_huff_4);
+
+ uint64_t bit_buf = DYN_HUFF_4_BITBUF;
+ int bit_buf_size = DYN_HUFF_4_BITBUF_SIZE;
+
+ const uint8_t* pSrc = pImg;
+ uint32_t src_ofs = 0;
+
+ uint32_t src_adler32 = fpng_adler32(pImg, bpl * h, FPNG_ADLER32_INIT);
+
+ for (uint32_t y = 0; y < h; y++)
+ {
+ const uint32_t end_src_ofs = src_ofs + bpl;
+
+ const uint32_t filter_lit = pSrc[src_ofs++];
+ PUT_BITS_CZ(g_dyn_huff_4_codes[filter_lit].m_code, g_dyn_huff_4_codes[filter_lit].m_code_size);
+
+ PUT_BITS_FLUSH;
+
+ uint32_t prev_lits;
+ {
+ uint32_t lits = READ_LE32(pSrc + src_ofs);
+
+ PUT_BITS_CZ(g_dyn_huff_4_codes[lits & 0xFF].m_code, g_dyn_huff_4_codes[lits & 0xFF].m_code_size);
+ PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code_size);
+ PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code_size);
+
+ if (bit_buf_size >= 49)
+ {
+ PUT_BITS_FLUSH;
+ }
+
+ PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 24)].m_code, g_dyn_huff_4_codes[(lits >> 24)].m_code_size);
+
+ src_ofs += 4;
+
+ prev_lits = lits;
+ }
+
+ PUT_BITS_FLUSH;
+
+ while (src_ofs < end_src_ofs)
+ {
+ uint32_t lits = READ_LE32(pSrc + src_ofs);
+
+ if (lits == prev_lits)
+ {
+ uint32_t match_len = 4;
+ uint32_t max_match_len = minimum(252, (int)(end_src_ofs - src_ofs));
+
+ while (match_len < max_match_len)
+ {
+ if (READ_LE32(pSrc + src_ofs + match_len) != lits)
+ break;
+ match_len += 4;
+ }
+
+ uint32_t adj_match_len = match_len - 3;
+
+ const uint32_t match_code_bits = g_dyn_huff_4_codes[g_defl_len_sym[adj_match_len]].m_code_size;
+ const uint32_t len_extra_bits = g_defl_len_extra[adj_match_len];
+
+ if (match_len == 4)
+ {
+ // This check is optional - see if just encoding 4 literals would be cheaper than using a short match.
+ uint32_t lit_bits = g_dyn_huff_4_codes[lits & 0xFF].m_code_size + g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code_size +
+ g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code_size + g_dyn_huff_4_codes[(lits >> 24)].m_code_size;
+
+ if ((match_code_bits + len_extra_bits + 1) > lit_bits)
+ goto do_literals;
+ }
+
+ PUT_BITS_CZ(g_dyn_huff_4_codes[g_defl_len_sym[adj_match_len]].m_code, match_code_bits);
+ PUT_BITS(adj_match_len & g_bitmasks[g_defl_len_extra[adj_match_len]], len_extra_bits + 1); // up to 6 bits, +1 for the match distance Huff code which is always 0
+
+ src_ofs += match_len;
+ }
+ else
+ {
+do_literals:
+ PUT_BITS_CZ(g_dyn_huff_4_codes[lits & 0xFF].m_code, g_dyn_huff_4_codes[lits & 0xFF].m_code_size);
+ PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 8) & 0xFF].m_code_size);
+ PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code, g_dyn_huff_4_codes[(lits >> 16) & 0xFF].m_code_size);
+
+ if (bit_buf_size >= 49)
+ {
+ PUT_BITS_FLUSH;
+ }
+
+ PUT_BITS_CZ(g_dyn_huff_4_codes[(lits >> 24)].m_code, g_dyn_huff_4_codes[(lits >> 24)].m_code_size);
+
+ src_ofs += 4;
+
+ prev_lits = lits;
+ }
+
+ PUT_BITS_FLUSH;
+
+ } // while (src_ofs < end_src_ofs)
+
+ } // y
+
+ assert(src_ofs == h * bpl);
+
+ assert(bit_buf_size <= 7);
+
+ PUT_BITS_CZ(g_dyn_huff_4_codes[256].m_code, g_dyn_huff_4_codes[256].m_code_size);
+
+ PUT_BITS_FORCE_FLUSH;
+
+ // Write zlib adler32
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ if ((dst_ofs + 1) > dst_buf_size)
+ return 0;
+ *(uint8_t*)(pDst + dst_ofs) = (uint8_t)(src_adler32 >> 24);
+ dst_ofs++;
+
+ src_adler32 <<= 8;
+ }
+
+ return dst_ofs;
+ }
+
+ static void vector_append(std::vector& buf, const void* pData, size_t len)
+ {
+ if (len)
+ {
+ size_t l = buf.size();
+ buf.resize(l + len);
+ memcpy(buf.data() + l, pData, len);
+ }
+ }
+
+ static void apply_filter(uint32_t filter, int w, int h, uint32_t num_chans, uint32_t bpl, const uint8_t* pSrc, const uint8_t* pPrev_src, uint8_t* pDst)
+ {
+ (void)h;
+
+ switch (filter)
+ {
+ case 0:
+ {
+ *pDst++ = 0;
+
+ memcpy(pDst, pSrc, bpl);
+ break;
+ }
+ case 2:
+ {
+ assert(pPrev_src);
+
+ // Previous scanline
+ *pDst++ = 2;
+
+#if FPNG_X86_OR_X64_CPU && !FPNG_NO_SSE
+ if (g_cpu_info.can_use_sse41())
+ {
+ uint32_t bytes_to_process = w * num_chans, ofs = 0;
+ for (; bytes_to_process >= 16; bytes_to_process -= 16, ofs += 16)
+ _mm_storeu_si128((__m128i*)(pDst + ofs), _mm_sub_epi8(_mm_loadu_si128((const __m128i*)(pSrc + ofs)), _mm_loadu_si128((const __m128i*)(pPrev_src + ofs))));
+
+ for (; bytes_to_process; bytes_to_process--, ofs++)
+ pDst[ofs] = (uint8_t)(pSrc[ofs] - pPrev_src[ofs]);
+ }
+ else
+#endif
+ {
+ if (num_chans == 3)
+ {
+ for (uint32_t x = 0; x < (uint32_t)w; x++)
+ {
+ pDst[0] = (uint8_t)(pSrc[0] - pPrev_src[0]);
+ pDst[1] = (uint8_t)(pSrc[1] - pPrev_src[1]);
+ pDst[2] = (uint8_t)(pSrc[2] - pPrev_src[2]);
+
+ pSrc += 3;
+ pPrev_src += 3;
+ pDst += 3;
+ }
+ }
+ else
+ {
+ for (uint32_t x = 0; x < (uint32_t)w; x++)
+ {
+ pDst[0] = (uint8_t)(pSrc[0] - pPrev_src[0]);
+ pDst[1] = (uint8_t)(pSrc[1] - pPrev_src[1]);
+ pDst[2] = (uint8_t)(pSrc[2] - pPrev_src[2]);
+ pDst[3] = (uint8_t)(pSrc[3] - pPrev_src[3]);
+
+ pSrc += 4;
+ pPrev_src += 4;
+ pDst += 4;
+ }
+ }
+ }
+
+ break;
+ }
+ default:
+ assert(0);
+ break;
+ }
+ }
+
+ bool fpng_encode_image_to_memory(const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, std::vector& out_buf, uint32_t flags)
+ {
+ if (!endian_check())
+ {
+ assert(0);
+ return false;
+ }
+
+ if ((w < 1) || (h < 1) || (w * (uint64_t)h > UINT32_MAX) || (w > FPNG_MAX_SUPPORTED_DIM) || (h > FPNG_MAX_SUPPORTED_DIM))
+ {
+ assert(0);
+ return false;
+ }
+
+ if ((num_chans != 3) && (num_chans != 4))
+ {
+ assert(0);
+ return false;
+ }
+
+ int i, bpl = w * num_chans;
+ uint32_t y;
+
+ std::vector temp_buf;
+ temp_buf.resize((bpl + 1) * h + 7);
+ uint32_t temp_buf_ofs = 0;
+
+ for (y = 0; y < h; ++y)
+ {
+ const uint8_t* pSrc = (uint8_t*)pImage + y * bpl;
+ const uint8_t* pPrev_src = y ? ((uint8_t*)pImage + (y - 1) * bpl) : nullptr;
+
+ uint8_t* pDst = &temp_buf[temp_buf_ofs];
+
+ apply_filter(y ? 2 : 0, w, h, num_chans, bpl, pSrc, pPrev_src, pDst);
+
+ temp_buf_ofs += 1 + bpl;
+ }
+
+ const uint32_t PNG_HEADER_SIZE = 58;
+
+ uint32_t out_ofs = PNG_HEADER_SIZE;
+
+ out_buf.resize((out_ofs + (bpl + 1) * h + 7) & ~7);
+
+ uint32_t defl_size = 0;
+ if ((flags & FPNG_FORCE_UNCOMPRESSED) == 0)
+ {
+ if (num_chans == 3)
+ {
+ if (flags & FPNG_ENCODE_SLOWER)
+ defl_size = pixel_deflate_dyn_3_rle(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs);
+ else
+ defl_size = pixel_deflate_dyn_3_rle_one_pass(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs);
+ }
+ else
+ {
+ if (flags & FPNG_ENCODE_SLOWER)
+ defl_size = pixel_deflate_dyn_4_rle(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs);
+ else
+ defl_size = pixel_deflate_dyn_4_rle_one_pass(temp_buf.data(), w, h, &out_buf[out_ofs], (uint32_t)out_buf.size() - out_ofs);
+ }
+ }
+
+ uint32_t zlib_size = defl_size;
+
+ if (!defl_size)
+ {
+ // Dynamic block failed to compress - fall back to uncompressed blocks, filter 0.
+
+ temp_buf_ofs = 0;
+
+ for (y = 0; y < h; ++y)
+ {
+ const uint8_t* pSrc = (uint8_t*)pImage + y * bpl;
+
+ uint8_t* pDst = &temp_buf[temp_buf_ofs];
+
+ apply_filter(0, w, h, num_chans, bpl, pSrc, nullptr, pDst);
+
+ temp_buf_ofs += 1 + bpl;
+ }
+
+ assert(temp_buf_ofs <= temp_buf.size());
+
+ out_buf.resize(out_ofs + 6 + temp_buf_ofs + ((temp_buf_ofs + 65534) / 65535) * 5);
+
+ uint32_t raw_size = write_raw_block(temp_buf.data(), (uint32_t)temp_buf_ofs, out_buf.data() + out_ofs, (uint32_t)out_buf.size() - out_ofs);
+ if (!raw_size)
+ {
+ // Somehow we miscomputed the size of the output buffer.
+ assert(0);
+ return false;
+ }
+
+ zlib_size = raw_size;
+ }
+
+ assert((out_ofs + zlib_size) <= out_buf.size());
+
+ out_buf.resize(out_ofs + zlib_size);
+
+ const uint32_t idat_len = (uint32_t)out_buf.size() - PNG_HEADER_SIZE;
+
+ // Write real PNG header, fdEC chunk, and the beginning of the IDAT chunk
+ {
+ static const uint8_t s_color_type[] = { 0x00, 0x00, 0x04, 0x02, 0x06 };
+
+ uint8_t pnghdr[58] = {
+ 0x89,0x50,0x4e,0x47,0x0d,0x0a,0x1a,0x0a, // PNG sig
+ 0x00,0x00,0x00,0x0d, 'I','H','D','R', // IHDR chunk len, type
+ 0,0,(uint8_t)(w >> 8),(uint8_t)w, // width
+ 0,0,(uint8_t)(h >> 8),(uint8_t)h, // height
+ 8, //bit_depth
+ s_color_type[num_chans], // color_type
+ 0, // compression
+ 0, // filter
+ 0, // interlace
+ 0, 0, 0, 0, // IHDR crc32
+ 0, 0, 0, 5, 'f', 'd', 'E', 'C', 82, 36, 147, 227, FPNG_FDEC_VERSION, 0xE5, 0xAB, 0x62, 0x99, // our custom private, ancillary, do not copy, fdEC chunk
+ (uint8_t)(idat_len >> 24),(uint8_t)(idat_len >> 16),(uint8_t)(idat_len >> 8),(uint8_t)idat_len, 'I','D','A','T' // IDATA chunk len, type
+ };
+
+ // Compute IHDR CRC32
+ uint32_t c = (uint32_t)fpng_crc32(pnghdr + 12, 17, FPNG_CRC32_INIT);
+ for (i = 0; i < 4; ++i, c <<= 8)
+ ((uint8_t*)(pnghdr + 29))[i] = (uint8_t)(c >> 24);
+
+ memcpy(out_buf.data(), pnghdr, PNG_HEADER_SIZE);
+ }
+
+ // Write IDAT chunk's CRC32 and a 0 length IEND chunk
+ vector_append(out_buf, "\0\0\0\0\0\0\0\0\x49\x45\x4e\x44\xae\x42\x60\x82", 16); // IDAT CRC32, followed by the IEND chunk
+
+ // Compute IDAT crc32
+ uint32_t c = (uint32_t)fpng_crc32(out_buf.data() + PNG_HEADER_SIZE - 4, idat_len + 4, FPNG_CRC32_INIT);
+
+ for (i = 0; i < 4; ++i, c <<= 8)
+ (out_buf.data() + out_buf.size() - 16)[i] = (uint8_t)(c >> 24);
+
+ return true;
+ }
+
+#ifndef FPNG_NO_STDIO
+ bool fpng_encode_image_to_file(const char* pFilename, const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, uint32_t flags)
+ {
+ std::vector out_buf;
+ if (!fpng_encode_image_to_memory(pImage, w, h, num_chans, out_buf, flags))
+ return false;
+
+ FILE* pFile = nullptr;
+#ifdef _MSC_VER
+ fopen_s(&pFile, pFilename, "wb");
+#else
+ pFile = fopen(pFilename, "wb");
+#endif
+ if (!pFile)
+ return false;
+
+ if (fwrite(out_buf.data(), 1, out_buf.size(), pFile) != out_buf.size())
+ {
+ fclose(pFile);
+ return false;
+ }
+
+ return (fclose(pFile) != EOF);
+ }
+#endif
+
+ // Decompression
+
+ const uint32_t FPNG_DECODER_TABLE_BITS = 12;
+ const uint32_t FPNG_DECODER_TABLE_SIZE = 1 << FPNG_DECODER_TABLE_BITS;
+
+ static bool build_decoder_table(uint32_t num_syms, uint8_t* pCode_sizes, uint32_t* pTable)
+ {
+ uint32_t num_codes[16];
+
+ memset(num_codes, 0, sizeof(num_codes));
+ for (uint32_t i = 0; i < num_syms; i++)
+ {
+ assert(pCode_sizes[i] <= FPNG_DECODER_TABLE_SIZE);
+ num_codes[pCode_sizes[i]]++;
+ }
+
+ uint32_t next_code[17];
+ next_code[0] = next_code[1] = 0;
+ uint32_t total = 0;
+ for (uint32_t i = 1; i <= 15; i++)
+ next_code[i + 1] = (uint32_t)(total = ((total + ((uint32_t)num_codes[i])) << 1));
+
+ if (total != 0x10000)
+ {
+ uint32_t j = 0;
+
+ for (uint32_t i = 15; i != 0; i--)
+ if ((j += num_codes[i]) > 1)
+ return false;
+
+ if (j != 1)
+ return false;
+ }
+
+ uint32_t rev_codes[DEFL_MAX_HUFF_SYMBOLS];
+
+ for (uint32_t i = 0; i < num_syms; i++)
+ rev_codes[i] = next_code[pCode_sizes[i]]++;
+
+ memset(pTable, 0, sizeof(uint32_t) * FPNG_DECODER_TABLE_SIZE);
+
+ for (uint32_t i = 0; i < num_syms; i++)
+ {
+ const uint32_t code_size = pCode_sizes[i];
+ if (!code_size)
+ continue;
+
+ uint32_t old_code = rev_codes[i], new_code = 0;
+ for (uint32_t j = code_size; j != 0; j--)
+ {
+ new_code = (new_code << 1) | (old_code & 1);
+ old_code >>= 1;
+ }
+
+ uint32_t j = 1 << code_size;
+
+ while (new_code < FPNG_DECODER_TABLE_SIZE)
+ {
+ pTable[new_code] = i | (code_size << 9);
+ new_code += j;
+ }
+ }
+
+ return true;
+ }
+
+ static const uint16_t g_run_len3_to_4[259] =
+ {
+ 0,
+ 0, 0, 4, 0, 0, 8, 0, 0, 12, 0, 0, 16, 0, 0, 20, 0, 0, 24, 0, 0, 28, 0, 0,
+ 32, 0, 0, 36, 0, 0, 40, 0, 0, 44, 0, 0, 48, 0, 0, 52, 0, 0, 56, 0, 0,
+ 60, 0, 0, 64, 0, 0, 68, 0, 0, 72, 0, 0, 76, 0, 0, 80, 0, 0, 84, 0, 0,
+ 88, 0, 0, 92, 0, 0, 96, 0, 0, 100, 0, 0, 104, 0, 0, 108, 0, 0, 112, 0, 0,
+ 116, 0, 0, 120, 0, 0, 124, 0, 0, 128, 0, 0, 132, 0, 0, 136, 0, 0, 140, 0, 0,
+ 144, 0, 0, 148, 0, 0, 152, 0, 0, 156, 0, 0, 160, 0, 0, 164, 0, 0, 168, 0, 0,
+ 172, 0, 0, 176, 0, 0, 180, 0, 0, 184, 0, 0, 188, 0, 0, 192, 0, 0, 196, 0, 0,
+ 200, 0, 0, 204, 0, 0, 208, 0, 0, 212, 0, 0, 216, 0, 0, 220, 0, 0, 224, 0, 0,
+ 228, 0, 0, 232, 0, 0, 236, 0, 0, 240, 0, 0, 244, 0, 0, 248, 0, 0, 252, 0, 0,
+ 256, 0, 0, 260, 0, 0, 264, 0, 0, 268, 0, 0, 272, 0, 0, 276, 0, 0, 280, 0, 0,
+ 284, 0, 0, 288, 0, 0, 292, 0, 0, 296, 0, 0, 300, 0, 0, 304, 0, 0, 308, 0, 0,
+ 312, 0, 0, 316, 0, 0, 320, 0, 0, 324, 0, 0, 328, 0, 0, 332, 0, 0, 336, 0, 0,
+ 340, 0, 0,
+ 344,
+ };
+
+ static const int s_length_extra[] = { 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_length_range[] = { 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 };
+
+#define ENSURE_32BITS() do { \
+ if (bit_buf_size < 32) { \
+ if ((src_ofs + 4) > src_len) return false; \
+ bit_buf |= ((uint64_t)READ_LE32(pSrc + src_ofs)) << bit_buf_size; \
+ src_ofs += 4; bit_buf_size += 32; } \
+ } while(0)
+
+#define GET_BITS(b, ll) do { \
+ uint32_t l = ll; assert(l && (l <= 32)); \
+ b = (uint32_t)(bit_buf & g_bitmasks[l]); \
+ bit_buf >>= l; \
+ bit_buf_size -= l; \
+ ENSURE_32BITS(); \
+ } while(0)
+
+#define SKIP_BITS(ll) do { \
+ uint32_t l = ll; assert(l <= 32); \
+ bit_buf >>= l; \
+ bit_buf_size -= l; \
+ ENSURE_32BITS(); \
+ } while(0)
+
+#define GET_BITS_NE(b, ll) do { \
+ uint32_t l = ll; assert(l && (l <= 32) && (bit_buf_size >= l)); \
+ b = (uint32_t)(bit_buf & g_bitmasks[l]); \
+ bit_buf >>= l; \
+ bit_buf_size -= l; \
+ } while(0)
+
+#define SKIP_BITS_NE(ll) do { \
+ uint32_t l = ll; assert(l <= 32 && (bit_buf_size >= l)); \
+ bit_buf >>= l; \
+ bit_buf_size -= l; \
+ } while(0)
+
+ static bool prepare_dynamic_block(
+ const uint8_t* pSrc, uint32_t src_len, uint32_t& src_ofs,
+ uint32_t& bit_buf_size, uint64_t& bit_buf,
+ uint32_t* pLit_table, uint32_t num_chans)
+ {
+ static const uint8_t s_bit_length_order[] = { 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 };
+
+ uint32_t num_lit_codes, num_dist_codes, num_clen_codes;
+
+ GET_BITS(num_lit_codes, 5);
+ num_lit_codes += 257;
+
+ GET_BITS(num_dist_codes, 5);
+ num_dist_codes += 1;
+
+ uint32_t total_codes = num_lit_codes + num_dist_codes;
+ if (total_codes > (DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1))
+ return false;
+
+ uint8_t code_sizes[DEFL_MAX_HUFF_SYMBOLS_0 + DEFL_MAX_HUFF_SYMBOLS_1];
+ memset(code_sizes, 0, sizeof(code_sizes));
+
+ GET_BITS(num_clen_codes, 4);
+ num_clen_codes += 4;
+
+ uint8_t clen_codesizes[DEFL_MAX_HUFF_SYMBOLS_2];
+ memset(clen_codesizes, 0, sizeof(clen_codesizes));
+
+ for (uint32_t i = 0; i < num_clen_codes; i++)
+ {
+ uint32_t len = 0;
+ GET_BITS(len, 3);
+ clen_codesizes[s_bit_length_order[i]] = (uint8_t)len;
+ }
+
+ uint32_t clen_table[FPNG_DECODER_TABLE_SIZE];
+ if (!build_decoder_table(DEFL_MAX_HUFF_SYMBOLS_2, clen_codesizes, clen_table))
+ return false;
+
+ uint32_t min_code_size = 15;
+
+ for (uint32_t cur_code = 0; cur_code < total_codes; )
+ {
+ uint32_t sym = clen_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ uint32_t sym_len = sym >> 9;
+ if (!sym_len)
+ return false;
+ SKIP_BITS(sym_len);
+ sym &= 511;
+
+ if (sym <= 15)
+ {
+ // Can't be a fpng Huffman table
+ if (sym > FPNG_DECODER_TABLE_BITS)
+ return false;
+
+ if (sym)
+ min_code_size = minimum(min_code_size, sym);
+
+ code_sizes[cur_code++] = (uint8_t)sym;
+ continue;
+ }
+
+ uint32_t rep_len = 0, rep_code_size = 0;
+
+ switch (sym)
+ {
+ case 16:
+ {
+ GET_BITS(rep_len, 2);
+ rep_len += 3;
+ if (!cur_code)
+ return false;
+ rep_code_size = code_sizes[cur_code - 1];
+ break;
+ }
+ case 17:
+ {
+ GET_BITS(rep_len, 3);
+ rep_len += 3;
+ rep_code_size = 0;
+ break;
+ }
+ case 18:
+ {
+ GET_BITS(rep_len, 7);
+ rep_len += 11;
+ rep_code_size = 0;
+ break;
+ }
+ }
+
+ if ((cur_code + rep_len) > total_codes)
+ return false;
+
+ for (; rep_len; rep_len--)
+ code_sizes[cur_code++] = (uint8_t)rep_code_size;
+ }
+
+ uint8_t lit_codesizes[DEFL_MAX_HUFF_SYMBOLS_0];
+
+ memcpy(lit_codesizes, code_sizes, num_lit_codes);
+ memset(lit_codesizes + num_lit_codes, 0, DEFL_MAX_HUFF_SYMBOLS_0 - num_lit_codes);
+
+ uint32_t total_valid_distcodes = 0;
+ for (uint32_t i = 0; i < num_dist_codes; i++)
+ total_valid_distcodes += (code_sizes[num_lit_codes + i] == 1);
+
+ // 1 or 2 because the first version of FPNG only issued 1 valid distance code, but that upset wuffs. So we let 1 or 2 through.
+ if ((total_valid_distcodes < 1) || (total_valid_distcodes > 2))
+ return false;
+
+ if (code_sizes[num_lit_codes + (num_chans - 1)] != 1)
+ return false;
+
+ if (total_valid_distcodes == 2)
+ {
+ // If there are two valid distance codes, make sure the first is 1 bit.
+ if (code_sizes[num_lit_codes + num_chans] != 1)
+ return false;
+ }
+
+ if (!build_decoder_table(num_lit_codes, lit_codesizes, pLit_table))
+ return false;
+
+ // Add next symbol to decoder table, when it fits
+ for (uint32_t i = 0; i < FPNG_DECODER_TABLE_SIZE; i++)
+ {
+ uint32_t sym = pLit_table[i] & 511;
+ if (sym >= 256)
+ continue;
+
+ uint32_t sym_bits = (pLit_table[i] >> 9) & 15;
+ if (!sym_bits)
+ continue;
+ assert(sym_bits <= FPNG_DECODER_TABLE_BITS);
+
+ uint32_t bits_left = FPNG_DECODER_TABLE_BITS - sym_bits;
+ if (bits_left < min_code_size)
+ continue;
+
+ uint32_t next_bits = i >> sym_bits;
+ uint32_t next_sym = pLit_table[next_bits] & 511;
+ uint32_t next_sym_bits = (pLit_table[next_bits] >> 9) & 15;
+ if ((!next_sym_bits) || (bits_left < next_sym_bits))
+ continue;
+
+ pLit_table[i] |= (next_sym << 16) | (next_sym_bits << (16 + 9));
+ }
+
+ return true;
+ }
+
+ static bool fpng_pixel_zlib_raw_decompress(
+ const uint8_t* pSrc, uint32_t src_len, uint32_t zlib_len,
+ uint8_t* pDst, uint32_t w, uint32_t h,
+ uint32_t src_chans, uint32_t dst_chans)
+ {
+ assert((src_chans == 3) || (src_chans == 4));
+ assert((dst_chans == 3) || (dst_chans == 4));
+
+ const uint32_t src_bpl = w * src_chans;
+ const uint32_t dst_bpl = w * dst_chans;
+ const uint32_t dst_len = dst_bpl * h;
+
+ uint32_t src_ofs = 2;
+ uint32_t dst_ofs = 0;
+ uint32_t raster_ofs = 0;
+ uint32_t comp_ofs = 0;
+
+ for (; ; )
+ {
+ if ((src_ofs + 1) > src_len)
+ return false;
+
+ const bool bfinal = (pSrc[src_ofs] & 1) != 0;
+ const uint32_t btype = (pSrc[src_ofs] >> 1) & 3;
+ if (btype != 0)
+ return false;
+
+ src_ofs++;
+
+ if ((src_ofs + 4) > src_len)
+ return false;
+ uint32_t len = pSrc[src_ofs + 0] | (pSrc[src_ofs + 1] << 8);
+ uint32_t nlen = pSrc[src_ofs + 2] | (pSrc[src_ofs + 3] << 8);
+ src_ofs += 4;
+
+ if (len != (~nlen & 0xFFFF))
+ return false;
+
+ if ((src_ofs + len) > src_len)
+ return false;
+
+ // Raw blocks are a relatively uncommon case so this isn't well optimized.
+ // Supports 3->4 and 4->3 byte/pixel conversion.
+ for (uint32_t i = 0; i < len; i++)
+ {
+ uint32_t c = pSrc[src_ofs + i];
+
+ if (!raster_ofs)
+ {
+ // Check filter type
+ if (c != 0)
+ return false;
+
+ assert(!comp_ofs);
+ }
+ else
+ {
+ if (comp_ofs < dst_chans)
+ {
+ if (dst_ofs == dst_len)
+ return false;
+
+ pDst[dst_ofs++] = (uint8_t)c;
+ }
+
+ if (++comp_ofs == src_chans)
+ {
+ if (dst_chans > src_chans)
+ {
+ if (dst_ofs == dst_len)
+ return false;
+
+ pDst[dst_ofs++] = (uint8_t)0xFF;
+ }
+
+ comp_ofs = 0;
+ }
+ }
+
+ if (++raster_ofs == (src_bpl + 1))
+ {
+ assert(!comp_ofs);
+ raster_ofs = 0;
+ }
+ }
+
+ src_ofs += len;
+
+ if (bfinal)
+ break;
+ }
+
+ if (comp_ofs != 0)
+ return false;
+
+ // Check for zlib adler32
+ if ((src_ofs + 4) != zlib_len)
+ return false;
+
+ return (dst_ofs == dst_len);
+ }
+
+ template
+ static bool fpng_pixel_zlib_decompress_3(
+ const uint8_t* pSrc, uint32_t src_len, uint32_t zlib_len,
+ uint8_t* pDst, uint32_t w, uint32_t h)
+ {
+ assert(src_len >= (zlib_len + 4));
+
+ const uint32_t dst_bpl = w * dst_comps;
+ //const uint32_t dst_len = dst_bpl * h;
+
+ if (zlib_len < 7)
+ return false;
+
+ // check zlib header
+ if ((pSrc[0] != 0x78) || (pSrc[1] != 0x01))
+ return false;
+
+ uint32_t src_ofs = 2;
+
+ if ((pSrc[src_ofs] & 6) == 0)
+ return fpng_pixel_zlib_raw_decompress(pSrc, src_len, zlib_len, pDst, w, h, 3, dst_comps);
+
+ if ((src_ofs + 4) > src_len)
+ return false;
+ uint64_t bit_buf = READ_LE32(pSrc + src_ofs);
+ src_ofs += 4;
+
+ uint32_t bit_buf_size = 32;
+
+ uint32_t bfinal, btype;
+ GET_BITS(bfinal, 1);
+ GET_BITS(btype, 2);
+
+ // Must be the final block or it's not valid, and type=2 (dynamic)
+ if ((bfinal != 1) || (btype != 2))
+ return false;
+
+ uint32_t lit_table[FPNG_DECODER_TABLE_SIZE];
+ if (!prepare_dynamic_block(pSrc, src_len, src_ofs, bit_buf_size, bit_buf, lit_table, 3))
+ return false;
+
+ const uint8_t* pPrev_scanline = nullptr;
+ uint8_t* pCur_scanline = pDst;
+
+ for (uint32_t y = 0; y < h; y++)
+ {
+ // At start of PNG scanline, so read the filter literal
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ uint32_t filter = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ uint32_t filter_len = (filter >> 9) & 15;
+ if (!filter_len)
+ return false;
+ SKIP_BITS(filter_len);
+ filter &= 511;
+
+ uint32_t expected_filter = (y ? 2 : 0);
+ if (filter != expected_filter)
+ return false;
+
+ uint32_t x_ofs = 0;
+ uint8_t prev_delta_r = 0, prev_delta_g = 0, prev_delta_b = 0;
+ do
+ {
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ uint32_t lit0_tab = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+
+ uint32_t lit0 = lit0_tab;
+ uint32_t lit0_len = (lit0_tab >> 9) & 15;
+ if (!lit0_len)
+ return false;
+ SKIP_BITS(lit0_len);
+
+ if (lit0 & 256)
+ {
+ lit0 &= 511;
+
+ // Can't be EOB - we still have more pixels to decompress.
+ if (lit0 == 256)
+ return false;
+
+ // Must be an RLE match against the previous pixel.
+ uint32_t run_len = s_length_range[lit0 - 257];
+ if (lit0 >= 265)
+ {
+ uint32_t e;
+ GET_BITS_NE(e, s_length_extra[lit0 - 257]);
+
+ run_len += e;
+ }
+
+ // Skip match distance - it's always the same (3)
+ SKIP_BITS_NE(1);
+
+ // Matches must always be a multiple of 3/4 bytes
+ assert((run_len % 3) == 0);
+
+ if (dst_comps == 4)
+ {
+ const uint32_t x_ofs_end = x_ofs + g_run_len3_to_4[run_len];
+
+ // Check for valid run lengths
+ if (x_ofs == x_ofs_end)
+ return false;
+
+ // Matches cannot cross scanlines.
+ if (x_ofs_end > dst_bpl)
+ return false;
+
+ if (pPrev_scanline)
+ {
+ if ((prev_delta_r | prev_delta_g | prev_delta_b) == 0)
+ {
+ memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, x_ofs_end - x_ofs);
+ x_ofs = x_ofs_end;
+ }
+ else
+ {
+ do
+ {
+ pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r);
+ pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g);
+ pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b);
+ pCur_scanline[x_ofs + 3] = 0xFF;
+ x_ofs += 4;
+ } while (x_ofs < x_ofs_end);
+ }
+ }
+ else
+ {
+ do
+ {
+ pCur_scanline[x_ofs] = prev_delta_r;
+ pCur_scanline[x_ofs + 1] = prev_delta_g;
+ pCur_scanline[x_ofs + 2] = prev_delta_b;
+ pCur_scanline[x_ofs + 3] = 0xFF;
+ x_ofs += 4;
+ } while (x_ofs < x_ofs_end);
+ }
+ }
+ else
+ {
+ // Check for valid run lengths
+ if (!g_run_len3_to_4[run_len])
+ return false;
+
+ const uint32_t x_ofs_end = x_ofs + run_len;
+
+ // Matches cannot cross scanlines.
+ if (x_ofs_end > dst_bpl)
+ return false;
+
+ if (pPrev_scanline)
+ {
+ if ((prev_delta_r | prev_delta_g | prev_delta_b) == 0)
+ {
+ memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, run_len);
+ x_ofs = x_ofs_end;
+ }
+ else
+ {
+ do
+ {
+ pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r);
+ pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g);
+ pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b);
+ x_ofs += 3;
+ } while (x_ofs < x_ofs_end);
+ }
+ }
+ else
+ {
+ do
+ {
+ pCur_scanline[x_ofs] = prev_delta_r;
+ pCur_scanline[x_ofs + 1] = prev_delta_g;
+ pCur_scanline[x_ofs + 2] = prev_delta_b;
+ x_ofs += 3;
+ } while (x_ofs < x_ofs_end);
+ }
+ }
+ }
+ else
+ {
+ uint32_t lit1, lit2;
+
+ uint32_t lit1_spec_len = (lit0_tab >> (16 + 9));
+ uint32_t lit2_len;
+ if (lit1_spec_len)
+ {
+ lit1 = (lit0_tab >> 16) & 511;
+ SKIP_BITS_NE(lit1_spec_len);
+
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ lit2_len = (lit2 >> 9) & 15;
+ if (!lit2_len)
+ return false;
+ }
+ else
+ {
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ lit1 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ uint32_t lit1_len = (lit1 >> 9) & 15;
+ if (!lit1_len)
+ return false;
+ SKIP_BITS_NE(lit1_len);
+
+ lit2_len = (lit1 >> (16 + 9));
+ if (lit2_len)
+ lit2 = lit1 >> 16;
+ else
+ {
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ lit2_len = (lit2 >> 9) & 15;
+ if (!lit2_len)
+ return false;
+ }
+ }
+
+ SKIP_BITS(lit2_len);
+
+ // Check for matches
+ if ((lit1 | lit2) & 256)
+ return false;
+
+ if (dst_comps == 4)
+ {
+ if (pPrev_scanline)
+ {
+ pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
+ pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
+ pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
+ pCur_scanline[x_ofs + 3] = 0xFF;
+ }
+ else
+ {
+ pCur_scanline[x_ofs] = (uint8_t)lit0;
+ pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
+ pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
+ pCur_scanline[x_ofs + 3] = 0xFF;
+ }
+ x_ofs += 4;
+ }
+ else
+ {
+ if (pPrev_scanline)
+ {
+ pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
+ pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
+ pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
+ }
+ else
+ {
+ pCur_scanline[x_ofs] = (uint8_t)lit0;
+ pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
+ pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
+ }
+ x_ofs += 3;
+ }
+
+ prev_delta_r = (uint8_t)lit0;
+ prev_delta_g = (uint8_t)lit1;
+ prev_delta_b = (uint8_t)lit2;
+
+ // See if we can decode one more pixel.
+ uint32_t spec_next_len0_len = lit2 >> (16 + 9);
+ if ((spec_next_len0_len) && (x_ofs < dst_bpl))
+ {
+ lit0 = (lit2 >> 16) & 511;
+ if (lit0 < 256)
+ {
+ SKIP_BITS_NE(spec_next_len0_len);
+
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ lit1 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ uint32_t lit1_len = (lit1 >> 9) & 15;
+ if (!lit1_len)
+ return false;
+ SKIP_BITS(lit1_len);
+
+ lit2_len = (lit1 >> (16 + 9));
+ if (lit2_len)
+ lit2 = lit1 >> 16;
+ else
+ {
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ lit2_len = (lit2 >> 9) & 15;
+ if (!lit2_len)
+ return false;
+ }
+
+ SKIP_BITS_NE(lit2_len);
+
+ // Check for matches
+ if ((lit1 | lit2) & 256)
+ return false;
+
+ if (dst_comps == 4)
+ {
+ if (pPrev_scanline)
+ {
+ pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
+ pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
+ pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
+ pCur_scanline[x_ofs + 3] = 0xFF;
+ }
+ else
+ {
+ pCur_scanline[x_ofs] = (uint8_t)lit0;
+ pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
+ pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
+ pCur_scanline[x_ofs + 3] = 0xFF;
+ }
+ x_ofs += 4;
+ }
+ else
+ {
+ if (pPrev_scanline)
+ {
+ pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
+ pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
+ pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
+ }
+ else
+ {
+ pCur_scanline[x_ofs] = (uint8_t)lit0;
+ pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
+ pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
+ }
+ x_ofs += 3;
+ }
+
+ prev_delta_r = (uint8_t)lit0;
+ prev_delta_g = (uint8_t)lit1;
+ prev_delta_b = (uint8_t)lit2;
+
+ } // if (lit0 < 256)
+
+ } // if ((spec_next_len0_len) && (x_ofs < bpl))
+ }
+
+ } while (x_ofs < dst_bpl);
+
+ pPrev_scanline = pCur_scanline;
+ pCur_scanline += dst_bpl;
+
+ } // y
+
+ // The last symbol should be EOB
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ uint32_t lit0 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ uint32_t lit0_len = (lit0 >> 9) & 15;
+ if (!lit0_len)
+ return false;
+ lit0 &= 511;
+ if (lit0 != 256)
+ return false;
+
+ bit_buf_size -= lit0_len;
+ bit_buf >>= lit0_len;
+
+ uint32_t align_bits = bit_buf_size & 7;
+ bit_buf_size -= align_bits;
+ bit_buf >>= align_bits;
+
+ if (src_ofs < (bit_buf_size >> 3))
+ return false;
+ src_ofs -= (bit_buf_size >> 3);
+
+ // We should be at the very end, because the bit buf reads ahead 32-bits (which contains the zlib adler32).
+ if ((src_ofs + 4) != zlib_len)
+ return false;
+
+ return true;
+ }
+
+ template
+ static bool fpng_pixel_zlib_decompress_4(
+ const uint8_t* pSrc, uint32_t src_len, uint32_t zlib_len,
+ uint8_t* pDst, uint32_t w, uint32_t h)
+ {
+ assert(src_len >= (zlib_len + 4));
+
+ const uint32_t dst_bpl = w * dst_comps;
+ //const uint32_t dst_len = dst_bpl * h;
+
+ if (zlib_len < 7)
+ return false;
+
+ // check zlib header
+ if ((pSrc[0] != 0x78) || (pSrc[1] != 0x01))
+ return false;
+
+ uint32_t src_ofs = 2;
+
+ if ((pSrc[src_ofs] & 6) == 0)
+ return fpng_pixel_zlib_raw_decompress(pSrc, src_len, zlib_len, pDst, w, h, 4, dst_comps);
+
+ if ((src_ofs + 4) > src_len)
+ return false;
+ uint64_t bit_buf = READ_LE32(pSrc + src_ofs);
+ src_ofs += 4;
+
+ uint32_t bit_buf_size = 32;
+
+ uint32_t bfinal, btype;
+ GET_BITS(bfinal, 1);
+ GET_BITS(btype, 2);
+
+ // Must be the final block or it's not valid, and type=2 (dynamic)
+ if ((bfinal != 1) || (btype != 2))
+ return false;
+
+ uint32_t lit_table[FPNG_DECODER_TABLE_SIZE];
+ if (!prepare_dynamic_block(pSrc, src_len, src_ofs, bit_buf_size, bit_buf, lit_table, 4))
+ return false;
+
+ const uint8_t* pPrev_scanline = nullptr;
+ uint8_t* pCur_scanline = pDst;
+
+ for (uint32_t y = 0; y < h; y++)
+ {
+ // At start of PNG scanline, so read the filter literal
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ uint32_t filter = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ uint32_t filter_len = (filter >> 9) & 15;
+ if (!filter_len)
+ return false;
+ SKIP_BITS(filter_len);
+ filter &= 511;
+
+ uint32_t expected_filter = (y ? 2 : 0);
+ if (filter != expected_filter)
+ return false;
+
+ uint32_t x_ofs = 0;
+ uint8_t prev_delta_r = 0, prev_delta_g = 0, prev_delta_b = 0, prev_delta_a = 0;
+ do
+ {
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ uint32_t lit0_tab = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+
+ uint32_t lit0 = lit0_tab;
+ uint32_t lit0_len = (lit0_tab >> 9) & 15;
+ if (!lit0_len)
+ return false;
+ SKIP_BITS(lit0_len);
+
+ if (lit0 & 256)
+ {
+ lit0 &= 511;
+
+ // Can't be EOB - we still have more pixels to decompress.
+ if (lit0 == 256)
+ return false;
+
+ // Must be an RLE match against the previous pixel.
+ uint32_t run_len = s_length_range[lit0 - 257];
+ if (lit0 >= 265)
+ {
+ uint32_t e;
+ GET_BITS_NE(e, s_length_extra[lit0 - 257]);
+
+ run_len += e;
+ }
+
+ // Skip match distance - it's always the same (4)
+ SKIP_BITS_NE(1);
+
+ // Matches must always be a multiple of 3/4 bytes
+ if (run_len & 3)
+ return false;
+
+ if (dst_comps == 3)
+ {
+ const uint32_t run_len3 = (run_len >> 2) * 3;
+ const uint32_t x_ofs_end = x_ofs + run_len3;
+
+ // Matches cannot cross scanlines.
+ if (x_ofs_end > dst_bpl)
+ return false;
+
+ if (pPrev_scanline)
+ {
+ if ((prev_delta_r | prev_delta_g | prev_delta_b | prev_delta_a) == 0)
+ {
+ memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, run_len3);
+ x_ofs = x_ofs_end;
+ }
+ else
+ {
+ do
+ {
+ pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r);
+ pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g);
+ pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b);
+ x_ofs += 3;
+ } while (x_ofs < x_ofs_end);
+ }
+ }
+ else
+ {
+ do
+ {
+ pCur_scanline[x_ofs] = prev_delta_r;
+ pCur_scanline[x_ofs + 1] = prev_delta_g;
+ pCur_scanline[x_ofs + 2] = prev_delta_b;
+ x_ofs += 3;
+ } while (x_ofs < x_ofs_end);
+ }
+ }
+ else
+ {
+ const uint32_t x_ofs_end = x_ofs + run_len;
+
+ // Matches cannot cross scanlines.
+ if (x_ofs_end > dst_bpl)
+ return false;
+
+ if (pPrev_scanline)
+ {
+ if ((prev_delta_r | prev_delta_g | prev_delta_b | prev_delta_a) == 0)
+ {
+ memcpy(pCur_scanline + x_ofs, pPrev_scanline + x_ofs, run_len);
+ x_ofs = x_ofs_end;
+ }
+ else
+ {
+ do
+ {
+ pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + prev_delta_r);
+ pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + prev_delta_g);
+ pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + prev_delta_b);
+ pCur_scanline[x_ofs + 3] = (uint8_t)(pPrev_scanline[x_ofs + 3] + prev_delta_a);
+ x_ofs += 4;
+ } while (x_ofs < x_ofs_end);
+ }
+ }
+ else
+ {
+ do
+ {
+ pCur_scanline[x_ofs] = prev_delta_r;
+ pCur_scanline[x_ofs + 1] = prev_delta_g;
+ pCur_scanline[x_ofs + 2] = prev_delta_b;
+ pCur_scanline[x_ofs + 3] = prev_delta_a;
+ x_ofs += 4;
+ } while (x_ofs < x_ofs_end);
+ }
+ }
+ }
+ else
+ {
+ uint32_t lit1, lit2;
+
+ uint32_t lit1_spec_len = (lit0_tab >> (16 + 9));
+ uint32_t lit2_len;
+ if (lit1_spec_len)
+ {
+ lit1 = (lit0_tab >> 16) & 511;
+ SKIP_BITS_NE(lit1_spec_len);
+
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ lit2_len = (lit2 >> 9) & 15;
+ if (!lit2_len)
+ return false;
+ }
+ else
+ {
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ lit1 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ uint32_t lit1_len = (lit1 >> 9) & 15;
+ if (!lit1_len)
+ return false;
+ SKIP_BITS_NE(lit1_len);
+
+ lit2_len = (lit1 >> (16 + 9));
+ if (lit2_len)
+ lit2 = lit1 >> 16;
+ else
+ {
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ lit2 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ lit2_len = (lit2 >> 9) & 15;
+ if (!lit2_len)
+ return false;
+ }
+ }
+
+ uint32_t lit3;
+ uint32_t lit3_len = lit2 >> (16 + 9);
+
+ if (lit3_len)
+ {
+ lit3 = (lit2 >> 16);
+ SKIP_BITS(lit2_len + lit3_len);
+ }
+ else
+ {
+ SKIP_BITS(lit2_len);
+
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ lit3 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ lit3_len = (lit3 >> 9) & 15;
+ if (!lit3_len)
+ return false;
+
+ SKIP_BITS_NE(lit3_len);
+ }
+
+ // Check for matches
+ if ((lit1 | lit2 | lit3) & 256)
+ return false;
+
+ if (dst_comps == 3)
+ {
+ if (pPrev_scanline)
+ {
+ pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
+ pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
+ pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
+ }
+ else
+ {
+ pCur_scanline[x_ofs] = (uint8_t)lit0;
+ pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
+ pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
+ }
+
+ x_ofs += 3;
+ }
+ else
+ {
+ if (pPrev_scanline)
+ {
+ pCur_scanline[x_ofs] = (uint8_t)(pPrev_scanline[x_ofs] + lit0);
+ pCur_scanline[x_ofs + 1] = (uint8_t)(pPrev_scanline[x_ofs + 1] + lit1);
+ pCur_scanline[x_ofs + 2] = (uint8_t)(pPrev_scanline[x_ofs + 2] + lit2);
+ pCur_scanline[x_ofs + 3] = (uint8_t)(pPrev_scanline[x_ofs + 3] + lit3);
+ }
+ else
+ {
+ pCur_scanline[x_ofs] = (uint8_t)lit0;
+ pCur_scanline[x_ofs + 1] = (uint8_t)lit1;
+ pCur_scanline[x_ofs + 2] = (uint8_t)lit2;
+ pCur_scanline[x_ofs + 3] = (uint8_t)lit3;
+ }
+
+ x_ofs += 4;
+ }
+
+ prev_delta_r = (uint8_t)lit0;
+ prev_delta_g = (uint8_t)lit1;
+ prev_delta_b = (uint8_t)lit2;
+ prev_delta_a = (uint8_t)lit3;
+ }
+
+ } while (x_ofs < dst_bpl);
+
+ pPrev_scanline = pCur_scanline;
+ pCur_scanline += dst_bpl;
+ } // y
+
+ // The last symbol should be EOB
+ assert(bit_buf_size >= FPNG_DECODER_TABLE_BITS);
+ uint32_t lit0 = lit_table[bit_buf & (FPNG_DECODER_TABLE_SIZE - 1)];
+ uint32_t lit0_len = (lit0 >> 9) & 15;
+ if (!lit0_len)
+ return false;
+ lit0 &= 511;
+ if (lit0 != 256)
+ return false;
+
+ bit_buf_size -= lit0_len;
+ bit_buf >>= lit0_len;
+
+ uint32_t align_bits = bit_buf_size & 7;
+ bit_buf_size -= align_bits;
+ bit_buf >>= align_bits;
+
+ if (src_ofs < (bit_buf_size >> 3))
+ return false;
+ src_ofs -= (bit_buf_size >> 3);
+
+ // We should be at the very end, because the bit buf reads ahead 32-bits (which contains the zlib adler32).
+ if ((src_ofs + 4) != zlib_len)
+ return false;
+
+ return true;
+ }
+
+#pragma pack(push)
+#pragma pack(1)
+ struct png_chunk_prefix
+ {
+ uint32_t m_length;
+ uint8_t m_type[4];
+ };
+ struct png_ihdr
+ {
+ png_chunk_prefix m_prefix;
+ uint32_t m_width;
+ uint32_t m_height;
+ uint8_t m_bitdepth;
+ uint8_t m_color_type;
+ uint8_t m_comp_method;
+ uint8_t m_filter_method;
+ uint8_t m_interlace_method;
+ uint32_t m_crc32;
+ };
+ const uint32_t IHDR_EXPECTED_LENGTH = 13;
+ struct png_iend
+ {
+ png_chunk_prefix m_prefix;
+ uint32_t m_crc32;
+ };
+#pragma pack(pop)
+
+ static int fpng_get_info_internal(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t &idat_ofs, uint32_t &idat_len)
+ {
+ static const uint8_t s_png_sig[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
+
+ if (!endian_check())
+ {
+ assert(0);
+ return false;
+ }
+
+ width = 0;
+ height = 0;
+ channels_in_file = 0;
+ idat_ofs = 0, idat_len = 0;
+
+ // Ensure the file has at least a minimum possible size
+ if (image_size < (sizeof(s_png_sig) + sizeof(png_ihdr) + sizeof(png_chunk_prefix) + 1 + sizeof(uint32_t) + sizeof(png_iend)))
+ return FPNG_DECODE_FAILED_NOT_PNG;
+
+ if (memcmp(pImage, s_png_sig, 8) != 0)
+ return FPNG_DECODE_FAILED_NOT_PNG;
+
+ const uint8_t* pImage_u8 = static_cast(pImage) + 8;
+
+ const png_ihdr& ihdr = *reinterpret_cast(pImage_u8);
+ pImage_u8 += sizeof(png_ihdr);
+
+ if (READ_BE32(&ihdr.m_prefix.m_length) != IHDR_EXPECTED_LENGTH)
+ return FPNG_DECODE_FAILED_NOT_PNG;
+
+ if (fpng_crc32(ihdr.m_prefix.m_type, 4 + IHDR_EXPECTED_LENGTH, FPNG_CRC32_INIT) != READ_BE32(&ihdr.m_crc32))
+ return FPNG_DECODE_FAILED_HEADER_CRC32;
+
+ width = READ_BE32(&ihdr.m_width);
+ height = READ_BE32(&ihdr.m_height);
+
+ if (!width || !height || (width > FPNG_MAX_SUPPORTED_DIM) || (height > FPNG_MAX_SUPPORTED_DIM))
+ return FPNG_DECODE_FAILED_INVALID_DIMENSIONS;
+
+ uint64_t total_pixels = (uint64_t)width * height;
+ if (total_pixels > (1 << 30))
+ return FPNG_DECODE_FAILED_INVALID_DIMENSIONS;
+
+ if ((ihdr.m_comp_method) || (ihdr.m_filter_method) || (ihdr.m_interlace_method) || (ihdr.m_bitdepth != 8))
+ return FPNG_DECODE_NOT_FPNG;
+
+ if (ihdr.m_color_type == 2)
+ channels_in_file = 3;
+ else if (ihdr.m_color_type == 6)
+ channels_in_file = 4;
+
+ if (!channels_in_file)
+ return FPNG_DECODE_NOT_FPNG;
+
+ // Scan all the chunks. Look for one IDAT, IEND, and our custom fdEC chunk that indicates the file was compressed by us. Skip any ancillary chunks.
+ bool found_fdec_chunk = false;
+
+ for (; ; )
+ {
+ const size_t src_ofs = pImage_u8 - static_cast(pImage);
+ if (src_ofs >= image_size)
+ return FPNG_DECODE_FAILED_CHUNK_PARSING;
+
+ const uint32_t bytes_remaining = image_size - (uint32_t)src_ofs;
+ if (bytes_remaining < sizeof(uint32_t) * 3)
+ return FPNG_DECODE_FAILED_CHUNK_PARSING;
+
+ const png_chunk_prefix* pChunk = reinterpret_cast(pImage_u8);
+
+ const uint32_t chunk_len = READ_BE32(&pChunk->m_length);
+ if ((src_ofs + sizeof(uint32_t) + chunk_len + sizeof(uint32_t)) > image_size)
+ return FPNG_DECODE_FAILED_CHUNK_PARSING;
+
+ for (uint32_t i = 0; i < 4; i++)
+ {
+ const uint8_t c = pChunk->m_type[i];
+ const bool is_upper = (c >= 65) && (c <= 90), is_lower = (c >= 97) && (c <= 122);
+ if ((!is_upper) && (!is_lower))
+ return FPNG_DECODE_FAILED_CHUNK_PARSING;
+ }
+
+ const uint32_t expected_crc32 = READ_BE32(pImage_u8 + sizeof(uint32_t) * 2 + chunk_len);
+
+ char chunk_type[5] = { (char)pChunk->m_type[0], (char)pChunk->m_type[1], (char)pChunk->m_type[2], (char)pChunk->m_type[3], 0 };
+ const bool is_idat = strcmp(chunk_type, "IDAT") == 0;
+
+#if !FPNG_DISABLE_DECODE_CRC32_CHECKS
+ if (!is_idat)
+ {
+ uint32_t actual_crc32 = fpng_crc32(pImage_u8 + sizeof(uint32_t), sizeof(uint32_t) + chunk_len, FPNG_CRC32_INIT);
+ if (actual_crc32 != expected_crc32)
+ return FPNG_DECODE_FAILED_HEADER_CRC32;
+ }
+#endif
+
+ const uint8_t* pChunk_data = pImage_u8 + sizeof(uint32_t) * 2;
+
+ if (strcmp(chunk_type, "IEND") == 0)
+ break;
+ else if (is_idat)
+ {
+ // If there were multiple IDAT's, or we didn't find the fdEC chunk, then it's not FPNG.
+ if ((idat_ofs) || (!found_fdec_chunk))
+ return FPNG_DECODE_NOT_FPNG;
+
+ idat_ofs = (uint32_t)src_ofs;
+ idat_len = chunk_len;
+
+ // Sanity check the IDAT chunk length
+ if (idat_len < 7)
+ return FPNG_DECODE_FAILED_INVALID_IDAT;
+ }
+ else if (strcmp(chunk_type, "fdEC") == 0)
+ {
+ if (found_fdec_chunk)
+ return FPNG_DECODE_NOT_FPNG;
+
+ // We've got our fdEC chunk. Now make sure it's big enough and check its contents.
+ if (chunk_len != 5)
+ return FPNG_DECODE_NOT_FPNG;
+
+ // Check fdEC chunk sig
+ if ((pChunk_data[0] != 82) || (pChunk_data[1] != 36) || (pChunk_data[2] != 147) || (pChunk_data[3] != 227))
+ return FPNG_DECODE_NOT_FPNG;
+
+ // Check fdEC version
+ if (pChunk_data[4] != FPNG_FDEC_VERSION)
+ return FPNG_DECODE_NOT_FPNG;
+
+ found_fdec_chunk = true;
+ }
+ else
+ {
+ // Bail if it's a critical chunk - can't be FPNG
+ if ((chunk_type[0] & 32) == 0)
+ return FPNG_DECODE_NOT_FPNG;
+
+ // ancillary chunk - skip it
+ }
+
+ pImage_u8 += sizeof(png_chunk_prefix) + chunk_len + sizeof(uint32_t);
+ }
+
+ if ((!found_fdec_chunk) || (!idat_ofs))
+ return FPNG_DECODE_NOT_FPNG;
+
+ return FPNG_DECODE_SUCCESS;
+ }
+
+ int fpng_get_info(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file)
+ {
+ uint32_t idat_ofs = 0, idat_len = 0;
+ return fpng_get_info_internal(pImage, image_size, width, height, channels_in_file, idat_ofs, idat_len);
+ }
+
+ int fpng_decode_memory(const void *pImage, uint32_t image_size, std::vector &out, uint32_t& width, uint32_t& height, uint32_t &channels_in_file, uint32_t desired_channels)
+ {
+ out.resize(0);
+ width = 0;
+ height = 0;
+ channels_in_file = 0;
+
+ if ((!pImage) || (!image_size) || ((desired_channels != 3) && (desired_channels != 4)))
+ {
+ assert(0);
+ return FPNG_DECODE_INVALID_ARG;
+ }
+
+ uint32_t idat_ofs = 0, idat_len = 0;
+ int status = fpng_get_info_internal(pImage, image_size, width, height, channels_in_file, idat_ofs, idat_len);
+ if (status)
+ return status;
+
+ const uint64_t mem_needed = (uint64_t)width * height * desired_channels;
+ if (mem_needed > UINT32_MAX)
+ return FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE;
+
+ // On 32-bit systems do a quick sanity check before we try to resize the output buffer.
+ if ((sizeof(size_t) == sizeof(uint32_t)) && (mem_needed >= 0x80000000))
+ return FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE;
+
+ out.resize(mem_needed);
+
+ const uint8_t* pIDAT_data = static_cast(pImage) + idat_ofs + sizeof(uint32_t) * 2;
+ const uint32_t src_len = image_size - (idat_ofs + sizeof(uint32_t) * 2);
+
+ bool decomp_status;
+ if (desired_channels == 3)
+ {
+ if (channels_in_file == 3)
+ decomp_status = fpng_pixel_zlib_decompress_3<3>(pIDAT_data, src_len, idat_len, out.data(), width, height);
+ else
+ decomp_status = fpng_pixel_zlib_decompress_4<3>(pIDAT_data, src_len, idat_len, out.data(), width, height);
+ }
+ else
+ {
+ if (channels_in_file == 3)
+ decomp_status = fpng_pixel_zlib_decompress_3<4>(pIDAT_data, src_len, idat_len, out.data(), width, height);
+ else
+ decomp_status = fpng_pixel_zlib_decompress_4<4>(pIDAT_data, src_len, idat_len, out.data(), width, height);
+ }
+ if (!decomp_status)
+ {
+ // Something went wrong. Either the file data was corrupted, or it doesn't conform to one of our zlib/Deflate constraints.
+ // The conservative thing to do is indicate it wasn't written by us, and let the general purpose PNG decoder handle it.
+ return FPNG_DECODE_NOT_FPNG;
+ }
+
+ return FPNG_DECODE_SUCCESS;
+ }
+
+#ifndef FPNG_NO_STDIO
+ int fpng_decode_file(const char* pFilename, std::vector& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels)
+ {
+ FILE* pFile = nullptr;
+
+#ifdef _MSC_VER
+ fopen_s(&pFile, pFilename, "rb");
+#else
+ pFile = fopen(pFilename, "rb");
+#endif
+
+ if (!pFile)
+ return FPNG_DECODE_FILE_OPEN_FAILED;
+
+ if (fseek(pFile, 0, SEEK_END) != 0)
+ {
+ fclose(pFile);
+ return FPNG_DECODE_FILE_SEEK_FAILED;
+ }
+
+#ifdef _WIN32
+ int64_t filesize = _ftelli64(pFile);
+#else
+ int64_t filesize = ftello(pFile);
+#endif
+
+ if (fseek(pFile, 0, SEEK_SET) != 0)
+ {
+ fclose(pFile);
+ return FPNG_DECODE_FILE_SEEK_FAILED;
+ }
+
+ if ( (filesize < 0) || (filesize > UINT32_MAX) || ( (sizeof(size_t) == sizeof(uint32_t)) && (filesize > 0x70000000) ) )
+ {
+ fclose(pFile);
+ return FPNG_DECODE_FILE_TOO_LARGE;
+ }
+
+ std::vector buf((size_t)filesize);
+ if (fread(buf.data(), 1, buf.size(), pFile) != buf.size())
+ {
+ fclose(pFile);
+ return FPNG_DECODE_FILE_READ_FAILED;
+ }
+
+ fclose(pFile);
+
+ return fpng_decode_memory(buf.data(), (uint32_t)buf.size(), out, width, height, channels_in_file, desired_channels);
+ }
+#endif
+
+} // namespace fpng
+
+/*
+ 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
+
+ Richard Geldreich, Jr.
+ 12/30/2021
+*/
diff --git a/ui/thirdparty/fpng/fpng.h b/ui/thirdparty/fpng/fpng.h
new file mode 100644
index 0000000000..4d55e3afde
--- /dev/null
+++ b/ui/thirdparty/fpng/fpng.h
@@ -0,0 +1,122 @@
+// fpng.h - unlicense (see end of fpng.cpp)
+#pragma once
+
+#include
+#include
+#include
+
+#ifndef FPNG_TRAIN_HUFFMAN_TABLES
+ // Set to 1 when using the -t (training) option in fpng_test to generate new opaque/alpha Huffman tables for the single pass encoder.
+ #define FPNG_TRAIN_HUFFMAN_TABLES (0)
+#endif
+
+namespace fpng
+{
+ // ---- Library initialization - call once to identify if the processor supports SSE.
+ // Otherwise you'll only get scalar fallbacks.
+ void fpng_init();
+
+ // ---- Useful Utilities
+
+ // Returns true if the CPU supports SSE 4.1, and SSE support wasn't disabled by setting FPNG_NO_SSE=1.
+ // fpng_init() must have been called first, or it'll assert and return false.
+ bool fpng_cpu_supports_sse41();
+
+ // Fast CRC-32 SSE4.1+pclmul or a scalar fallback (slice by 4)
+ const uint32_t FPNG_CRC32_INIT = 0;
+ uint32_t fpng_crc32(const void* pData, size_t size, uint32_t prev_crc32 = FPNG_CRC32_INIT);
+
+ // Fast Adler32 SSE4.1 Adler-32 with a scalar fallback.
+ const uint32_t FPNG_ADLER32_INIT = 1;
+ uint32_t fpng_adler32(const void* pData, size_t size, uint32_t adler = FPNG_ADLER32_INIT);
+
+ // ---- Compression
+ enum
+ {
+ // Enables computing custom Huffman tables for each file, instead of using the custom global tables.
+ // Results in roughly 6% smaller files on average, but compression is around 40% slower.
+ FPNG_ENCODE_SLOWER = 1,
+
+ // Only use raw Deflate blocks (no compression at all). Intended for testing.
+ FPNG_FORCE_UNCOMPRESSED = 2,
+ };
+
+ // Fast PNG encoding. The resulting file can be decoded either using a standard PNG decoder or by the fpng_decode_memory() function below.
+ // pImage: pointer to RGB or RGBA image pixels, R first in memory, B/A last.
+ // w/h - image dimensions. Image's row pitch in bytes must is w*num_chans.
+ // num_chans must be 3 or 4.
+ bool fpng_encode_image_to_memory(const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, std::vector& out_buf, uint32_t flags = 0);
+
+#ifndef FPNG_NO_STDIO
+ // Fast PNG encoding to the specified file.
+ bool fpng_encode_image_to_file(const char* pFilename, const void* pImage, uint32_t w, uint32_t h, uint32_t num_chans, uint32_t flags = 0);
+#endif
+
+ // ---- Decompression
+
+ enum
+ {
+ FPNG_DECODE_SUCCESS = 0, // file is a valid PNG file and written by FPNG and the decode succeeded
+
+ FPNG_DECODE_NOT_FPNG, // file is a valid PNG file, but it wasn't written by FPNG so you should try decoding it with a general purpose PNG decoder
+
+ FPNG_DECODE_INVALID_ARG, // invalid function parameter
+
+ FPNG_DECODE_FAILED_NOT_PNG, // file cannot be a PNG file
+ FPNG_DECODE_FAILED_HEADER_CRC32, // a chunk CRC32 check failed, file is likely corrupted or not PNG
+ FPNG_DECODE_FAILED_INVALID_DIMENSIONS, // invalid image dimensions in IHDR chunk (0 or too large)
+ FPNG_DECODE_FAILED_DIMENSIONS_TOO_LARGE, // decoding the file fully into memory would likely require too much memory (only on 32bpp builds)
+ FPNG_DECODE_FAILED_CHUNK_PARSING, // failed while parsing the chunk headers, or file is corrupted
+ FPNG_DECODE_FAILED_INVALID_IDAT, // IDAT data length is too small and cannot be valid, file is either corrupted or it's a bug
+
+ // fpng_decode_file() specific errors
+ FPNG_DECODE_FILE_OPEN_FAILED,
+ FPNG_DECODE_FILE_TOO_LARGE,
+ FPNG_DECODE_FILE_READ_FAILED,
+ FPNG_DECODE_FILE_SEEK_FAILED
+ };
+
+ // Fast PNG decoding of files ONLY created by fpng_encode_image_to_memory() or fpng_encode_image_to_file().
+ // If fpng_get_info() or fpng_decode_memory() returns FPNG_DECODE_NOT_FPNG, you should decode the PNG by falling back to a general purpose decoder.
+ //
+ // fpng_get_info() parses the PNG header and iterates through all chunks to determine if it's a file written by FPNG, but does not decompress the actual image data so it's relatively fast.
+ //
+ // pImage, image_size: Pointer to PNG image data and its size
+ // width, height: output image's dimensions
+ // channels_in_file: will be 3 or 4
+ //
+ // Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above.
+ // If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder.
+ // If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail).
+ int fpng_get_info(const void* pImage, uint32_t image_size, uint32_t& width, uint32_t& height, uint32_t& channels_in_file);
+
+ // fpng_decode_memory() decompresses 24/32bpp PNG files ONLY encoded by this module.
+ // If the image was written by FPNG, it will decompress the image data, otherwise it will return FPNG_DECODE_NOT_FPNG in which case you should fall back to a general purpose PNG decoder (lodepng, stb_image, libpng, etc.)
+ //
+ // pImage, image_size: Pointer to PNG image data and its size
+ // out: Output 24/32bpp image buffer
+ // width, height: output image's dimensions
+ // channels_in_file: will be 3 or 4
+ // desired_channels: must be 3 or 4
+ //
+ // If the image is 24bpp and 32bpp is requested, the alpha values will be set to 0xFF.
+ // If the image is 32bpp and 24bpp is requested, the alpha values will be discarded.
+ //
+ // Returns FPNG_DECODE_SUCCESS on success, otherwise one of the failure codes above.
+ // If FPNG_DECODE_NOT_FPNG is returned, you must decompress the file with a general purpose PNG decoder.
+ // If another error occurs, the file is likely corrupted or invalid, but you can still try to decompress the file with another decoder (which will likely fail).
+ int fpng_decode_memory(const void* pImage, uint32_t image_size, std::vector& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels);
+
+#ifndef FPNG_NO_STDIO
+ int fpng_decode_file(const char* pFilename, std::vector& out, uint32_t& width, uint32_t& height, uint32_t& channels_in_file, uint32_t desired_channels);
+#endif
+
+ // ---- Internal API used for Huffman table training purposes
+
+#if FPNG_TRAIN_HUFFMAN_TABLES
+ const uint32_t HUFF_COUNTS_SIZE = 288;
+ extern uint64_t g_huff_counts[HUFF_COUNTS_SIZE];
+ bool create_dynamic_block_prefix(uint64_t* pFreq, uint32_t num_chans, std::vector& prefix, uint64_t& bit_buf, int& bit_buf_size, uint32_t *pCodes, uint8_t *pCodesizes);
+#endif
+
+} // namespace fpng
diff --git a/ui/httplib.h b/ui/thirdparty/httplib/httplib.h
similarity index 100%
rename from ui/httplib.h
rename to ui/thirdparty/httplib/httplib.h
diff --git a/ui/thirdparty/imgui b/ui/thirdparty/imgui
new file mode 160000
index 0000000000..c71a50deb5
--- /dev/null
+++ b/ui/thirdparty/imgui
@@ -0,0 +1 @@
+Subproject commit c71a50deb5ddf1ea386b91e60fa2e4a26d080074
diff --git a/ui/thirdparty/imgui_impl_opengl3_loader_override.h b/ui/thirdparty/imgui_impl_opengl3_loader_override.h
new file mode 100644
index 0000000000..2daa135837
--- /dev/null
+++ b/ui/thirdparty/imgui_impl_opengl3_loader_override.h
@@ -0,0 +1 @@
+#include
diff --git a/ui/thirdparty/implot b/ui/thirdparty/implot
new file mode 160000
index 0000000000..b47c8bacdb
--- /dev/null
+++ b/ui/thirdparty/implot
@@ -0,0 +1 @@
+Subproject commit b47c8bacdbc78bc521691f70666f13924bb522ab
diff --git a/ui/json.hpp b/ui/thirdparty/json/json.hpp
similarity index 100%
rename from ui/json.hpp
rename to ui/thirdparty/json/json.hpp
diff --git a/ui/thirdparty/meson.build b/ui/thirdparty/meson.build
new file mode 100644
index 0000000000..677283bdb5
--- /dev/null
+++ b/ui/thirdparty/meson.build
@@ -0,0 +1,63 @@
+imgui_files = files(
+ 'imgui/imgui.cpp',
+ 'imgui/imgui_draw.cpp',
+ 'imgui/imgui_tables.cpp',
+ 'imgui/imgui_widgets.cpp',
+ 'imgui/backends/imgui_impl_sdl.cpp',
+ 'imgui/backends/imgui_impl_opengl3.cpp',
+ #'imgui/imgui_demo.cpp',
+)
+
+imgui_cppargs = ['-DIMGUI_IMPL_OPENGL_LOADER_CUSTOM',
+ '-include', 'imgui_impl_opengl3_loader_override.h']
+
+libimgui = static_library('imgui',
+ sources: imgui_files,
+ cpp_args: imgui_cppargs,
+ include_directories: ['.', 'imgui'],
+ dependencies: [sdl, opengl])
+imgui = declare_dependency(link_with: libimgui,
+ include_directories: ['imgui', 'imgui/backends'])
+
+implot_files = files(
+ 'implot/implot.cpp',
+ 'implot/implot_items.cpp'
+ #'implot/implot_demo.cpp',
+)
+
+libimplot = static_library('implot',
+ sources: implot_files,
+ include_directories: 'implot',
+ dependencies: [imgui])
+implot = declare_dependency(link_with: libimplot,
+ include_directories: 'implot')
+
+noc_ss = ss.source_set()
+noc_ss.add(when: 'CONFIG_LINUX', if_true: [xemu_gtk, files('noc_file_dialog/noc_file_dialog_gtk.c')])
+noc_ss.add(when: 'CONFIG_WIN32', if_true: files('noc_file_dialog/noc_file_dialog_win32.c'))
+noc_ss.add(when: 'CONFIG_DARWIN', if_true: files('noc_file_dialog/noc_file_dialog_macos.m'))
+noc_ss = noc_ss.apply(config_all, strict: false)
+noclib = static_library('noc',
+ sources: noc_ss.sources(),
+ dependencies: noc_ss.dependencies(),
+ include_directories: 'noc_file_dialog')
+noc = declare_dependency(include_directories: 'noc_file_dialog', link_with: noclib)
+
+libstb_image = static_library('stb_image',
+ sources: 'stb_image/stb_image_impl.c')
+stb_image = declare_dependency(include_directories: 'stb_image',
+ link_with: libstb_image)
+
+fa = declare_dependency(include_directories: 'fa')
+
+if cpu == 'x86_64'
+ libfpng_cpp_args = ['-DFPNG_NO_SSE=0', '-msse4.1', '-mpclmul']
+else
+ libfpng_cpp_args = ['-DFPNG_NO_SSE=1']
+endif
+
+libfpng = static_library('fpng', sources: 'fpng/fpng.cpp', cpp_args: libfpng_cpp_args)
+fpng = declare_dependency(include_directories: 'fpng', link_with: libfpng)
+
+json = declare_dependency(include_directories: 'json')
+httplib = declare_dependency(include_directories: 'httplib')
diff --git a/ui/noc_file_dialog.h b/ui/thirdparty/noc_file_dialog/noc_file_dialog.h
similarity index 99%
rename from ui/noc_file_dialog.h
rename to ui/thirdparty/noc_file_dialog/noc_file_dialog.h
index 93ab4a9c8a..c72bf8330a 100644
--- a/ui/noc_file_dialog.h
+++ b/ui/thirdparty/noc_file_dialog/noc_file_dialog.h
@@ -20,6 +20,8 @@
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
+#ifndef NOC_FILE_DIALOG_H
+#define NOC_FILE_DIALOG_H
/* A portable library to create open and save dialogs on linux, osx and
* windows.
@@ -328,3 +330,4 @@ const char *noc_file_dialog_open(int flags,
#endif
+#endif
diff --git a/ui/noc_file_dialog_gtk.c b/ui/thirdparty/noc_file_dialog/noc_file_dialog_gtk.c
similarity index 100%
rename from ui/noc_file_dialog_gtk.c
rename to ui/thirdparty/noc_file_dialog/noc_file_dialog_gtk.c
diff --git a/ui/noc_file_dialog_macos.m b/ui/thirdparty/noc_file_dialog/noc_file_dialog_macos.m
similarity index 100%
rename from ui/noc_file_dialog_macos.m
rename to ui/thirdparty/noc_file_dialog/noc_file_dialog_macos.m
diff --git a/ui/noc_file_dialog_win32.c b/ui/thirdparty/noc_file_dialog/noc_file_dialog_win32.c
similarity index 100%
rename from ui/noc_file_dialog_win32.c
rename to ui/thirdparty/noc_file_dialog/noc_file_dialog_win32.c
diff --git a/ui/stb_image.h b/ui/thirdparty/stb_image/stb_image.h
similarity index 93%
rename from ui/stb_image.h
rename to ui/thirdparty/stb_image/stb_image.h
index 2857f05d38..d60371b95f 100644
--- a/ui/stb_image.h
+++ b/ui/thirdparty/stb_image/stb_image.h
@@ -1,4 +1,4 @@
-/* stb_image - v2.25 - public domain image loader - http://nothings.org/stb
+/* stb_image - v2.27 - public domain image loader - http://nothings.org/stb
no warranty implied; use at your own risk
Do this:
@@ -48,6 +48,8 @@ LICENSE
RECENT REVISION HISTORY:
+ 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
@@ -88,27 +90,37 @@ RECENT REVISION HISTORY:
Jeremy Sawicki (handle all ImageNet JPGs)
Optimizations & bugfixes Mikhail Morozov (1-bit BMP)
Fabian "ryg" Giesen Anael Seghezzi (is-16-bit query)
- Arseny Kapoulkine
+ 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 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
- Laurent Gomila Cort Stratton Sergio Gonzalez github:snagar
- Aruelien Pocheville Thibault Reuille Cass Everitt github:Zelex
- Ryamond Barbiero Paul Du Bois Engin Manap github:grim210
- Aldo Culquicondor Philipp Wiesemann Dale Weiler github:sammyhw
- Oriol Ferrer Mesia Josh Tobin Matthew Gregan github:phprus
- Julian Raschke Gregory Mullen Baldur Karlsson github:poppolopoppo
- Christian Floisand Kevin Schmidt JR Smith github:darealshinji
- Brad Weinberger Matvey Cherevko github:Michaelangel007
- Blazej Dariusz Roszkowski Alexander Veselov
+ 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 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
@@ -167,6 +179,32 @@ RECENT REVISION HISTORY:
//
// 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:
@@ -272,11 +310,10 @@ RECENT REVISION HISTORY:
//
// iPhone PNG support:
//
-// By default we convert iphone-formatted PNGs back to RGB, even though
-// they are internally encoded differently. You can disable this conversion
-// by calling stbi_convert_iphone_png_to_rgb(0), in which case
-// you will always just get the native iphone "format" through (which
-// is BGR stored in RGB).
+// 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
@@ -318,7 +355,14 @@ RECENT REVISION HISTORY:
// - 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
@@ -473,6 +517,8 @@ 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
@@ -574,13 +620,19 @@ STBIDEF int stbi_zlib_decode_noheader_buffer(char *obuffer, int olen, const ch
#ifndef STBI_NO_THREAD_LOCALS
#if defined(__cplusplus) && __cplusplus >= 201103L
#define STBI_THREAD_LOCAL thread_local
- #elif defined (__STDC_VERSION__) && __STDC_VERSION__ >= 201112L
- #define STBI_THREAD_LOCAL _Thread_local
- #elif defined(__GNUC__)
+ #elif defined(__GNUC__) && __GNUC__ < 5
#define STBI_THREAD_LOCAL __thread
#elif defined(_MSC_VER)
#define STBI_THREAD_LOCAL __declspec(thread)
-#endif
+ #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
#ifdef _MSC_VER
@@ -612,7 +664,7 @@ typedef unsigned char validate_uint32[sizeof(stbi__uint32)==4 ? 1 : -1];
#ifdef STBI_HAS_LROTL
#define stbi_lrot(x,y) _lrotl(x,y)
#else
- #define stbi_lrot(x,y) (((x) << (y)) | ((x) >> (32 - (y))))
+ #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))
@@ -726,14 +778,21 @@ static int stbi__sse2_available(void)
#ifdef STBI_NEON
#include
-// assume GCC or Clang on ARM targets
+#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
@@ -751,6 +810,7 @@ typedef struct
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;
@@ -764,6 +824,7 @@ 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;
}
@@ -775,7 +836,8 @@ static void stbi__start_callbacks(stbi__context *s, stbi_io_callbacks *c, void *
s->io_user_data = user;
s->buflen = sizeof(s->buffer_start);
s->read_from_callbacks = 1;
- s->img_buffer_original = s->buffer_start;
+ 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;
}
@@ -789,12 +851,17 @@ static int stbi__stdio_read(void *user, char *data, int size)
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);
+ return feof((FILE*) user) || ferror((FILE *) user);
}
static stbi_io_callbacks stbi__stdio_callbacks =
@@ -890,6 +957,7 @@ static int stbi__gif_info(stbi__context *s, int *x, int *y, int *comp);
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
@@ -964,7 +1032,7 @@ static int stbi__mad3sizes_valid(int a, int b, int c, int 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)
+#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) &&
@@ -987,7 +1055,7 @@ static void *stbi__malloc_mad3(int a, int b, int c, int add)
return stbi__malloc(a*b*c + add);
}
-#if !defined(STBI_NO_LINEAR) || !defined(STBI_NO_HDR)
+#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;
@@ -1053,9 +1121,8 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re
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;
- #ifndef STBI_NO_JPEG
- if (stbi__jpeg_test(s)) return stbi__jpeg_load(s,x,y,comp,req_comp, ri);
- #endif
+ // 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
@@ -1073,6 +1140,13 @@ static void *stbi__load_main(stbi__context *s, int *x, int *y, int *comp, int re
#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
@@ -1171,8 +1245,10 @@ static unsigned char *stbi__load_and_postprocess_8bit(stbi__context *s, int *x,
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) {
- STBI_ASSERT(ri.bits_per_channel == 16);
result = stbi__convert_16_to_8((stbi__uint16 *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
ri.bits_per_channel = 8;
}
@@ -1195,8 +1271,10 @@ static stbi__uint16 *stbi__load_and_postprocess_16bit(stbi__context *s, int *x,
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) {
- STBI_ASSERT(ri.bits_per_channel == 8);
result = stbi__convert_8_to_16((stbi_uc *) result, *x, *y, req_comp == 0 ? *comp : req_comp);
ri.bits_per_channel = 16;
}
@@ -1224,12 +1302,12 @@ static void stbi__float_postprocess(float *result, int *x, int *y, int *comp, in
#ifndef STBI_NO_STDIO
-#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+#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(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+#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);
@@ -1239,16 +1317,16 @@ STBIDEF int stbi_convert_wchar_to_utf8(char *buffer, size_t bufferlen, const wch
static FILE *stbi__fopen(char const *filename, char const *mode)
{
FILE *f;
-#if defined(_MSC_VER) && defined(STBI_WINDOWS_UTF8)
+#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)))
+ 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)))
+ if (0 == MultiByteToWideChar(65001 /* UTF8 */, 0, mode, -1, wMode, sizeof(wMode)/sizeof(*wMode)))
return 0;
-#if _MSC_VER >= 1400
+#if defined(_MSC_VER) && _MSC_VER >= 1400
if (0 != _wfopen_s(&f, wFilename, wMode))
f = 0;
#else
@@ -1499,6 +1577,7 @@ enum
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
@@ -1544,6 +1623,7 @@ stbi_inline static int stbi__at_eof(stbi__context *s)
#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;
@@ -1622,7 +1702,8 @@ static int stbi__get16le(stbi__context *s)
static stbi__uint32 stbi__get32le(stbi__context *s)
{
stbi__uint32 z = stbi__get16le(s);
- return z + (stbi__get16le(s) << 16);
+ z += (stbi__uint32)stbi__get16le(s) << 16;
+ return z;
}
#endif
@@ -1686,7 +1767,7 @@ static unsigned char *stbi__convert_format(unsigned char *data, int img_n, int r
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);
+ default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return stbi__errpuc("unsupported", "Unsupported format conversion");
}
#undef STBI__CASE
}
@@ -1743,7 +1824,7 @@ static stbi__uint16 *stbi__convert_format16(stbi__uint16 *data, int img_n, int r
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);
+ default: STBI_ASSERT(0); STBI_FREE(data); STBI_FREE(good); return (stbi__uint16*) stbi__errpuc("unsupported", "Unsupported format conversion");
}
#undef STBI__CASE
}
@@ -2050,13 +2131,12 @@ stbi_inline static int stbi__extend_receive(stbi__jpeg *j, int n)
int sgn;
if (j->code_bits < n) stbi__grow_buffer_unsafe(j);
- sgn = (stbi__int32)j->code_buffer >> 31; // sign bit is always in MSB
+ 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);
- STBI_ASSERT(n >= 0 && n < (int) (sizeof(stbi__bmask)/sizeof(*stbi__bmask)));
j->code_buffer = k & ~stbi__bmask[n];
k &= stbi__bmask[n];
j->code_bits -= n;
- return k + (stbi__jbias[n] & ~sgn);
+ return k + (stbi__jbias[n] & (sgn - 1));
}
// get some unsigned bits
@@ -2106,7 +2186,7 @@ static int stbi__jpeg_decode_block(stbi__jpeg *j, short data[64], stbi__huffman
if (j->code_bits < 16) stbi__grow_buffer_unsafe(j);
t = stbi__jpeg_huff_decode(j, hdc);
- if (t < 0) return stbi__err("bad huffman code","Corrupt JPEG");
+ 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]));
@@ -2163,11 +2243,12 @@ static int stbi__jpeg_decode_block_prog_dc(stbi__jpeg *j, short data[64], stbi__
// 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;
dc = j->img_comp[b].dc_pred + diff;
j->img_comp[b].dc_pred = dc;
- data[0] = (short) (dc << j->succ_low);
+ data[0] = (short) (dc * (1 << j->succ_low));
} else {
// refinement scan for DC coefficient
if (stbi__jpeg_get_bit(j))
@@ -2204,7 +2285,7 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__
j->code_buffer <<= s;
j->code_bits -= s;
zig = stbi__jpeg_dezigzag[k++];
- data[zig] = (short) ((r >> 8) << shift);
+ 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");
@@ -2222,7 +2303,7 @@ static int stbi__jpeg_decode_block_prog_ac(stbi__jpeg *j, short data[64], stbi__
} else {
k += r;
zig = stbi__jpeg_dezigzag[k++];
- data[zig] = (short) (stbi__extend_receive(j,s) << shift);
+ data[zig] = (short) (stbi__extend_receive(j,s) * (1 << shift));
}
}
} while (k <= j->spec_end);
@@ -3153,6 +3234,8 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan)
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;
@@ -3184,6 +3267,13 @@ static int stbi__process_frame_header(stbi__jpeg *z, int scan)
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;
@@ -3739,6 +3829,10 @@ static stbi_uc *load_jpeg_image(stbi__jpeg *z, int *out_x, int *out_y, int *comp
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;
@@ -3881,6 +3975,7 @@ static void *stbi__jpeg_load(stbi__context *s, int *x, int *y, int *comp, int re
{
unsigned char* result;
stbi__jpeg* j = (stbi__jpeg*) stbi__malloc(sizeof(stbi__jpeg));
+ if (!j) return stbi__errpuc("outofmem", "Out of memory");
STBI_NOTUSED(ri);
j->s = s;
stbi__setup_jpeg(j);
@@ -3893,6 +3988,7 @@ 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");
j->s = s;
stbi__setup_jpeg(j);
r = stbi__decode_jpeg_header(j, STBI__SCAN_type);
@@ -3917,6 +4013,7 @@ 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");
j->s = s;
result = stbi__jpeg_info_raw(j, x, y, comp);
STBI_FREE(j);
@@ -3936,6 +4033,7 @@ static int stbi__jpeg_info(stbi__context *s, int *x, int *y, int *comp)
// 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)
@@ -3945,8 +4043,8 @@ typedef struct
stbi__uint16 firstcode[16];
int maxcode[17];
stbi__uint16 firstsymbol[16];
- stbi_uc size[288];
- stbi__uint16 value[288];
+ stbi_uc size[STBI__ZNSYMS];
+ stbi__uint16 value[STBI__ZNSYMS];
} stbi__zhuffman;
stbi_inline static int stbi__bitreverse16(int n)
@@ -4033,16 +4131,23 @@ typedef struct
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)
{
- if (z->zbuffer >= z->zbuffer_end) return 0;
- return *z->zbuffer++;
+ return stbi__zeof(z) ? 0 : *z->zbuffer++;
}
static void stbi__fill_bits(stbi__zbuf *z)
{
do {
- STBI_ASSERT(z->code_buffer < (1U << z->num_bits));
+ 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);
@@ -4067,10 +4172,11 @@ static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z)
for (s=STBI__ZFAST_BITS+1; ; ++s)
if (k < z->maxcode[s])
break;
- if (s == 16) return -1; // invalid code!
+ if (s >= 16) return -1; // invalid code!
// code size is s, so:
b = (k >> (16-s)) - z->firstcode[s] + z->firstsymbol[s];
- STBI_ASSERT(z->size[b] == 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];
@@ -4079,7 +4185,12 @@ static int stbi__zhuffman_decode_slowpath(stbi__zbuf *a, stbi__zhuffman *z)
stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
{
int b,s;
- if (a->num_bits < 16) stbi__fill_bits(a);
+ if (a->num_bits < 16) {
+ if (stbi__zeof(a)) {
+ return -1; /* report error for unexpected end of data. */
+ }
+ stbi__fill_bits(a);
+ }
b = z->fast[a->code_buffer & STBI__ZFAST_MASK];
if (b) {
s = b >> 9;
@@ -4093,13 +4204,16 @@ stbi_inline static int stbi__zhuffman_decode(stbi__zbuf *a, stbi__zhuffman *z)
static int stbi__zexpand(stbi__zbuf *z, char *zout, int n) // need to make room for n bytes
{
char *q;
- int cur, limit, old_limit;
+ unsigned int cur, limit, old_limit;
z->zout = zout;
if (!z->z_expandable) return stbi__err("output buffer limit","Corrupt PNG");
- cur = (int) (z->zout - z->zout_start);
- limit = old_limit = (int) (z->zout_end - z->zout_start);
- while (cur + n > limit)
+ 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");
@@ -4197,11 +4311,12 @@ static int stbi__compute_huffman_codes(stbi__zbuf *a)
c = stbi__zreceive(a,2)+3;
if (n == 0) return stbi__err("bad codelengths", "Corrupt PNG");
fill = lencodes[n-1];
- } else if (c == 17)
+ } else if (c == 17) {
c = stbi__zreceive(a,3)+3;
- else {
- STBI_ASSERT(c == 18);
+ } 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);
@@ -4227,7 +4342,7 @@ static int stbi__parse_uncompressed_block(stbi__zbuf *a)
a->code_buffer >>= 8;
a->num_bits -= 8;
}
- STBI_ASSERT(a->num_bits == 0);
+ 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);
@@ -4249,6 +4364,7 @@ static int stbi__parse_zlib_header(stbi__zbuf *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
@@ -4256,7 +4372,7 @@ static int stbi__parse_zlib_header(stbi__zbuf *a)
return 1;
}
-static const stbi_uc stbi__zdefault_length[288] =
+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,
@@ -4302,7 +4418,7 @@ static int stbi__parse_zlib(stbi__zbuf *a, int parse_header)
} else {
if (type == 1) {
// use fixed code lengths
- if (!stbi__zbuild_huffman(&a->z_length , stbi__zdefault_length , 288)) return 0;
+ 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;
@@ -4510,7 +4626,7 @@ static int stbi__create_png_image_raw(stbi__png *a, stbi_uc *raw, stbi__uint32 r
return stbi__err("invalid filter","Corrupt PNG");
if (depth < 8) {
- STBI_ASSERT(img_width_bytes <= x);
+ if (img_width_bytes > x) return stbi__err("invalid width","Corrupt PNG");
cur += x*out_n - img_width_bytes; // store output to the rightmost img_len bytes, so we can decode in place
filter_bytes = 1;
width = img_width_bytes;
@@ -4698,6 +4814,7 @@ static int stbi__create_png_image(stbi__png *a, stbi_uc *image_data, stbi__uint3
// 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 };
@@ -4818,19 +4935,46 @@ static int stbi__expand_png_palette(stbi__png *a, stbi_uc *palette, int len, int
return 1;
}
-static int stbi__unpremultiply_on_load = 0;
-static int stbi__de_iphone_flag = 0;
+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 = 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 = 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__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;
@@ -4905,8 +5049,10 @@ static int stbi__parse_png_file(stbi__png *z, int scan, int req_comp)
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); if (s->img_x > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)");
- s->img_y = stbi__get32be(s); if (s->img_y > (1 << 24)) return stbi__err("too large","Very large image (corrupt?)");
+ 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");
@@ -5055,10 +5201,12 @@ static void *stbi__do_png(stbi__png *p, int *x, int *y, int *n, int req_comp, st
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)
+ if (p->depth <= 8)
ri->bits_per_channel = 8;
+ else if (p->depth == 16)
+ ri->bits_per_channel = 16;
else
- ri->bits_per_channel = p->depth;
+ 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) {
@@ -5207,6 +5355,32 @@ typedef struct
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;
@@ -5219,6 +5393,8 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
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);
@@ -5232,6 +5408,8 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
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
@@ -5246,17 +5424,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
}
if (info->bpp == 16 || info->bpp == 32) {
if (compress == 0) {
- 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 {
- info->mr = 31u << 10;
- info->mg = 31u << 5;
- info->mb = 31u << 0;
- }
+ stbi__bmp_set_mask_defaults(info, compress);
} else if (compress == 3) {
info->mr = stbi__get32le(s);
info->mg = stbi__get32le(s);
@@ -5271,6 +5439,7 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
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");
@@ -5278,6 +5447,8 @@ static void *stbi__bmp_parse_header(stbi__context *s, stbi__bmp_data *info)
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
@@ -5310,6 +5481,9 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req
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;
@@ -5324,7 +5498,9 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req
psize = (info.offset - info.extra_read - info.hsz) >> 2;
}
if (psize == 0) {
- STBI_ASSERT(info.offset == (s->img_buffer - s->buffer_start));
+ if (info.offset != s->callback_already_read + (s->img_buffer - s->img_buffer_original)) {
+ return stbi__errpuc("bad offset", "Corrupt BMP");
+ }
}
if (info.bpp == 24 && ma == 0xff000000)
@@ -5419,6 +5595,7 @@ static void *stbi__bmp_load(stbi__context *s, int *x, int *y, int *comp, int req
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) {
@@ -5643,6 +5820,9 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req
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 )
{
@@ -5682,6 +5862,11 @@ static void *stbi__tga_load(stbi__context *s, int *x, int *y, int *comp, int req
// 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
@@ -5890,6 +6075,9 @@ static void *stbi__psd_load(stbi__context *s, int *x, int *y, int *comp, int req
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)
@@ -6244,6 +6432,10 @@ static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_c
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");
@@ -6253,6 +6445,7 @@ static void *stbi__pic_load(stbi__context *s,int *px,int *py,int *comp,int req_c
// 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)) {
@@ -6352,6 +6545,9 @@ static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_in
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;
@@ -6365,6 +6561,7 @@ static int stbi__gif_header(stbi__context *s, stbi__gif *g, int *comp, int is_in
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 );
@@ -6529,7 +6726,7 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
memset(g->history, 0x00, pcount); // pixels that were affected previous frame
first_frame = 1;
} else {
- // second frame - how do we dispoase of the previous one?
+ // second frame - how do we dispose of the previous one?
dispose = (g->eflags & 0x1C) >> 2;
pcount = g->w * g->h;
@@ -6674,6 +6871,17 @@ static stbi_uc *stbi__gif_load_next(stbi__context *s, stbi__gif *g, int *comp, i
}
}
+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)) {
@@ -6683,6 +6891,12 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y,
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;
@@ -6699,22 +6913,31 @@ static void *stbi__load_gif_main(stbi__context *s, int **delays, int *x, int *y,
stride = g.w * g.h * 4;
if (out) {
- void *tmp = (stbi_uc*) STBI_REALLOC( out, layers * stride );
- if (NULL == tmp) {
- STBI_FREE(g.out);
- STBI_FREE(g.history);
- STBI_FREE(g.background);
- return stbi__errpuc("outofmem", "Out of memory");
+ 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;
}
- else
- out = (stbi_uc*) tmp;
+
if (delays) {
- *delays = (int*) STBI_REALLOC( *delays, sizeof(int) * layers );
+ 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 );
@@ -6893,6 +7116,9 @@ static float *stbi__hdr_load(stbi__context *s, int *x, int *y, int *comp, int re
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;
@@ -7035,9 +7261,10 @@ static int stbi__bmp_info(stbi__context *s, int *x, int *y, int *comp)
info.all_a = 255;
p = stbi__bmp_parse_header(s, &info);
- stbi__rewind( s );
- if (p == NULL)
+ if (p == NULL) {
+ stbi__rewind( s );
return 0;
+ }
if (x) *x = s->img_x;
if (y) *y = s->img_y;
if (comp) {
@@ -7103,8 +7330,8 @@ static int stbi__psd_is16(stbi__context *s)
stbi__rewind( s );
return 0;
}
- (void) stbi__get32be(s);
- (void) stbi__get32be(s);
+ STBI_NOTUSED(stbi__get32be(s));
+ STBI_NOTUSED(stbi__get32be(s));
depth = stbi__get16be(s);
if (depth != 16) {
stbi__rewind( s );
@@ -7183,7 +7410,6 @@ static int stbi__pic_info(stbi__context *s, int *x, int *y, int *comp)
// Known limitations:
// Does not support comments in the header section
// Does not support ASCII image data (formats P2 and P3)
-// Does not support 16-bit-per-channel
#ifndef STBI_NO_PNM
@@ -7204,19 +7430,23 @@ static void *stbi__pnm_load(stbi__context *s, int *x, int *y, int *comp, int req
stbi_uc *out;
STBI_NOTUSED(ri);
- if (!stbi__pnm_info(s, (int *)&s->img_x, (int *)&s->img_y, (int *)&s->img_n))
+ 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__mad3sizes_valid(s->img_n, s->img_x, s->img_y, 0))
+ 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_mad3(s->img_n, s->img_x, s->img_y, 0);
+ 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");
- stbi__getn(s, out, s->img_n * s->img_x * s->img_y);
+ stbi__getn(s, out, s->img_n * s->img_x * s->img_y * (ri->bits_per_channel / 8));
if (req_comp && req_comp != s->img_n) {
out = stbi__convert_format(out, s->img_n, req_comp, s->img_x, s->img_y);
@@ -7292,11 +7522,19 @@ static int stbi__pnm_info(stbi__context *s, int *x, int *y, int *comp)
stbi__pnm_skip_whitespace(s, &c);
maxv = stbi__pnm_getinteger(s, &c); // read max value
-
- if (maxv > 255)
- return stbi__err("max value > 255", "PPM image not 8-bit");
+ 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 1;
+ return 8;
+}
+
+static int stbi__pnm_is16(stbi__context *s)
+{
+ if (stbi__pnm_info(s, NULL, NULL, NULL) == 16)
+ return 1;
+ return 0;
}
#endif
@@ -7352,6 +7590,9 @@ static int stbi__is_16_main(stbi__context *s)
if (stbi__psd_is16(s)) return 1;
#endif
+ #ifndef STBI_NO_PNM
+ if (stbi__pnm_is16(s)) return 1;
+ #endif
return 0;
}
diff --git a/ui/thirdparty/stb_image/stb_image_impl.c b/ui/thirdparty/stb_image/stb_image_impl.c
new file mode 100644
index 0000000000..8ddfd1f546
--- /dev/null
+++ b/ui/thirdparty/stb_image/stb_image_impl.c
@@ -0,0 +1,2 @@
+#define STB_IMAGE_IMPLEMENTATION
+#include "stb_image.h"
diff --git a/ui/xemu-custom-widgets.c b/ui/xemu-custom-widgets.c
deleted file mode 100644
index f31b4c5d12..0000000000
--- a/ui/xemu-custom-widgets.c
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * xemu User Interface Rendering Helpers
- *
- * Copyright (C) 2020-2021 Matt Borgerson
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include
-#include
-#include
-
-#include "xemu-shaders.h"
-#include "xemu-custom-widgets.h"
-
-#include "data/controller_mask.png.h"
-#include "data/logo_sdf.png.h"
-
-static struct decal_shader *s = NULL;
-static struct decal_shader *s_logo = NULL;
-GLuint main_fb;
-struct fbo *controller_fbo;
-struct fbo *logo_fbo;
-GLint vp[4];
-GLuint g_ui_tex;
-GLuint g_logo_tex;
-
-struct rect {
- int x, y, w, h;
-};
-
-const struct rect tex_items[] = {
- { 0, 148, 467, 364 }, // obj_controller
- { 0, 81, 67, 67 }, // obj_lstick
- { 0, 14, 67, 67 }, // obj_rstick
- { 67, 104, 68, 44 }, // obj_port_socket
- { 67, 76, 28, 28 }, // obj_port_lbl_1
- { 67, 48, 28, 28 }, // obj_port_lbl_2
- { 67, 20, 28, 28 }, // obj_port_lbl_3
- { 95, 76, 28, 28 }, // obj_port_lbl_4
-};
-
-enum tex_item_names {
- obj_controller,
- obj_lstick,
- obj_rstick,
- obj_port_socket,
- obj_port_lbl_1,
- obj_port_lbl_2,
- obj_port_lbl_3,
- obj_port_lbl_4,
-};
-
-void initialize_custom_ui_rendering(void)
-{
- glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (GLint*)&main_fb);
- glGetIntegerv(GL_VIEWPORT, vp);
-
- glActiveTexture(GL_TEXTURE0);
- g_ui_tex = load_texture_from_memory(controller_mask_data, controller_mask_size);
- s = create_decal_shader(SHADER_TYPE_MASK);
- g_logo_tex = load_texture_from_memory(logo_sdf_data, logo_sdf_size);
- s_logo = create_decal_shader(SHADER_TYPE_LOGO);
- controller_fbo = create_fbo(512, 512);
- logo_fbo = create_fbo(512, 512);
- render_to_default_fb();
-}
-
-void render_meter(
- struct decal_shader *s,
- float x, float y, float width, float height, float p,
- uint32_t color_bg, uint32_t color_fg)
-{
- render_decal(s, x, y, width, height,0, 0, 1, 1, 0, 0, color_bg);
- render_decal(s, x, y, width*p, height ,0, 0, 1, 1, 0, 0, color_fg);
-}
-
-void render_controller(float frame_x, float frame_y, uint32_t primary_color, uint32_t secondary_color, ControllerState *state)
-{
- // Location within the controller texture of masked button locations,
- // relative to the origin of the controller
- const struct rect jewel = { 177, 172, 113, 118 };
- const struct rect lstick_ctr = { 93, 246, 0, 0 };
- const struct rect rstick_ctr = { 342, 148, 0, 0 };
- const struct rect buttons[12] = {
- { 367, 187, 30, 38 }, // A
- { 368, 229, 30, 38 }, // B
- { 330, 204, 30, 38 }, // X
- { 331, 247, 30, 38 }, // Y
- { 82, 121, 31, 47 }, // D-Left
- { 104, 160, 44, 25 }, // D-Up
- { 141, 121, 31, 47 }, // D-Right
- { 104, 105, 44, 25 }, // D-Down
- { 187, 94, 34, 24 }, // Back
- { 246, 94, 36, 26 }, // Start
- { 348, 288, 30, 38 }, // White
- { 386, 268, 30, 38 }, // Black
- };
-
- uint8_t alpha = 0;
- uint32_t now = SDL_GetTicks();
- float t;
-
- glUseProgram(s->prog);
- glBindVertexArray(s->vao);
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, g_ui_tex);
-
- // Add a 5 pixel space around the controller so we can wiggle the controller
- // around to visualize rumble in action
- frame_x += 5;
- frame_y += 5;
- float original_frame_x = frame_x;
- float original_frame_y = frame_y;
-
- // Floating point versions that will get scaled
- float rumble_l = 0;
- float rumble_r = 0;
-
- glBlendEquation(GL_FUNC_ADD);
- glBlendFunc(GL_ONE, GL_ZERO);
-
- uint32_t jewel_color = secondary_color;
-
- // Check to see if the guide button is pressed
- const uint32_t animate_guide_button_duration = 2000;
- if (state->buttons & CONTROLLER_BUTTON_GUIDE) {
- state->animate_guide_button_end = now + animate_guide_button_duration;
- }
-
- if (now < state->animate_guide_button_end) {
- t = 1.0f - (float)(state->animate_guide_button_end-now)/(float)animate_guide_button_duration;
- float sin_wav = (1-sin(M_PI * t / 2.0f));
-
- // Animate guide button by highlighting logo jewel and fading out over time
- alpha = sin_wav * 255.0f;
- jewel_color = primary_color + alpha;
-
- // Add a little extra flare: wiggle the frame around while we rumble
- frame_x += ((float)(rand() % 5)-2.5) * (1-t);
- frame_y += ((float)(rand() % 5)-2.5) * (1-t);
- rumble_l = rumble_r = sin_wav;
- }
-
- // Render controller texture
- render_decal(s,
- frame_x+0, frame_y+0, tex_items[obj_controller].w, tex_items[obj_controller].h,
- tex_items[obj_controller].x, tex_items[obj_controller].y, tex_items[obj_controller].w, tex_items[obj_controller].h,
- primary_color, secondary_color, 0);
-
- glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE); // Blend with controller cutouts
- render_decal(s, frame_x+jewel.x, frame_y+jewel.y, jewel.w, jewel.h, 0, 0, 1, 1, 0, 0, jewel_color);
-
- // The controller has alpha cutouts where the buttons are. Draw a surface
- // behind the buttons if they are activated
- for (int i = 0; i < 12; i++) {
- bool enabled = !!(state->buttons & (1 << i));
- if (!enabled) continue;
- render_decal(s,
- frame_x+buttons[i].x, frame_y+buttons[i].y,
- buttons[i].w, buttons[i].h,
- 0, 0, 1, 1,
- 0, 0, (enabled ? primary_color : secondary_color)+0xff);
- }
-
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Blend with controller
-
- // Render left thumbstick
- float w = tex_items[obj_lstick].w;
- float h = tex_items[obj_lstick].h;
- float c_x = frame_x+lstick_ctr.x;
- float c_y = frame_y+lstick_ctr.y;
- float lstick_x = (float)state->axis[CONTROLLER_AXIS_LSTICK_X]/32768.0;
- float lstick_y = (float)state->axis[CONTROLLER_AXIS_LSTICK_Y]/32768.0;
- render_decal(s,
- (int)(c_x-w/2.0f+10.0f*lstick_x),
- (int)(c_y-h/2.0f+10.0f*lstick_y),
- w, h,
- tex_items[obj_lstick].x, tex_items[obj_lstick].y, w, h,
- (state->buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color : primary_color,
- (state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color : secondary_color,
- 0
- );
-
- // Render right thumbstick
- w = tex_items[obj_rstick].w;
- h = tex_items[obj_rstick].h;
- c_x = frame_x+rstick_ctr.x;
- c_y = frame_y+rstick_ctr.y;
- float rstick_x = (float)state->axis[CONTROLLER_AXIS_RSTICK_X]/32768.0;
- float rstick_y = (float)state->axis[CONTROLLER_AXIS_RSTICK_Y]/32768.0;
- render_decal(s,
- (int)(c_x-w/2.0f+10.0f*rstick_x),
- (int)(c_y-h/2.0f+10.0f*rstick_y),
- w, h,
- tex_items[obj_rstick].x, tex_items[obj_rstick].y, w, h,
- (state->buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color : primary_color,
- (state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color : secondary_color,
- 0
- );
-
- glBlendFunc(GL_ONE, GL_ZERO); // Don't blend, just overwrite values in buffer
-
- // Render trigger bars
- float ltrig = state->axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
- float rtrig = state->axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
- const uint32_t animate_trigger_duration = 1000;
- if ((ltrig > 0) || (rtrig > 0)) {
- state->animate_trigger_end = now + animate_trigger_duration;
- rumble_l = fmax(rumble_l, ltrig);
- rumble_r = fmax(rumble_r, rtrig);
- }
-
- // Animate trigger alpha down after a period of inactivity
- alpha = 0x80;
- if (state->animate_trigger_end > now) {
- t = 1.0f - (float)(state->animate_trigger_end-now)/(float)animate_trigger_duration;
- float sin_wav = (1-sin(M_PI * t / 2.0f));
- alpha += fmin(sin_wav * 0x40, 0x80);
- }
-
- render_meter(s,
- original_frame_x+10,
- original_frame_y+tex_items[obj_controller].h+20,
- 150, 5,
- ltrig,
- primary_color + alpha,
- primary_color + 0xff);
- render_meter(s,
- original_frame_x+tex_items[obj_controller].w-160,
- original_frame_y+tex_items[obj_controller].h+20,
- 150, 5,
- rtrig,
- primary_color + alpha,
- primary_color + 0xff);
-
- // Apply rumble updates
- state->rumble_l = (int)(rumble_l * (float)0xffff);
- state->rumble_r = (int)(rumble_r * (float)0xffff);
- xemu_input_update_rumble(state);
-
- glBindVertexArray(0);
- glUseProgram(0);
-}
-
-void render_controller_port(float frame_x, float frame_y, int i, uint32_t port_color)
-{
- glUseProgram(s->prog);
- glBindVertexArray(s->vao);
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, g_ui_tex);
-
- glBlendFunc(GL_ONE, GL_ZERO);
-
- // Render port socket
- render_decal(s,
- frame_x, frame_y,
- tex_items[obj_port_socket].w, tex_items[obj_port_socket].h,
- tex_items[obj_port_socket].x, tex_items[obj_port_socket].y,
- tex_items[obj_port_socket].w, tex_items[obj_port_socket].h,
- port_color, port_color, 0
- );
-
- frame_x += (tex_items[obj_port_socket].w-tex_items[obj_port_lbl_1].w)/2;
- frame_y += tex_items[obj_port_socket].h + 8;
-
- // Render port label
- render_decal(s,
- frame_x, frame_y,
- tex_items[obj_port_lbl_1+i].w, tex_items[obj_port_lbl_1+i].h,
- tex_items[obj_port_lbl_1+i].x, tex_items[obj_port_lbl_1+i].y,
- tex_items[obj_port_lbl_1+i].w, tex_items[obj_port_lbl_1+i].h,
- port_color, port_color, 0
- );
-
- glBindVertexArray(0);
- glUseProgram(0);
-}
-
-void render_logo(uint32_t time, uint32_t primary_color, uint32_t secondary_color, uint32_t fill_color)
-{
- s_logo->time = time;
- glUseProgram(s_logo->prog);
- glBindVertexArray(s->vao);
- glBlendFunc(GL_ONE, GL_ZERO);
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, g_logo_tex);
- render_decal(
- s_logo,
- 0, 0, 512, 512,
- 0,
- 0,
- 128,
- 128,
- primary_color, secondary_color, fill_color
- );
- glBindVertexArray(0);
- glUseProgram(0);
-}
diff --git a/ui/xemu-custom-widgets.h b/ui/xemu-custom-widgets.h
deleted file mode 100644
index 776113423f..0000000000
--- a/ui/xemu-custom-widgets.h
+++ /dev/null
@@ -1,45 +0,0 @@
-/*
- * xemu User Interface Rendering Helpers
- *
- * Copyright (C) 2020-2021 Matt Borgerson
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#ifndef XEMU_CUSTOM_WIDGETS
-#define XEMU_CUSTOM_WIDGETS
-
-#include
-#include "xemu-input.h"
-#include "xemu-shaders.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// FIXME: Cleanup
-extern struct fbo *controller_fbo;
-extern struct fbo *logo_fbo;
-
-void initialize_custom_ui_rendering(void);
-void render_meter(struct decal_shader *s, float x, float y, float width, float height, float p, uint32_t color_bg, uint32_t color_fg);
-void render_controller(float frame_x, float frame_y, uint32_t primary_color, uint32_t secondary_color, ControllerState *state);
-void render_controller_port(float frame_x, float frame_y, int i, uint32_t port_color);
-void render_logo(uint32_t time, uint32_t primary_color, uint32_t secondary_color, uint32_t fill_color);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/ui/xemu-hud.cc b/ui/xemu-hud.cc
deleted file mode 100644
index 50eea79712..0000000000
--- a/ui/xemu-hud.cc
+++ /dev/null
@@ -1,2412 +0,0 @@
-/*
- * xemu User Interface
- *
- * Copyright (C) 2020-2021 Matt Borgerson
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include
-#include
-#include
-#include
-#include
-#include
-
-#include "xemu-hud.h"
-#include "xemu-input.h"
-#include "xemu-notifications.h"
-#include "xemu-settings.h"
-#include "xemu-shaders.h"
-#include "xemu-custom-widgets.h"
-#include "xemu-monitor.h"
-#include "xemu-version.h"
-#include "xemu-net.h"
-#include "xemu-os-utils.h"
-#include "xemu-xbe.h"
-#include "xemu-reporting.h"
-
-#if defined(_WIN32)
-#include "xemu-update.h"
-#endif
-
-#include "data/roboto_medium.ttf.h"
-
-#include "imgui/imgui.h"
-#include "imgui/backends/imgui_impl_sdl.h"
-#include "imgui/backends/imgui_impl_opengl3.h"
-#include "implot/implot.h"
-
-extern "C" {
-#include "noc_file_dialog.h"
-
-// Include necessary QEMU headers
-#include "qemu/osdep.h"
-#include "qemu-common.h"
-#include "sysemu/sysemu.h"
-#include "sysemu/runstate.h"
-#include "hw/xbox/mcpx/apu_debug.h"
-#include "hw/xbox/nv2a/debug.h"
-#include "hw/xbox/nv2a/nv2a.h"
-#include "net/pcap.h"
-
-#undef typename
-#undef atomic_fetch_add
-#undef atomic_fetch_and
-#undef atomic_fetch_xor
-#undef atomic_fetch_or
-#undef atomic_fetch_sub
-}
-
-ImFont *g_fixed_width_font;
-float g_main_menu_height;
-float g_ui_scale = 1.0;
-bool g_trigger_style_update = true;
-
-class NotificationManager
-{
-private:
- const int kNotificationDuration = 4000;
- std::deque notification_queue;
- bool active;
- uint32_t notification_end_ts;
- const char *msg;
-
-public:
- NotificationManager()
- {
- active = false;
- }
-
- ~NotificationManager()
- {
-
- }
-
- void QueueNotification(const char *msg)
- {
- notification_queue.push_back(strdup(msg));
- }
-
- void Draw()
- {
- uint32_t now = SDL_GetTicks();
-
- if (active) {
- // Currently displaying a notification
- float t = (notification_end_ts - now)/(float)kNotificationDuration;
- if (t > 1.0) {
- // Notification delivered, free it
- free((void*)msg);
- active = false;
- } else {
- // Notification should be displayed
- DrawNotification(t, msg);
- }
- } else {
- // Check to see if a notification is pending
- if (notification_queue.size() > 0) {
- msg = notification_queue[0];
- active = true;
- notification_end_ts = now + kNotificationDuration;
- notification_queue.pop_front();
- }
- }
- }
-
-private:
- void DrawNotification(float t, const char *msg)
- {
- const float DISTANCE = 10.0f;
- static int corner = 1;
- ImGuiIO& io = ImGui::GetIO();
- if (corner != -1)
- {
- ImVec2 window_pos = ImVec2((corner & 1) ? io.DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? io.DisplaySize.y - DISTANCE : DISTANCE);
- window_pos.y = g_main_menu_height + DISTANCE;
- ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f);
- ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
- }
-
- const float fade_in = 0.1;
- const float fade_out = 0.9;
- float fade = 0;
-
- if (t < fade_in) {
- // Linear fade in
- fade = t/fade_in;
- } else if (t >= fade_out) {
- // Linear fade out
- fade = 1-(t-fade_out)/(1-fade_out);
- } else {
- // Constant
- fade = 1.0;
- }
-
- ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_ButtonActive];
- color.w *= fade;
- ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 1);
- ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0,0,0,fade*0.9f));
- ImGui::PushStyleColor(ImGuiCol_Border, color);
- ImGui::PushStyleColor(ImGuiCol_Text, color);
- ImGui::SetNextWindowBgAlpha(0.90f * fade);
- if (ImGui::Begin("Notification", NULL,
- ImGuiWindowFlags_Tooltip |
- ImGuiWindowFlags_NoMove |
- ImGuiWindowFlags_NoDecoration |
- ImGuiWindowFlags_AlwaysAutoResize |
- ImGuiWindowFlags_NoSavedSettings |
- ImGuiWindowFlags_NoFocusOnAppearing |
- ImGuiWindowFlags_NoNav |
- ImGuiWindowFlags_NoInputs
- ))
- {
- ImGui::Text("%s", msg);
- }
- ImGui::PopStyleColor();
- ImGui::PopStyleColor();
- ImGui::PopStyleColor();
- ImGui::PopStyleVar();
- ImGui::End();
- }
-};
-
-static void HelpMarker(const char* desc)
-{
- ImGui::TextDisabled("(?)");
- if (ImGui::IsItemHovered())
- {
- ImGui::BeginTooltip();
- ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
- ImGui::TextUnformatted(desc);
- ImGui::PopTextWrapPos();
- ImGui::EndTooltip();
- }
-}
-
-static void Hyperlink(const char *text, const char *url)
-{
- // FIXME: Color text when hovered
- ImColor col;
- ImGui::Text("%s", text);
- if (ImGui::IsItemHovered()) {
- col = IM_COL32_WHITE;
- ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
- } else {
- col = ImColor(127, 127, 127, 255);
- }
-
- ImVec2 max = ImGui::GetItemRectMax();
- ImVec2 min = ImGui::GetItemRectMin();
- min.x -= 1 * g_ui_scale;
- min.y = max.y;
- max.x -= 1 * g_ui_scale;
- ImGui::GetWindowDrawList()->AddLine(min, max, col, 1.0 * g_ui_scale);
-
- if (ImGui::IsItemClicked()) {
- xemu_open_web_browser(url);
- }
-}
-
-static int PushWindowTransparencySettings(bool transparent, float alpha_transparent = 0.4, float alpha_opaque = 1.0)
-{
- float alpha = transparent ? alpha_transparent : alpha_opaque;
-
- ImVec4 c;
-
- c = ImGui::GetStyle().Colors[transparent ? ImGuiCol_WindowBg : ImGuiCol_TitleBg];
- c.w *= alpha;
- ImGui::PushStyleColor(ImGuiCol_TitleBg, c);
-
- c = ImGui::GetStyle().Colors[transparent ? ImGuiCol_WindowBg : ImGuiCol_TitleBgActive];
- c.w *= alpha;
- ImGui::PushStyleColor(ImGuiCol_TitleBgActive, c);
-
- c = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
- c.w *= alpha;
- ImGui::PushStyleColor(ImGuiCol_WindowBg, c);
-
- c = ImGui::GetStyle().Colors[ImGuiCol_Border];
- c.w *= alpha;
- ImGui::PushStyleColor(ImGuiCol_Border, c);
-
- c = ImGui::GetStyle().Colors[ImGuiCol_FrameBg];
- c.w *= alpha;
- ImGui::PushStyleColor(ImGuiCol_FrameBg, c);
-
- return 5;
-}
-
-class MonitorWindow
-{
-public:
- bool is_open;
-
-private:
- char InputBuf[256];
- ImVector Items;
- ImVector Commands;
- ImVector History;
- int HistoryPos; // -1: new line, 0..History.Size-1 browsing history.
- ImGuiTextFilter Filter;
- bool AutoScroll;
- bool ScrollToBottom;
-
-public:
- MonitorWindow()
- {
- is_open = false;
- memset(InputBuf, 0, sizeof(InputBuf));
- HistoryPos = -1;
- AutoScroll = true;
- ScrollToBottom = false;
- }
- ~MonitorWindow()
- {
- }
-
- // Portable helpers
- static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; }
- static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buf = malloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)str, len); }
- static void Strtrim(char* str) { char* str_end = str + strlen(str); while (str_end > str && str_end[-1] == ' ') str_end--; *str_end = 0; }
-
- void Draw()
- {
- if (!is_open) return;
- int style_pop_cnt = PushWindowTransparencySettings(true);
- ImGuiIO& io = ImGui::GetIO();
- ImVec2 window_pos = ImVec2(0,io.DisplaySize.y/2);
- ImGui::SetNextWindowPos(window_pos, ImGuiCond_Appearing);
- ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y/2), ImGuiCond_Appearing);
- if (ImGui::Begin("Monitor", &is_open, ImGuiWindowFlags_NoCollapse)) {
- const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text
- ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText
-
- ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing
- ImGui::PushFont(g_fixed_width_font);
- ImGui::TextUnformatted(xemu_get_monitor_buffer());
- ImGui::PopFont();
-
- if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) {
- ImGui::SetScrollHereY(1.0f);
- }
- ScrollToBottom = false;
-
- ImGui::PopStyleVar();
- ImGui::EndChild();
- ImGui::Separator();
-
- // Command-line
- bool reclaim_focus = ImGui::IsWindowAppearing();
-
- ImGui::SetNextItemWidth(-1);
- ImGui::PushFont(g_fixed_width_font);
- if (ImGui::InputText("", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) {
- char* s = InputBuf;
- Strtrim(s);
- if (s[0])
- ExecCommand(s);
- strcpy(s, "");
- reclaim_focus = true;
- }
- ImGui::PopFont();
-
- // Auto-focus on window apparition
- ImGui::SetItemDefaultFocus();
- if (reclaim_focus) {
- ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget
- }
- }
- ImGui::End();
- ImGui::PopStyleColor(style_pop_cnt);
- }
-
- void toggle_open(void)
- {
- is_open = !is_open;
- }
-
-private:
- void ExecCommand(const char* command_line)
- {
- xemu_run_monitor_command(command_line);
-
- // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal.
- HistoryPos = -1;
- for (int i = History.Size-1; i >= 0; i--)
- if (Stricmp(History[i], command_line) == 0)
- {
- free(History[i]);
- History.erase(History.begin() + i);
- break;
- }
- History.push_back(Strdup(command_line));
-
- // On commad input, we scroll to bottom even if AutoScroll==false
- ScrollToBottom = true;
- }
-
- static int TextEditCallbackStub(ImGuiInputTextCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks
- {
- MonitorWindow* console = (MonitorWindow*)data->UserData;
- return console->TextEditCallback(data);
- }
-
- int TextEditCallback(ImGuiInputTextCallbackData* data)
- {
- switch (data->EventFlag)
- {
- case ImGuiInputTextFlags_CallbackHistory:
- {
- // Example of HISTORY
- const int prev_history_pos = HistoryPos;
- if (data->EventKey == ImGuiKey_UpArrow)
- {
- if (HistoryPos == -1)
- HistoryPos = History.Size - 1;
- else if (HistoryPos > 0)
- HistoryPos--;
- }
- else if (data->EventKey == ImGuiKey_DownArrow)
- {
- if (HistoryPos != -1)
- if (++HistoryPos >= History.Size)
- HistoryPos = -1;
- }
-
- // A better implementation would preserve the data on the current input line along with cursor position.
- if (prev_history_pos != HistoryPos)
- {
- const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : "";
- data->DeleteChars(0, data->BufTextLen);
- data->InsertChars(0, history_str);
- }
- }
- }
- return 0;
- }
-};
-
-class InputWindow
-{
-public:
- bool is_open;
-
- InputWindow()
- {
- is_open = false;
- }
-
- ~InputWindow()
- {
- }
-
- void Draw()
- {
- if (!is_open) return;
-
- ImGui::SetNextWindowContentSize(ImVec2(500.0f*g_ui_scale, 0.0f));
- // Remove window X padding for this window to easily center stuff
- ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0,ImGui::GetStyle().WindowPadding.y));
- if (!ImGui::Begin("Input", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize))
- {
- ImGui::End();
- ImGui::PopStyleVar();
- return;
- }
-
- static int active = 0;
-
- // Output dimensions of texture
- float t_w = 512, t_h = 512;
- // Dimensions of (port+label)s
- float b_x = 0, b_x_stride = 100, b_y = 400;
- float b_w = 68, b_h = 81;
- // Dimensions of controller (rendered at origin)
- float controller_width = 477.0f;
- float controller_height = 395.0f;
-
- // Setup rendering to fbo for controller and port images
- ImTextureID id = (ImTextureID)(intptr_t)render_to_fbo(controller_fbo);
-
- //
- // Render buttons with icons of the Xbox style port sockets with
- // circular numbers above them. These buttons can be activated to
- // configure the associated port, like a tabbed interface.
- //
- ImVec4 color_active(0.50, 0.86, 0.54, 0.12);
- ImVec4 color_inactive(0, 0, 0, 0);
-
- // Begin a 4-column layout to render the ports
- ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0,12));
- ImGui::Columns(4, "mixed", false);
-
- const int port_padding = 8;
- for (int i = 0; i < 4; i++) {
- bool is_currently_selected = (i == active);
- bool port_is_bound = (xemu_input_get_bound(i) != NULL);
-
- // Set an X offset to center the image button within the column
- ImGui::SetCursorPosX(ImGui::GetCursorPosX()+(int)((ImGui::GetColumnWidth()-b_w*g_ui_scale-2*port_padding*g_ui_scale)/2));
-
- // We are using the same texture for all buttons, but ImageButton
- // uses the texture as a unique ID. Push a new ID now to resolve
- // the conflict.
- ImGui::PushID(i);
- float x = b_x+i*b_x_stride;
- ImGui::PushStyleColor(ImGuiCol_Button, is_currently_selected ? color_active : color_inactive);
- bool activated = ImGui::ImageButton(id,
- ImVec2(b_w*g_ui_scale,b_h*g_ui_scale),
- ImVec2(x/t_w, (b_y+b_h)/t_h),
- ImVec2((x+b_w)/t_w, b_y/t_h),
- port_padding);
- ImGui::PopStyleColor();
-
- if (activated) {
- active = i;
- }
-
- uint32_t port_color = 0xafafafff;
- bool is_hovered = ImGui::IsItemHovered();
- if (is_currently_selected || port_is_bound) {
- port_color = 0x81dc8a00;
- } else if (is_hovered) {
- port_color = 0x000000ff;
- }
-
- render_controller_port(x, b_y, i, port_color);
-
- ImGui::PopID();
- ImGui::NextColumn();
- }
- ImGui::PopStyleVar(); // ItemSpacing
- ImGui::Columns(1);
-
- //
- // Render input device combo
- //
-
- // Center the combo above the controller with the same width
- ImGui::SetCursorPosX(ImGui::GetCursorPosX()+(int)((ImGui::GetColumnWidth()-controller_width*g_ui_scale)/2.0));
-
- // Note: SetNextItemWidth applies only to the combo element, but not the
- // associated label which follows, so scale back a bit to make space for
- // the label.
- ImGui::SetNextItemWidth(controller_width*0.75*g_ui_scale);
-
- // List available input devices
- const char *not_connected = "Not Connected";
- ControllerState *bound_state = xemu_input_get_bound(active);
-
- // Get current controller name
- const char *name;
- if (bound_state == NULL) {
- name = not_connected;
- } else {
- name = bound_state->name;
- }
-
- if (ImGui::BeginCombo("Input Devices", name))
- {
- // Handle "Not connected"
- bool is_selected = bound_state == NULL;
- if (ImGui::Selectable(not_connected, is_selected)) {
- xemu_input_bind(active, NULL, 1);
- bound_state = NULL;
- }
- if (is_selected) {
- ImGui::SetItemDefaultFocus();
- }
-
- // Handle all available input devices
- ControllerState *iter;
- QTAILQ_FOREACH(iter, &available_controllers, entry) {
- is_selected = bound_state == iter;
- ImGui::PushID(iter);
- const char *selectable_label = iter->name;
- char buf[128];
- if (iter->bound >= 0) {
- snprintf(buf, sizeof(buf), "%s (Port %d)", iter->name, iter->bound+1);
- selectable_label = buf;
- }
- if (ImGui::Selectable(selectable_label, is_selected)) {
- xemu_input_bind(active, iter, 1);
- bound_state = iter;
- }
- if (is_selected) {
- ImGui::SetItemDefaultFocus();
- }
- ImGui::PopID();
- }
-
- ImGui::EndCombo();
- }
-
- ImGui::Columns(1);
-
- //
- // Add a separator between input selection and controller graphic
- //
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
- ImGui::Separator();
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
-
- //
- // Render controller image
- //
- bool device_selected = false;
-
- if (bound_state) {
- device_selected = true;
- render_controller(0, 0, 0x81dc8a00, 0x0f0f0f00, bound_state);
- } else {
- static ControllerState state = { 0 };
- render_controller(0, 0, 0x1f1f1f00, 0x0f0f0f00, &state);
- }
-
- // update_sdl_controller_state(&state);
- // update_sdl_kbd_controller_state(&state);
- ImVec2 cur = ImGui::GetCursorPos();
- ImGui::SetCursorPosX(ImGui::GetCursorPosX()+(int)((ImGui::GetColumnWidth()-controller_width*g_ui_scale)/2.0));
- ImGui::Image(id,
- ImVec2(controller_width*g_ui_scale, controller_height*g_ui_scale),
- ImVec2(0, controller_height/t_h),
- ImVec2(controller_width/t_w, 0));
-
- if (!device_selected) {
- // ImGui::SameLine();
- const char *msg = "Please select an available input device";
- ImVec2 dim = ImGui::CalcTextSize(msg);
- ImGui::SetCursorPosX(cur.x + (controller_width*g_ui_scale-dim.x)/2);
- ImGui::SetCursorPosY(cur.y + (controller_height*g_ui_scale-dim.y)/2);
- ImGui::Text("%s", msg);
- ImGui::SameLine();
- }
-
- ImGui::End();
- ImGui::PopStyleVar(); // Window padding
-
- // Restore original framebuffer target
- render_to_default_fb();
- }
-};
-
-static const char *paused_file_open(int flags,
- const char *filters,
- const char *default_path,
- const char *default_name)
-{
- bool is_running = runstate_is_running();
- if (is_running) {
- vm_stop(RUN_STATE_PAUSED);
- }
- const char *r = noc_file_dialog_open(flags, filters, default_path, default_name);
- if (is_running) {
- vm_start();
- }
-
- return r;
-}
-
-#define MAX_STRING_LEN 2048 // FIXME: Completely arbitrary and only used here
- // to give a buffer to ImGui for each field
-
-class SettingsWindow
-{
-public:
- bool is_open;
-
-private:
- bool dirty;
- bool pending_restart;
-
- char flashrom_path[MAX_STRING_LEN];
- char bootrom_path[MAX_STRING_LEN];
- char hdd_path[MAX_STRING_LEN];
- char eeprom_path[MAX_STRING_LEN];
-
-public:
- SettingsWindow()
- {
- is_open = false;
- dirty = false;
- pending_restart = false;
-
- flashrom_path[0] = '\0';
- bootrom_path[0] = '\0';
- hdd_path[0] = '\0';
- eeprom_path[0] = '\0';
- }
-
- ~SettingsWindow()
- {
- }
-
- void Load()
- {
- strncpy(flashrom_path, g_config.sys.files.flashrom_path, sizeof(flashrom_path)-1);
- strncpy(bootrom_path, g_config.sys.files.bootrom_path, sizeof(bootrom_path)-1);
- strncpy(hdd_path, g_config.sys.files.hdd_path, sizeof(hdd_path)-1);
- strncpy(eeprom_path, g_config.sys.files.eeprom_path, sizeof(eeprom_path)-1);
- dirty = false;
- }
-
- void Save()
- {
- xemu_settings_set_string(&g_config.sys.files.flashrom_path, flashrom_path);
- xemu_settings_set_string(&g_config.sys.files.bootrom_path, bootrom_path);
- xemu_settings_set_string(&g_config.sys.files.hdd_path, hdd_path);
- xemu_settings_set_string(&g_config.sys.files.eeprom_path, eeprom_path);
- xemu_queue_notification("Settings saved. Restart to apply updates.");
- pending_restart = true;
- g_config.general.show_welcome = false;
- }
-
- void FilePicker(const char *name, char *buf, size_t len, const char *filters)
- {
- ImGui::PushID(name);
- if (ImGui::InputText("", buf, len)) {
- dirty = true;
- }
- ImGui::SameLine();
- if (ImGui::Button("Browse...", ImVec2(100*g_ui_scale, 0))) {
- const char *selected = paused_file_open(NOC_FILE_DIALOG_OPEN, filters, buf, NULL);
- if ((selected != NULL) && (strcmp(buf, selected) != 0)) {
- strncpy(buf, selected, len-1);
- dirty = true;
- }
- }
- ImGui::PopID();
- }
-
- void Draw()
- {
- if (!is_open) return;
-
- ImGui::SetNextWindowContentSize(ImVec2(550.0f*g_ui_scale, 0.0f));
- if (!ImGui::Begin("Settings", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize))
- {
- ImGui::End();
- return;
- }
-
- if (ImGui::IsWindowAppearing()) {
- Load();
- }
-
- const char *rom_file_filters = ".bin Files\0*.bin\0.rom Files\0*.rom\0All Files\0*.*\0";
- const char *qcow_file_filters = ".qcow2 Files\0*.qcow2\0All Files\0*.*\0";
-
- ImGui::Columns(2, "", false);
- ImGui::SetColumnWidth(0, ImGui::GetWindowWidth()*0.25);
-
- ImGui::Text("Flash (BIOS) File");
- ImGui::NextColumn();
- float picker_width = ImGui::GetColumnWidth()-120*g_ui_scale;
- ImGui::SetNextItemWidth(picker_width);
- FilePicker("###Flash", flashrom_path, sizeof(flashrom_path), rom_file_filters);
- ImGui::NextColumn();
-
- ImGui::Text("MCPX Boot ROM File");
- ImGui::NextColumn();
- ImGui::SetNextItemWidth(picker_width);
- FilePicker("###BootROM", bootrom_path, sizeof(bootrom_path), rom_file_filters);
- ImGui::NextColumn();
-
- ImGui::Text("Hard Disk Image File");
- ImGui::NextColumn();
- ImGui::SetNextItemWidth(picker_width);
- FilePicker("###HDD", hdd_path, sizeof(hdd_path), qcow_file_filters);
- ImGui::NextColumn();
-
- ImGui::Text("EEPROM File");
- ImGui::NextColumn();
- ImGui::SetNextItemWidth(picker_width);
- FilePicker("###EEPROM", eeprom_path, sizeof(eeprom_path), rom_file_filters);
- ImGui::NextColumn();
-
- ImGui::Text("System Memory");
- ImGui::NextColumn();
- ImGui::SetNextItemWidth(ImGui::GetColumnWidth()*0.5);
- ImGui::Combo("###mem", &g_config.sys.mem_limit, "64 MiB\0" "128 MiB\0");
- ImGui::NextColumn();
-
- ImGui::Dummy(ImVec2(0,0));
- ImGui::NextColumn();
- ImGui::Checkbox("Skip startup animation", &g_config.general.misc.skip_boot_anim);
- ImGui::NextColumn();
-
-#if defined(_WIN32)
- ImGui::Dummy(ImVec2(0,0));
- ImGui::NextColumn();
- ImGui::Checkbox("Check for updates on startup", &g_config.general.updates.check);
- ImGui::NextColumn();
-#endif
-
- ImGui::Columns(1);
-
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
- ImGui::Separator();
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
-
- Hyperlink("Help", "https://xemu.app/docs/getting-started/");
- ImGui::SameLine();
-
- const char *msg = NULL;
- if (dirty) {
- msg = "Warning: Unsaved changes!";
- } else if (pending_restart) {
- msg = "Restart to apply updates";
- }
-
- if (msg) {
- ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2.0);
- ImGui::Text("%s", msg);
- ImGui::SameLine();
- }
-
- ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+10)*g_ui_scale);
- ImGui::SetItemDefaultFocus();
- if (ImGui::Button("Save", ImVec2(120*g_ui_scale, 0))) {
- Save();
- dirty = false;
- pending_restart = true;
- }
-
- ImGui::End();
- }
-};
-
-static const char *get_os_platform(void)
-{
- const char *platform_name;
-
-#if defined(__linux__)
- platform_name = "Linux";
-#elif defined(_WIN32)
- platform_name = "Windows";
-#elif defined(__APPLE__)
- platform_name = "macOS";
-#else
- platform_name = "Unknown";
-#endif
- return platform_name;
-}
-
-#ifndef _WIN32
-#ifdef CONFIG_CPUID_H
-#include
-#endif
-#endif
-
-const char *xemu_get_cpu_info(void)
-{
- const char *cpu_info = "";
-#ifdef CONFIG_CPUID_H
- static uint32_t brand[12];
- if (__get_cpuid_max(0x80000004, NULL)) {
- __get_cpuid(0x80000002, brand+0x0, brand+0x1, brand+0x2, brand+0x3);
- __get_cpuid(0x80000003, brand+0x4, brand+0x5, brand+0x6, brand+0x7);
- __get_cpuid(0x80000004, brand+0x8, brand+0x9, brand+0xa, brand+0xb);
- }
- cpu_info = (const char *)brand;
-#endif
- // FIXME: Support other architectures (e.g. ARM)
- return cpu_info;
-}
-
-class AboutWindow
-{
-public:
- bool is_open;
-
-private:
- char build_info_text[256];
- char platform_info_text[350];
-
-public:
- AboutWindow()
- {
- snprintf(build_info_text, sizeof(build_info_text),
- "Version: %s\nBranch: %s\nCommit: %s\nDate: %s",
- xemu_version, xemu_branch, xemu_commit, xemu_date);
- }
-
- void Draw()
- {
- if (!is_open) return;
-
- ImGui::SetNextWindowContentSize(ImVec2(400.0f*g_ui_scale, 0.0f));
- if (!ImGui::Begin("About", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize))
- {
- ImGui::End();
- return;
- }
-
- static uint32_t time_start = 0;
- if (ImGui::IsWindowAppearing()) {
- const char *gl_shader_version = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
- const char *gl_version = (const char*)glGetString(GL_VERSION);
- const char *gl_renderer = (const char*)glGetString(GL_RENDERER);
- const char *gl_vendor = (const char*)glGetString(GL_VENDOR);
-
- snprintf(platform_info_text, sizeof(platform_info_text),
- "CPU: %s\nOS Platform: %s\nOS Version: %s\nManufacturer: %s\n"
- "GPU Model: %s\nDriver: %s\nShader: %s",
- xemu_get_cpu_info(), get_os_platform(), xemu_get_os_info(), gl_vendor,
- gl_renderer, gl_version, gl_shader_version);
- // FIXME: Show BIOS/BootROM hash
-
- time_start = SDL_GetTicks();
- }
- uint32_t now = SDL_GetTicks() - time_start;
-
- ImGui::SetCursorPosY(ImGui::GetCursorPosY()-50*g_ui_scale);
- ImGui::SetCursorPosX((ImGui::GetWindowWidth()-256*g_ui_scale)/2);
-
- ImTextureID id = (ImTextureID)(intptr_t)render_to_fbo(logo_fbo);
- float t_w = 256.0;
- float t_h = 256.0;
- float x_off = 0;
- ImGui::Image(id,
- ImVec2((t_w-x_off)*g_ui_scale, t_h*g_ui_scale),
- ImVec2(x_off/t_w, t_h/t_h),
- ImVec2(t_w/t_w, 0));
- if (ImGui::IsItemClicked()) {
- time_start = SDL_GetTicks();
- }
- render_logo(now, 0x42e335ff, 0x42e335ff, 0x00000000);
- render_to_default_fb();
- ImGui::SetCursorPosX(10*g_ui_scale);
-
- ImGui::SetCursorPosY(ImGui::GetCursorPosY()-100*g_ui_scale);
- ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(xemu_version).x)/2);
- ImGui::Text("%s", xemu_version);
-
- ImGui::SetCursorPosX(10*g_ui_scale);
- ImGui::Dummy(ImVec2(0,20*g_ui_scale));
-
- const char *msg = "Visit https://xemu.app for more information";
- ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2);
- Hyperlink(msg, "https://xemu.app");
-
- ImGui::Dummy(ImVec2(0,40*g_ui_scale));
-
- ImGui::PushFont(g_fixed_width_font);
- ImGui::InputTextMultiline("##build_info", build_info_text, sizeof(build_info_text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 5), ImGuiInputTextFlags_ReadOnly);
- ImGui::InputTextMultiline("##platform_info", platform_info_text, sizeof(platform_info_text), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 8), ImGuiInputTextFlags_ReadOnly);
- ImGui::PopFont();
-
- ImGui::End();
- }
-};
-
-class NetworkInterface
-{
-public:
- std::string pcap_name;
- std::string description;
- std::string friendlyname;
-
- NetworkInterface(pcap_if_t *pcap_desc, char *_friendlyname = NULL)
- {
- pcap_name = pcap_desc->name;
- description = pcap_desc->description ?: pcap_desc->name;
- if (_friendlyname) {
- char *tmp = g_strdup_printf("%s (%s)", _friendlyname, description.c_str());
- friendlyname = tmp;
- g_free((gpointer)tmp);
- } else {
- friendlyname = description;
- }
- }
-};
-
-class NetworkInterfaceManager
-{
-public:
- std::vector> ifaces;
- NetworkInterface *current_iface;
- bool failed_to_load_lib;
-
- NetworkInterfaceManager()
- {
- current_iface = NULL;
- failed_to_load_lib = false;
- }
-
- void refresh(void)
- {
- pcap_if_t *alldevs, *iter;
- char err[PCAP_ERRBUF_SIZE];
-
- if (xemu_net_is_enabled()) {
- return;
- }
-
-#if defined(_WIN32)
- if (pcap_load_library()) {
- failed_to_load_lib = true;
- return;
- }
-#endif
-
- ifaces.clear();
- current_iface = NULL;
-
- if (pcap_findalldevs(&alldevs, err)) {
- return;
- }
-
- for (iter=alldevs; iter != NULL; iter=iter->next) {
-#if defined(_WIN32)
- char *friendlyname = get_windows_interface_friendly_name(iter->name);
- ifaces.emplace_back(new NetworkInterface(iter, friendlyname));
- if (friendlyname) {
- g_free((gpointer)friendlyname);
- }
-#else
- ifaces.emplace_back(new NetworkInterface(iter));
-#endif
- if (!strcmp(g_config.net.pcap.netif, iter->name)) {
- current_iface = ifaces.back().get();
- }
- }
-
- pcap_freealldevs(alldevs);
- }
-
- void select(NetworkInterface &iface)
- {
- current_iface = &iface;
- xemu_settings_set_string(&g_config.net.pcap.netif,
- iface.pcap_name.c_str());
- }
-
- bool is_current(NetworkInterface &iface)
- {
- return &iface == current_iface;
- }
-};
-
-
-class NetworkWindow
-{
-public:
- bool is_open;
- char remote_addr[64];
- char local_addr[64];
- std::unique_ptr iface_mgr;
-
- NetworkWindow()
- {
- is_open = false;
- }
-
- ~NetworkWindow()
- {
- }
-
- void Draw()
- {
- if (!is_open) return;
-
- ImGui::SetNextWindowContentSize(ImVec2(500.0f*g_ui_scale, 0.0f));
- if (!ImGui::Begin("Network", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) {
- ImGui::End();
- return;
- }
-
- if (ImGui::IsWindowAppearing()) {
- strncpy(remote_addr, g_config.net.udp.remote_addr, sizeof(remote_addr)-1);
- strncpy(local_addr, g_config.net.udp.bind_addr, sizeof(local_addr)-1);
- }
-
- ImGuiInputTextFlags flg = 0;
- bool is_enabled = xemu_net_is_enabled();
- if (is_enabled) {
- flg |= ImGuiInputTextFlags_ReadOnly;
- }
-
- ImGui::Columns(2, "", false);
- ImGui::SetColumnWidth(0, ImGui::GetWindowWidth()*0.33);
-
- ImGui::Text("Attached To");
- ImGui::SameLine(); HelpMarker("The network backend which the emulated NIC interacts with");
- ImGui::NextColumn();
- if (is_enabled) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f);
- int temp_backend = g_config.net.backend; // Temporary to make backend combo read-only (FIXME: surely there's a nicer way)
- ImGui::Combo("##backend", is_enabled ? &temp_backend : &g_config.net.backend, "NAT\0UDP Tunnel\0Bridged Adapter\0");
- if (is_enabled) ImGui::PopStyleVar();
- ImGui::SameLine();
- if (g_config.net.backend == CONFIG_NET_BACKEND_NAT) {
- HelpMarker("User-mode TCP/IP stack with network address translation");
- } else if (g_config.net.backend == CONFIG_NET_BACKEND_UDP) {
- HelpMarker("Tunnels link-layer traffic to a remote host via UDP");
- } else if (g_config.net.backend == CONFIG_NET_BACKEND_PCAP) {
- HelpMarker("Bridges with a host network interface");
- }
- ImGui::NextColumn();
-
- if (g_config.net.backend == CONFIG_NET_BACKEND_UDP) {
- ImGui::Text("Remote Host");
- ImGui::SameLine(); HelpMarker("The remote : to forward packets to (e.g. 1.2.3.4:9368)");
- ImGui::NextColumn();
- float w = ImGui::GetColumnWidth()-10*g_ui_scale;
- ImGui::SetNextItemWidth(w);
- if (is_enabled) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f);
- ImGui::InputText("###remote_host", remote_addr, sizeof(remote_addr), flg);
- if (is_enabled) ImGui::PopStyleVar();
- ImGui::NextColumn();
-
- ImGui::Text("Local Host");
- ImGui::SameLine(); HelpMarker("The local : to receive packets on (e.g. 0.0.0.0:9368)");
- ImGui::NextColumn();
- ImGui::SetNextItemWidth(w);
- if (is_enabled) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f);
- ImGui::InputText("###local_host", local_addr, sizeof(local_addr), flg);
- if (is_enabled) ImGui::PopStyleVar();
- ImGui::NextColumn();
- } else if (g_config.net.backend == CONFIG_NET_BACKEND_PCAP) {
- static bool should_refresh = true;
- if (iface_mgr.get() == nullptr) {
- iface_mgr.reset(new NetworkInterfaceManager());
- iface_mgr->refresh();
- }
-
- if (iface_mgr->failed_to_load_lib) {
-#if defined(_WIN32)
- ImGui::Columns(1);
- ImGui::Dummy(ImVec2(0,20*g_ui_scale));
- const char *msg = "WinPcap/npcap library could not be loaded.\n"
- "To use this attachment, please install npcap.";
- ImGui::SetCursorPosX(ImGui::GetCursorPosX() + (ImGui::GetColumnWidth() - g_ui_scale*ImGui::CalcTextSize(msg).x)/2);
- ImGui::Text("%s", msg);
- ImGui::Dummy(ImVec2(0,10*g_ui_scale));
- ImGui::SetCursorPosX((ImGui::GetWindowWidth()-120*g_ui_scale)/2);
- if (ImGui::Button("Install npcap", ImVec2(120*g_ui_scale, 0))) {
- xemu_open_web_browser("https://nmap.org/npcap/");
- }
- ImGui::Dummy(ImVec2(0,10*g_ui_scale));
-#endif
- } else {
- ImGui::Text("Network Interface");
- ImGui::SameLine(); HelpMarker("Host network interface to bridge with");
- ImGui::NextColumn();
-
- float w = ImGui::GetColumnWidth()-10*g_ui_scale;
- ImGui::SetNextItemWidth(w);
- const char *selected_display_name = (
- iface_mgr->current_iface
- ? iface_mgr->current_iface->friendlyname.c_str()
- : g_config.net.pcap.netif
- );
- if (is_enabled) ImGui::PushStyleVar(ImGuiStyleVar_Alpha, 0.6f);
- if (ImGui::BeginCombo("###network_iface", selected_display_name)) {
- if (should_refresh) {
- iface_mgr->refresh();
- should_refresh = false;
- }
- int i = 0;
- for (auto& iface : iface_mgr->ifaces) {
- bool is_selected = iface_mgr->is_current((*iface));
- ImGui::PushID(i++);
- if (ImGui::Selectable(iface->friendlyname.c_str(), is_selected)) {
- if (!is_enabled) iface_mgr->select((*iface));
- }
- if (is_selected) ImGui::SetItemDefaultFocus();
- ImGui::PopID();
- }
- ImGui::EndCombo();
- } else {
- should_refresh = true;
- }
- if (is_enabled) ImGui::PopStyleVar();
-
- ImGui::NextColumn();
- }
- }
-
- ImGui::Columns(1);
-
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
- ImGui::Separator();
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
-
- Hyperlink("Help", "https://xemu.app/docs/networking/");
-
- ImGui::SameLine();
- ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+10)*g_ui_scale);
- ImGui::SetItemDefaultFocus();
- if (ImGui::Button(is_enabled ? "Disable" : "Enable", ImVec2(120*g_ui_scale, 0))) {
- if (!is_enabled) {
- xemu_settings_set_string(&g_config.net.udp.remote_addr, remote_addr);
- xemu_settings_set_string(&g_config.net.udp.bind_addr, local_addr);
- xemu_net_enable();
- } else {
- xemu_net_disable();
- }
- }
-
- ImGui::End();
- }
-};
-
-class CompatibilityReporter
-{
-public:
- CompatibilityReport report;
- bool dirty;
- bool is_open;
- bool is_xbe_identified;
- bool did_send, send_result;
- char token_buf[512];
- int playability;
- char description[1024];
- std::string serialized_report;
-
- CompatibilityReporter()
- {
- is_open = false;
-
- report.token = "";
- report.xemu_version = xemu_version;
- report.xemu_branch = xemu_branch;
- report.xemu_commit = xemu_commit;
- report.xemu_date = xemu_date;
-
- report.os_platform = get_os_platform();
- report.os_version = xemu_get_os_info();
- report.cpu = xemu_get_cpu_info();
- dirty = true;
- is_xbe_identified = false;
- did_send = send_result = false;
- }
-
- ~CompatibilityReporter()
- {
- }
-
- void Draw()
- {
- if (!is_open) return;
-
- const char *playability_names[] = {
- "Broken",
- "Intro",
- "Starts",
- "Playable",
- "Perfect",
- };
-
- const char *playability_descriptions[] = {
- "This title crashes very soon after launching, or displays nothing at all.",
- "This title displays an intro sequence, but fails to make it to gameplay.",
- "This title starts, but may crash or have significant issues.",
- "This title is playable, but may have minor issues.",
- "This title is playable from start to finish with no noticable issues."
- };
-
- ImGui::SetNextWindowContentSize(ImVec2(550.0f*g_ui_scale, 0.0f));
- if (!ImGui::Begin("Report Compatibility", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) {
- ImGui::End();
- return;
- }
-
- if (ImGui::IsWindowAppearing()) {
- report.gl_vendor = (const char *)glGetString(GL_VENDOR);
- report.gl_renderer = (const char *)glGetString(GL_RENDERER);
- report.gl_version = (const char *)glGetString(GL_VERSION);
- report.gl_shading_language_version = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
- struct xbe *xbe = xemu_get_xbe_info();
- is_xbe_identified = xbe != NULL;
- if (is_xbe_identified) {
- report.SetXbeData(xbe);
- }
- did_send = send_result = false;
-
- playability = 3; // Playable
- report.compat_rating = playability_names[playability];
- description[0] = '\x00';
- report.compat_comments = description;
-
- strncpy(token_buf, g_config.general.user_token, sizeof(token_buf)-1);
- report.token = token_buf;
-
- dirty = true;
- }
-
- if (!is_xbe_identified) {
- ImGui::TextWrapped(
- "An XBE could not be identified. Please launch an official "
- "Xbox title to submit a compatibility report.");
- ImGui::End();
- return;
- }
-
- ImGui::TextWrapped(
- "If you would like to help improve xemu by submitting a compatibility report for this "
- "title, please select an appropriate playability level, enter a "
- "brief description, then click 'Send'."
- "\n\n"
- "Note: By submitting a report, you acknowledge and consent to "
- "collection, archival, and publication of information as outlined "
- "in 'Privacy Disclosure' below.");
-
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
- ImGui::Separator();
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
-
- ImGui::Columns(2, "", false);
- ImGui::SetColumnWidth(0, ImGui::GetWindowWidth()*0.25);
-
- ImGui::Text("User Token");
- ImGui::SameLine();
- HelpMarker("This is a unique access token used to authorize submission of the report. To request a token, click 'Get Token'.");
- ImGui::NextColumn();
- float item_width = ImGui::GetColumnWidth()*0.75-20*g_ui_scale;
- ImGui::SetNextItemWidth(item_width);
- ImGui::PushFont(g_fixed_width_font);
- if (ImGui::InputText("###UserToken", token_buf, sizeof(token_buf), 0)) {
- report.token = token_buf;
- dirty = true;
- }
- ImGui::PopFont();
- ImGui::SameLine();
- if (ImGui::Button("Get Token")) {
- xemu_open_web_browser("https://reports.xemu.app");
- }
- ImGui::NextColumn();
-
- ImGui::Text("Playability");
- ImGui::NextColumn();
- ImGui::SetNextItemWidth(item_width);
- if (ImGui::Combo("###PlayabilityRating", &playability,
- "Broken\0" "Intro/Menus\0" "Starts\0" "Playable\0" "Perfect\0")) {
- report.compat_rating = playability_names[playability];
- dirty = true;
- }
- ImGui::SameLine();
- HelpMarker(playability_descriptions[playability]);
- ImGui::NextColumn();
-
- ImGui::Columns(1);
-
- ImGui::Text("Description");
- if (ImGui::InputTextMultiline("###desc", description, sizeof(description), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 6), 0)) {
- report.compat_comments = description;
- dirty = true;
- }
-
- if (ImGui::TreeNode("Report Details")) {
- ImGui::PushFont(g_fixed_width_font);
- if (dirty) {
- serialized_report = report.GetSerializedReport();
- dirty = false;
- }
- ImGui::InputTextMultiline("##build_info", (char*)serialized_report.c_str(), strlen(serialized_report.c_str())+1, ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 7), ImGuiInputTextFlags_ReadOnly);
- ImGui::PopFont();
- ImGui::TreePop();
- }
-
- if (ImGui::TreeNode("Privacy Disclosure (Please read before submission!)")) {
- ImGui::TextWrapped(
- "By volunteering to submit a compatibility report, basic information about your "
- "computer is collected, including: your operating system version, CPU model, "
- "graphics card/driver information, and details about the title which are "
- "extracted from the executable in memory. The contents of this report can be "
- "seen before submission by expanding 'Report Details'."
- "\n\n"
- "Like many websites, upon submission, the public IP address of your computer is "
- "also recorded with your report. If provided, the identity associated with your "
- "token is also recorded."
- "\n\n"
- "This information will be archived and used to analyze, resolve problems with, "
- "and improve the application. This information may be made publicly visible, "
- "for example: to anyone who wishes to see the playability status of a title, as "
- "indicated by your report.");
- ImGui::TreePop();
- }
-
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
- ImGui::Separator();
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
-
- if (did_send) {
- if (send_result) {
- ImGui::Text("Sent! Thanks.");
- } else {
- ImGui::Text("Error: %s (%d)", report.GetResultMessage().c_str(), report.GetResultCode());
- }
- ImGui::SameLine();
- }
-
- ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+10)*g_ui_scale);
-
- ImGui::SetItemDefaultFocus();
- if (ImGui::Button("Send", ImVec2(120*g_ui_scale, 0))) {
- did_send = true;
- send_result = report.Send();
- if (send_result) {
- is_open = false;
- xemu_settings_set_string(&g_config.general.user_token, token_buf);
- }
- }
-
- ImGui::End();
- }
-};
-
-#include
-
-float mix(float a, float b, float t)
-{
- return a*(1.0-t) + (b-a)*t;
-}
-
-class DebugApuWindow
-{
-public:
- bool is_open;
-
- DebugApuWindow()
- {
- is_open = false;
- }
-
- ~DebugApuWindow()
- {
- }
-
- void Draw()
- {
- if (!is_open) return;
-
- ImGui::SetNextWindowContentSize(ImVec2(600.0f*g_ui_scale, 0.0f));
- if (!ImGui::Begin("Audio Debug", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) {
- ImGui::End();
- return;
- }
-
- const struct McpxApuDebug *dbg = mcpx_apu_get_debug_info();
-
-
- ImGui::Columns(2, "", false);
- int now = SDL_GetTicks() % 1000;
- float t = now/1000.0f;
- float freq = 1;
- float v = fabs(sin(M_PI*t*freq));
- float c_active = mix(0.4, 0.97, v);
- float c_inactive = 0.2f;
-
- int voice_monitor = -1;
- int voice_info = -1;
- int voice_mute = -1;
-
- // Color buttons, demonstrate using PushID() to add unique identifier in the ID stack, and changing style.
- ImGui::PushFont(g_fixed_width_font);
- ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
- ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
- ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
- for (int i = 0; i < 256; i++)
- {
- if (i % 16) {
- ImGui::SameLine();
- }
-
- float c, s, h;
- h = 0.6;
- if (dbg->vp.v[i].active) {
- if (dbg->vp.v[i].paused) {
- c = c_inactive;
- s = 0.4;
- } else {
- c = c_active;
- s = 0.7;
- }
- if (mcpx_apu_debug_is_muted(i)) {
- h = 1.0;
- }
- } else {
- c = c_inactive;
- s = 0;
- }
-
- ImGui::PushID(i);
- ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(h, s, c));
- ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(h, s, 0.8));
- ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(h, 0.8f, 1.0));
- char buf[12];
- snprintf(buf, sizeof(buf), "%02x", i);
- ImGui::Button(buf);
- if (/*dbg->vp.v[i].active &&*/ ImGui::IsItemHovered()) {
- voice_monitor = i;
- voice_info = i;
- }
- if (ImGui::IsItemClicked(1)) {
- voice_mute = i;
- }
- ImGui::PopStyleColor(3);
- ImGui::PopID();
- }
- ImGui::PopStyleVar(3);
- ImGui::PopFont();
-
- if (voice_info >= 0) {
- const struct McpxApuDebugVoice *voice = &dbg->vp.v[voice_info];
- ImGui::BeginTooltip();
- bool is_paused = voice->paused;
- ImGui::Text("Voice 0x%x/%d %s", voice_info, voice_info, is_paused ? "(Paused)" : "");
- ImGui::SameLine();
- ImGui::Text(voice->stereo ? "Stereo" : "Mono");
-
- ImGui::Separator();
- ImGui::PushFont(g_fixed_width_font);
-
- const char *noyes[2] = { "NO", "YES" };
- ImGui::Text("Stream: %-3s Loop: %-3s Persist: %-3s Multipass: %-3s "
- "Linked: %-3s",
- noyes[voice->stream], noyes[voice->loop],
- noyes[voice->persist], noyes[voice->multipass],
- noyes[voice->linked]);
-
- const char *cs[4] = { "1 byte", "2 bytes", "ADPCM", "4 bytes" };
- const char *ss[4] = {
- "Unsigned 8b PCM",
- "Signed 16b PCM",
- "Signed 24b PCM",
- "Signed 32b PCM"
- };
-
- assert(voice->container_size < 4);
- assert(voice->sample_size < 4);
- ImGui::Text("Container Size: %s, Sample Size: %s, Samples per Block: %d",
- cs[voice->container_size], ss[voice->sample_size], voice->samples_per_block);
- ImGui::Text("Rate: %f (%d Hz)", voice->rate, (int)(48000.0/voice->rate));
- ImGui::Text("EBO=%d CBO=%d LBO=%d BA=%x",
- voice->ebo, voice->cbo, voice->lbo, voice->ba);
- ImGui::Text("Mix: ");
- for (int i = 0; i < 8; i++) {
- if (i == 4) ImGui::Text(" ");
- ImGui::SameLine();
- char buf[64];
- if (voice->vol[i] == 0xFFF) {
- snprintf(buf, sizeof(buf),
- "Bin %2d (MUTE) ", voice->bin[i]);
- } else {
- snprintf(buf, sizeof(buf),
- "Bin %2d (-%.3f) ", voice->bin[i],
- (float)((voice->vol[i] >> 6) & 0x3f) +
- (float)((voice->vol[i] >> 0) & 0x3f) / 64.0);
- }
- ImGui::Text("%-17s", buf);
- }
- ImGui::PopFont();
- ImGui::EndTooltip();
- }
-
- if (voice_monitor >= 0) {
- mcpx_apu_debug_isolate_voice(voice_monitor);
- } else {
- mcpx_apu_debug_clear_isolations();
- }
- if (voice_mute >= 0) {
- mcpx_apu_debug_toggle_mute(voice_mute);
- }
-
- ImGui::SameLine();
- ImGui::SetColumnWidth(0, ImGui::GetCursorPosX());
- ImGui::NextColumn();
-
- ImGui::PushFont(g_fixed_width_font);
- ImGui::Text("Frames: %04d", dbg->frames_processed);
- ImGui::Text("GP Cycles: %04d", dbg->gp.cycles);
- ImGui::Text("EP Cycles: %04d", dbg->ep.cycles);
- bool color = (dbg->utilization > 0.9);
- if (color) ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
- ImGui::Text("Utilization: %.2f%%", (dbg->utilization*100));
- if (color) ImGui::PopStyleColor();
- ImGui::PopFont();
-
- ImGui::Separator();
-
- static int mon = 0;
- mon = mcpx_apu_debug_get_monitor();
- if (ImGui::Combo("Monitor", &mon, "AC97\0VP Only\0GP Only\0EP Only\0GP/EP if enabled\0")) {
- mcpx_apu_debug_set_monitor(mon);
- }
-
- static bool gp_realtime;
- gp_realtime = dbg->gp_realtime;
- if (ImGui::Checkbox("GP Realtime\n", &gp_realtime)) {
- mcpx_apu_debug_set_gp_realtime_enabled(gp_realtime);
- }
-
- static bool ep_realtime;
- ep_realtime = dbg->ep_realtime;
- if (ImGui::Checkbox("EP Realtime\n", &ep_realtime)) {
- mcpx_apu_debug_set_ep_realtime_enabled(ep_realtime);
- }
-
- ImGui::Columns(1);
- ImGui::End();
- }
-};
-
-
-
-// utility structure for realtime plot
-struct ScrollingBuffer {
- int MaxSize;
- int Offset;
- ImVector Data;
- ScrollingBuffer() {
- MaxSize = 2000;
- Offset = 0;
- Data.reserve(MaxSize);
- }
- void AddPoint(float x, float y) {
- if (Data.size() < MaxSize)
- Data.push_back(ImVec2(x,y));
- else {
- Data[Offset] = ImVec2(x,y);
- Offset = (Offset + 1) % MaxSize;
- }
- }
- void Erase() {
- if (Data.size() > 0) {
- Data.shrink(0);
- Offset = 0;
- }
- }
-};
-
-class DebugVideoWindow
-{
-public:
- bool is_open;
- bool transparent;
-
- DebugVideoWindow()
- {
- is_open = false;
- transparent = false;
- }
-
- ~DebugVideoWindow()
- {
- }
-
- void Draw()
- {
- if (!is_open) return;
-
- float alpha = transparent ? 0.2 : 1.0;
- PushWindowTransparencySettings(transparent, 0.2);
- ImGui::SetNextWindowSize(ImVec2(600.0f*g_ui_scale, 150.0f*g_ui_scale), ImGuiCond_Once);
- if (ImGui::Begin("Video Debug", &is_open)) {
-
- double x_start, x_end;
- static ImPlotAxisFlags rt_axis = ImPlotAxisFlags_NoTickLabels;
- ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(5,5));
- ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f);
- static ScrollingBuffer fps;
- static float t = 0;
- if (runstate_is_running()) {
- t += ImGui::GetIO().DeltaTime;
- fps.AddPoint(t, g_nv2a_stats.increment_fps);
- }
- x_start = t - 10.0;
- x_end = t;
- ImPlot::SetNextPlotLimitsX(x_start, x_end, ImGuiCond_Always);
- ImPlot::SetNextPlotLimitsY(0, 65, ImGuiCond_Always);
-
- float plot_width = 0.5 * (ImGui::GetWindowSize().x -
- 2 * ImGui::GetStyle().WindowPadding.x -
- ImGui::GetStyle().ItemSpacing.x);
-
- ImGui::SetNextWindowBgAlpha(alpha);
- if (ImPlot::BeginPlot("##ScrollingFPS", NULL, NULL, ImVec2(plot_width,75*g_ui_scale), 0, rt_axis, rt_axis | ImPlotAxisFlags_Lock)) {
- if (fps.Data.size() > 0) {
- ImPlot::PlotShaded("##fps", &fps.Data[0].x, &fps.Data[0].y, fps.Data.size(), 0, fps.Offset, 2 * sizeof(float));
- ImPlot::PlotLine("##fps", &fps.Data[0].x, &fps.Data[0].y, fps.Data.size(), fps.Offset, 2 * sizeof(float));
- }
- ImPlot::AnnotateClamped(x_start, 65, ImVec2(0,0), ImPlot::GetLastItemColor(), "FPS: %d", g_nv2a_stats.increment_fps);
- ImPlot::EndPlot();
- }
-
- ImGui::SameLine();
-
- x_end = g_nv2a_stats.frame_count;
- x_start = x_end - NV2A_PROF_NUM_FRAMES;
-
- ImPlot::SetNextPlotLimitsX(x_start, x_end, ImGuiCond_Always);
- ImPlot::SetNextPlotLimitsY(0, 100, ImGuiCond_Always);
- ImPlot::PushStyleColor(ImPlotCol_Line, ImPlot::GetColormapColor(1));
- ImGui::SetNextWindowBgAlpha(alpha);
- if (ImPlot::BeginPlot("##ScrollingMSPF", NULL, NULL, ImVec2(plot_width,75*g_ui_scale), 0, rt_axis, rt_axis | ImPlotAxisFlags_Lock)) {
- ImPlot::PlotShaded("##mspf", &g_nv2a_stats.frame_history[0].mspf, NV2A_PROF_NUM_FRAMES, 0, 1, x_start, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
- ImPlot::PlotLine("##mspf", &g_nv2a_stats.frame_history[0].mspf, NV2A_PROF_NUM_FRAMES, 1, x_start, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
- ImPlot::AnnotateClamped(x_start, 100, ImVec2(0,0), ImPlot::GetLastItemColor(), "MSPF: %d", g_nv2a_stats.frame_history[(g_nv2a_stats.frame_ptr - 1) % NV2A_PROF_NUM_FRAMES].mspf);
- ImPlot::EndPlot();
- }
- ImPlot::PopStyleColor();
-
- if (ImGui::TreeNode("Advanced")) {
- ImPlot::SetNextPlotLimitsX(x_start, x_end, ImGuiCond_Always);
- ImPlot::SetNextPlotLimitsY(0, 1500, ImGuiCond_Always);
- ImGui::SetNextWindowBgAlpha(alpha);
- if (ImPlot::BeginPlot("##ScrollingDraws", NULL, NULL, ImVec2(-1,500*g_ui_scale), 0, rt_axis, rt_axis | ImPlotAxisFlags_Lock)) {
- for (int i = 0; i < NV2A_PROF__COUNT; i++) {
- ImGui::PushID(i);
- char title[64];
- snprintf(title, sizeof(title), "%s: %d",
- nv2a_profile_get_counter_name(i),
- nv2a_profile_get_counter_value(i));
- ImPlot::PushStyleColor(ImPlotCol_Line, ImPlot::GetColormapColor(i));
- ImPlot::PushStyleColor(ImPlotCol_Fill, ImPlot::GetColormapColor(i));
- ImPlot::PlotLine(title, &g_nv2a_stats.frame_history[0].counters[i], NV2A_PROF_NUM_FRAMES, 1, x_start, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
- ImPlot::PopStyleColor(2);
- ImGui::PopID();
- }
- ImPlot::EndPlot();
- }
- ImGui::TreePop();
- }
-
- if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(2)) {
- transparent = !transparent;
- }
-
- ImPlot::PopStyleVar(2);
- }
- ImGui::End();
- ImGui::PopStyleColor(5);
- }
-};
-
-#if defined(_WIN32)
-class AutoUpdateWindow
-{
-protected:
- Updater updater;
-
-public:
- bool is_open;
-
- AutoUpdateWindow()
- {
- is_open = false;
- }
-
- ~AutoUpdateWindow()
- {
- }
-
- void check_for_updates_and_prompt_if_available()
- {
- updater.check_for_update([this](){
- is_open |= updater.is_update_available();
- });
- }
-
- void Draw()
- {
- if (!is_open) return;
- ImGui::SetNextWindowContentSize(ImVec2(550.0f*g_ui_scale, 0.0f));
- if (!ImGui::Begin("Update", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) {
- ImGui::End();
- return;
- }
-
- if (ImGui::IsWindowAppearing() && !updater.is_update_available()) {
- updater.check_for_update();
- }
-
- const char *status_msg[] = {
- "",
- "An error has occured. Try again.",
- "Checking for update...",
- "Downloading update...",
- "Update successful! Restart to launch updated version of xemu."
- };
- const char *available_msg[] = {
- "Update availability unknown.",
- "This version of xemu is up to date.",
- "An updated version of xemu is available!",
- };
-
- if (updater.get_status() == UPDATER_IDLE) {
- ImGui::Text(available_msg[updater.get_update_availability()]);
- } else {
- ImGui::Text(status_msg[updater.get_status()]);
- }
-
- if (updater.is_updating()) {
- ImGui::ProgressBar(updater.get_update_progress_percentage()/100.0f,
- ImVec2(-1.0f, 0.0f));
- }
-
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
- ImGui::Separator();
- ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
-
- float w = (130)*g_ui_scale;
- float bw = w + (10)*g_ui_scale;
- ImGui::SetCursorPosX(ImGui::GetWindowWidth()-bw);
-
- if (updater.is_checking_for_update() || updater.is_updating()) {
- if (ImGui::Button("Cancel", ImVec2(w, 0))) {
- updater.cancel();
- }
- } else {
- if (updater.is_pending_restart()) {
- if (ImGui::Button("Restart", ImVec2(w, 0))) {
- updater.restart_to_updated();
- }
- } else if (updater.is_update_available()) {
- if (ImGui::Button("Update", ImVec2(w, 0))) {
- updater.update();
- }
- } else {
- if (ImGui::Button("Check for Update", ImVec2(w, 0))) {
- updater.check_for_update();
- }
- }
- }
-
- ImGui::End();
- }
-};
-#endif
-
-static MonitorWindow monitor_window;
-static DebugApuWindow apu_window;
-static DebugVideoWindow video_window;
-static InputWindow input_window;
-static NetworkWindow network_window;
-static AboutWindow about_window;
-static SettingsWindow settings_window;
-static CompatibilityReporter compatibility_reporter_window;
-static NotificationManager notification_manager;
-#if defined(_WIN32)
-static AutoUpdateWindow update_window;
-#endif
-static std::deque g_errors;
-
-#ifdef CONFIG_RENDERDOC
-static bool capture_renderdoc_frame = false;
-#endif
-
-class FirstBootWindow
-{
-public:
- bool is_open;
-
- FirstBootWindow()
- {
- is_open = false;
- }
-
- ~FirstBootWindow()
- {
- }
-
- void Draw()
- {
- if (!is_open) return;
-
- ImVec2 size(400*g_ui_scale, 300*g_ui_scale);
- ImGuiIO& io = ImGui::GetIO();
-
- ImVec2 window_pos = ImVec2((io.DisplaySize.x - size.x)/2, (io.DisplaySize.y - size.y)/2);
- ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always);
-
- ImGui::SetNextWindowSize(size, ImGuiCond_Appearing);
- if (!ImGui::Begin("First Boot", &is_open, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDecoration)) {
- ImGui::End();
- return;
- }
-
- static uint32_t time_start = 0;
- if (ImGui::IsWindowAppearing()) {
- time_start = SDL_GetTicks();
- }
- uint32_t now = SDL_GetTicks() - time_start;
-
- ImGui::SetCursorPosY(ImGui::GetCursorPosY()-50*g_ui_scale);
- ImGui::SetCursorPosX((ImGui::GetWindowWidth()-256*g_ui_scale)/2);
-
- ImTextureID id = (ImTextureID)(intptr_t)render_to_fbo(logo_fbo);
- float t_w = 256.0;
- float t_h = 256.0;
- float x_off = 0;
- ImGui::Image(id,
- ImVec2((t_w-x_off)*g_ui_scale, t_h*g_ui_scale),
- ImVec2(x_off/t_w, t_h/t_h),
- ImVec2(t_w/t_w, 0));
- if (ImGui::IsItemClicked()) {
- time_start = SDL_GetTicks();
- }
- render_logo(now, 0x42e335ff, 0x42e335ff, 0x00000000);
- render_to_default_fb();
-
- ImGui::SetCursorPosY(ImGui::GetCursorPosY()-100*g_ui_scale);
- ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(xemu_version).x)/2);
- ImGui::Text("%s", xemu_version);
-
- ImGui::SetCursorPosX(10*g_ui_scale);
- ImGui::Dummy(ImVec2(0,20*g_ui_scale));
-
- const char *msg = "To get started, please configure machine settings.";
- ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2);
- ImGui::Text("%s", msg);
-
- ImGui::Dummy(ImVec2(0,20*g_ui_scale));
- ImGui::SetCursorPosX((ImGui::GetWindowWidth()-120*g_ui_scale)/2);
- if (ImGui::Button("Settings", ImVec2(120*g_ui_scale, 0))) {
- settings_window.is_open = true; // FIXME
- }
- ImGui::Dummy(ImVec2(0,20*g_ui_scale));
-
- msg = "Visit https://xemu.app for more information";
- ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2);
- Hyperlink(msg, "https://xemu.app");
-
- ImGui::End();
- }
-};
-
-static bool is_shortcut_key_pressed(int scancode)
-{
- ImGuiIO& io = ImGui::GetIO();
- const bool is_osx = io.ConfigMacOSXBehaviors;
- const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
- return is_shortcut_key && io.KeysDown[scancode] && (io.KeysDownDuration[scancode] == 0.0);
-}
-
-static void action_eject_disc(void)
-{
- xemu_settings_set_string(&g_config.sys.files.dvd_path, "");
- xemu_eject_disc();
-}
-
-static void action_load_disc(void)
-{
- const char *iso_file_filters = ".iso Files\0*.iso\0All Files\0*.*\0";
- const char *new_disc_path = paused_file_open(NOC_FILE_DIALOG_OPEN, iso_file_filters, g_config.sys.files.dvd_path, NULL);
- if (new_disc_path == NULL) {
- /* Cancelled */
- return;
- }
- xemu_settings_set_string(&g_config.sys.files.dvd_path, new_disc_path);
- xemu_load_disc(new_disc_path);
-}
-
-static void action_toggle_pause(void)
-{
- if (runstate_is_running()) {
- vm_stop(RUN_STATE_PAUSED);
- } else {
- vm_start();
- }
-}
-
-static void action_reset(void)
-{
- qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
-}
-
-static void action_shutdown(void)
-{
- qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
-}
-
-
-static bool is_key_pressed(int scancode)
-{
- ImGuiIO& io = ImGui::GetIO();
- return io.KeysDown[scancode] && (io.KeysDownDuration[scancode] == 0.0);
-}
-
-static void process_keyboard_shortcuts(void)
-{
- if (is_shortcut_key_pressed(SDL_SCANCODE_E)) {
- action_eject_disc();
- }
-
- if (is_shortcut_key_pressed(SDL_SCANCODE_O)) {
- action_load_disc();
- }
-
- if (is_shortcut_key_pressed(SDL_SCANCODE_P)) {
- action_toggle_pause();
- }
-
- if (is_shortcut_key_pressed(SDL_SCANCODE_R)) {
- action_reset();
- }
-
- if (is_shortcut_key_pressed(SDL_SCANCODE_Q)) {
- action_shutdown();
- }
-
- if (is_key_pressed(SDL_SCANCODE_GRAVE)) {
- monitor_window.toggle_open();
- }
-
-#if defined(DEBUG_NV2A_GL) && defined(CONFIG_RENDERDOC)
- if (is_key_pressed(SDL_SCANCODE_F10)) {
- nv2a_dbg_renderdoc_capture_frames(1);
- }
-#endif
-}
-
-#if defined(__APPLE__)
-#define SHORTCUT_MENU_TEXT(c) "Cmd+" #c
-#else
-#define SHORTCUT_MENU_TEXT(c) "Ctrl+" #c
-#endif
-
-static void ShowMainMenu()
-{
- bool running = runstate_is_running();
-
- if (ImGui::BeginMainMenuBar())
- {
- if (ImGui::BeginMenu("Machine"))
- {
- if (ImGui::MenuItem("Eject Disc", SHORTCUT_MENU_TEXT(E))) {
- action_eject_disc();
- }
- if (ImGui::MenuItem("Load Disc...", SHORTCUT_MENU_TEXT(O))) {
- action_load_disc();
- }
-
- ImGui::Separator();
-
- ImGui::MenuItem("Input", NULL, &input_window.is_open);
- ImGui::MenuItem("Network", NULL, &network_window.is_open);
- ImGui::MenuItem("Settings", NULL, &settings_window.is_open);
-
- ImGui::Separator();
-
- if (ImGui::MenuItem(running ? "Pause" : "Run", SHORTCUT_MENU_TEXT(P))) {
- action_toggle_pause();
- }
- if (ImGui::MenuItem("Reset", SHORTCUT_MENU_TEXT(R))) {
- action_reset();
- }
- if (ImGui::MenuItem("Shutdown", SHORTCUT_MENU_TEXT(Q))) {
- action_shutdown();
- }
- ImGui::EndMenu();
- }
-
- if (ImGui::BeginMenu("View"))
- {
- int ui_scale_combo = g_ui_scale - 1.0;
- if (ui_scale_combo < 0) ui_scale_combo = 0;
- if (ui_scale_combo > 1) ui_scale_combo = 1;
- if (ImGui::Combo("UI Scale", &ui_scale_combo, "1x\0" "2x\0")) {
- g_ui_scale = ui_scale_combo + 1;
- g_config.display.ui.scale = g_ui_scale;
- g_trigger_style_update = true;
- }
-
- int rendering_scale = nv2a_get_surface_scale_factor() - 1;
- if (ImGui::Combo("Rendering Scale", &rendering_scale, "1x\0" "2x\0" "3x\0" "4x\0" "5x\0" "6x\0" "7x\0" "8x\0" "9x\0" "10x\0")) {
- nv2a_set_surface_scale_factor(rendering_scale+1);
- }
-
- if (ImGui::Combo(
- "Scaling Mode", &g_config.display.ui.fit, "Center\0Scale\0Scale (Widescreen 16:9)\0Scale (4:3)\0Stretch\0")) {
- }
- ImGui::SameLine(); HelpMarker("Controls how the rendered content should be scaled into the window");
- if (ImGui::MenuItem("Fullscreen", SHORTCUT_MENU_TEXT(Alt+F), xemu_is_fullscreen(), true)) {
- xemu_toggle_fullscreen();
- }
-
- ImGui::EndMenu();
- }
-
- if (ImGui::BeginMenu("Debug"))
- {
- ImGui::MenuItem("Monitor", "~", &monitor_window.is_open);
- ImGui::MenuItem("Audio", NULL, &apu_window.is_open);
- ImGui::MenuItem("Video", NULL, &video_window.is_open);
-#if defined(DEBUG_NV2A_GL) && defined(CONFIG_RENDERDOC)
- if (nv2a_dbg_renderdoc_available()) {
- ImGui::MenuItem("RenderDoc: Capture", NULL, &capture_renderdoc_frame);
- }
-#endif
- ImGui::EndMenu();
- }
-
- if (ImGui::BeginMenu("Help"))
- {
- if (ImGui::MenuItem("Help", NULL))
- {
- xemu_open_web_browser("https://xemu.app/docs/getting-started/");
- }
-
- ImGui::MenuItem("Report Compatibility...", NULL, &compatibility_reporter_window.is_open);
-#if defined(_WIN32)
- ImGui::MenuItem("Check for Updates...", NULL, &update_window.is_open);
-#endif
-
- ImGui::Separator();
- ImGui::MenuItem("About", NULL, &about_window.is_open);
- ImGui::EndMenu();
- }
-
- g_main_menu_height = ImGui::GetWindowHeight();
- ImGui::EndMainMenuBar();
- }
-}
-
-static void InitializeStyle()
-{
- ImGuiIO& io = ImGui::GetIO();
-
- io.Fonts->Clear();
-
- ImFontConfig roboto_font_cfg = ImFontConfig();
- roboto_font_cfg.FontDataOwnedByAtlas = false;
- io.Fonts->AddFontFromMemoryTTF((void*)roboto_medium_data, roboto_medium_size, 16*g_ui_scale, &roboto_font_cfg);
-
- ImFontConfig font_cfg = ImFontConfig();
- font_cfg.OversampleH = font_cfg.OversampleV = 1;
- font_cfg.PixelSnapH = true;
- font_cfg.SizePixels = 13.0f*g_ui_scale;
- g_fixed_width_font = io.Fonts->AddFontDefault(&font_cfg);
-
- ImGui_ImplOpenGL3_CreateFontsTexture();
-
- ImGuiStyle style;
- style.WindowRounding = 8.0;
- style.FrameRounding = 8.0;
- style.GrabRounding = 12.0;
- style.PopupRounding = 5.0;
- style.ScrollbarRounding = 12.0;
- style.FramePadding.x = 10;
- style.FramePadding.y = 4;
- style.WindowBorderSize = 0;
- style.PopupBorderSize = 0;
- style.FrameBorderSize = 0;
- style.TabBorderSize = 0;
- ImGui::GetStyle() = style;
- ImGui::GetStyle().ScaleAllSizes(g_ui_scale);
-
- // Set default theme, override
- ImGui::StyleColorsDark();
-
- ImVec4* colors = ImGui::GetStyle().Colors;
- colors[ImGuiCol_Text] = ImVec4(0.86f, 0.93f, 0.89f, 0.78f);
- colors[ImGuiCol_TextDisabled] = ImVec4(0.86f, 0.93f, 0.89f, 0.28f);
- colors[ImGuiCol_WindowBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.98f);
- colors[ImGuiCol_ChildBg] = ImVec4(0.16f, 0.16f, 0.16f, 0.58f);
- colors[ImGuiCol_PopupBg] = ImVec4(0.16f, 0.16f, 0.16f, 0.90f);
- colors[ImGuiCol_Border] = ImVec4(0.11f, 0.11f, 0.11f, 0.60f);
- colors[ImGuiCol_BorderShadow] = ImVec4(0.16f, 0.16f, 0.16f, 0.00f);
- colors[ImGuiCol_FrameBg] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f);
- colors[ImGuiCol_FrameBgHovered] = ImVec4(0.28f, 0.71f, 0.25f, 0.78f);
- colors[ImGuiCol_FrameBgActive] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
- colors[ImGuiCol_TitleBg] = ImVec4(0.20f, 0.51f, 0.18f, 1.00f);
- colors[ImGuiCol_TitleBgActive] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
- colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.16f, 0.16f, 0.16f, 0.75f);
- colors[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 0.00f);
- colors[ImGuiCol_ScrollbarBg] = ImVec4(0.16f, 0.16f, 0.16f, 1.00f);
- colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.20f, 0.51f, 0.18f, 1.00f);
- colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.28f, 0.71f, 0.25f, 0.78f);
- colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
- colors[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
- colors[ImGuiCol_SliderGrab] = ImVec4(0.26f, 0.26f, 0.26f, 1.00f);
- colors[ImGuiCol_SliderGrabActive] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
- colors[ImGuiCol_Button] = ImVec4(0.36f, 0.36f, 0.36f, 1.00f);
- colors[ImGuiCol_ButtonHovered] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
- colors[ImGuiCol_ButtonActive] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
- colors[ImGuiCol_Header] = ImVec4(0.28f, 0.71f, 0.25f, 0.76f);
- colors[ImGuiCol_HeaderHovered] = ImVec4(0.28f, 0.71f, 0.25f, 0.86f);
- colors[ImGuiCol_HeaderActive] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
- colors[ImGuiCol_Separator] = ImVec4(0.11f, 0.11f, 0.11f, 0.60f);
- colors[ImGuiCol_SeparatorHovered] = ImVec4(0.13f, 0.87f, 0.16f, 0.78f);
- colors[ImGuiCol_SeparatorActive] = ImVec4(0.25f, 0.75f, 0.10f, 1.00f);
- colors[ImGuiCol_ResizeGrip] = ImVec4(0.47f, 0.83f, 0.49f, 0.04f);
- colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.28f, 0.71f, 0.25f, 0.78f);
- colors[ImGuiCol_ResizeGripActive] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
- colors[ImGuiCol_Tab] = ImVec4(0.26f, 0.67f, 0.23f, 0.95f);
- colors[ImGuiCol_TabHovered] = ImVec4(0.28f, 0.71f, 0.25f, 0.86f);
- colors[ImGuiCol_TabActive] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
- colors[ImGuiCol_TabUnfocused] = ImVec4(0.21f, 0.54f, 0.19f, 0.99f);
- colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.24f, 0.60f, 0.21f, 1.00f);
- colors[ImGuiCol_PlotLines] = ImVec4(0.86f, 0.93f, 0.89f, 0.63f);
- colors[ImGuiCol_PlotLinesHovered] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
- colors[ImGuiCol_PlotHistogram] = ImVec4(0.86f, 0.93f, 0.89f, 0.63f);
- colors[ImGuiCol_PlotHistogramHovered] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
- colors[ImGuiCol_TextSelectedBg] = ImVec4(0.28f, 0.71f, 0.25f, 0.43f);
- colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
- colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f);
- colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
- colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
- colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.16f, 0.16f, 0.16f, 0.73f);
-}
-
-/* External interface, called from ui/xemu.c which handles SDL main loop */
-static FirstBootWindow first_boot_window;
-static SDL_Window *g_sdl_window;
-
-void xemu_hud_init(SDL_Window* window, void* sdl_gl_context)
-{
- xemu_monitor_init();
-
- initialize_custom_ui_rendering();
-
- // Setup Dear ImGui context
- IMGUI_CHECKVERSION();
- ImGui::CreateContext();
- ImGuiIO& io = ImGui::GetIO();
- io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
- io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
- io.IniFilename = NULL;
-
- // Setup Platform/Renderer bindings
- ImGui_ImplSDL2_InitForOpenGL(window, sdl_gl_context);
- ImGui_ImplOpenGL3_Init("#version 150");
-
- first_boot_window.is_open = g_config.general.show_welcome;
-
- int ui_scale_int = g_config.display.ui.scale;
- if (ui_scale_int < 1) ui_scale_int = 1;
- g_ui_scale = ui_scale_int;
-
- g_sdl_window = window;
-
- ImPlot::CreateContext();
-
-#if defined(_WIN32)
- if (!g_config.general.show_welcome && g_config.general.updates.check) {
- update_window.check_for_updates_and_prompt_if_available();
- }
-#endif
-}
-
-void xemu_hud_cleanup(void)
-{
- ImGui_ImplOpenGL3_Shutdown();
- ImGui_ImplSDL2_Shutdown();
- ImGui::DestroyContext();
-}
-
-void xemu_hud_process_sdl_events(SDL_Event *event)
-{
- ImGui_ImplSDL2_ProcessEvent(event);
-}
-
-void xemu_hud_should_capture_kbd_mouse(int *kbd, int *mouse)
-{
- ImGuiIO& io = ImGui::GetIO();
- if (kbd) *kbd = io.WantCaptureKeyboard;
- if (mouse) *mouse = io.WantCaptureMouse;
-}
-
-void xemu_hud_render(void)
-{
- uint32_t now = SDL_GetTicks();
- bool ui_wakeup = false;
-
- // Combine all controller states to allow any controller to navigate
- uint32_t buttons = 0;
- int16_t axis[CONTROLLER_AXIS__COUNT] = {0};
-
- ControllerState *iter;
- QTAILQ_FOREACH(iter, &available_controllers, entry) {
- if (iter->type != INPUT_DEVICE_SDL_GAMECONTROLLER) continue;
- buttons |= iter->buttons;
- // We simply take any axis that is >10 % activation
- for (int i = 0; i < CONTROLLER_AXIS__COUNT; i++) {
- if ((iter->axis[i] > 3276) || (iter->axis[i] < -3276)) {
- axis[i] = iter->axis[i];
- }
- }
- }
-
- // If the guide button is pressed, wake the ui
- bool menu_button = false;
- if (buttons & CONTROLLER_BUTTON_GUIDE) {
- ui_wakeup = true;
- menu_button = true;
- }
-
- // Allow controllers without a guide button to also work
- if ((buttons & CONTROLLER_BUTTON_BACK) &&
- (buttons & CONTROLLER_BUTTON_START)) {
- ui_wakeup = true;
- menu_button = true;
- }
-
- // If the mouse is moved, wake the ui
- static ImVec2 last_mouse_pos = ImVec2();
- ImVec2 current_mouse_pos = ImGui::GetMousePos();
- if ((current_mouse_pos.x != last_mouse_pos.x) ||
- (current_mouse_pos.y != last_mouse_pos.y)) {
- last_mouse_pos = current_mouse_pos;
- ui_wakeup = true;
- }
-
- // If mouse capturing is enabled (we are in a dialog), ensure the UI is alive
- bool controller_focus_capture = false;
- ImGuiIO& io = ImGui::GetIO();
- if (io.NavActive) {
- ui_wakeup = true;
- controller_focus_capture = true;
- }
-
- // Prevent controller events from going to the guest if they are being used
- // to navigate the HUD
- xemu_input_set_test_mode(controller_focus_capture);
-
- if (g_trigger_style_update) {
- InitializeStyle();
- g_trigger_style_update = false;
- }
-
- // Start the Dear ImGui frame
- ImGui_ImplOpenGL3_NewFrame();
-
- // Override SDL2 implementation gamecontroller interface
- io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
- ImGui_ImplSDL2_NewFrame(g_sdl_window);
- io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
- io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
-
- // Update gamepad inputs (from imgui_impl_sdl.cpp)
- memset(io.NavInputs, 0, sizeof(io.NavInputs));
- #define MAP_BUTTON(NAV_NO, BUTTON_NO) { io.NavInputs[NAV_NO] = (buttons & BUTTON_NO) ? 1.0f : 0.0f; }
- #define MAP_ANALOG(NAV_NO, AXIS_NO, V0, V1) { float vn = (float)(axis[AXIS_NO] - V0) / (float)(V1 - V0); if (vn > 1.0f) vn = 1.0f; if (vn > 0.0f && io.NavInputs[NAV_NO] < vn) io.NavInputs[NAV_NO] = vn; }
- const int thumb_dead_zone = 8000; // SDL_gamecontroller.h suggests using this value.
- MAP_BUTTON(ImGuiNavInput_Activate, CONTROLLER_BUTTON_A); // Cross / A
- MAP_BUTTON(ImGuiNavInput_Cancel, CONTROLLER_BUTTON_B); // Circle / B
- MAP_BUTTON(ImGuiNavInput_Input, CONTROLLER_BUTTON_Y); // Triangle / Y
- MAP_BUTTON(ImGuiNavInput_DpadLeft, CONTROLLER_BUTTON_DPAD_LEFT); // D-Pad Left
- MAP_BUTTON(ImGuiNavInput_DpadRight, CONTROLLER_BUTTON_DPAD_RIGHT); // D-Pad Right
- MAP_BUTTON(ImGuiNavInput_DpadUp, CONTROLLER_BUTTON_DPAD_UP); // D-Pad Up
- MAP_BUTTON(ImGuiNavInput_DpadDown, CONTROLLER_BUTTON_DPAD_DOWN); // D-Pad Down
- MAP_BUTTON(ImGuiNavInput_FocusPrev, CONTROLLER_BUTTON_WHITE); // L1 / LB
- MAP_BUTTON(ImGuiNavInput_FocusNext, CONTROLLER_BUTTON_BLACK); // R1 / RB
- MAP_BUTTON(ImGuiNavInput_TweakSlow, CONTROLLER_BUTTON_WHITE); // L1 / LB
- MAP_BUTTON(ImGuiNavInput_TweakFast, CONTROLLER_BUTTON_BLACK); // R1 / RB
-
- // Allow Guide and "Back+Start" buttons to act as Menu button
- if (menu_button) {
- io.NavInputs[ImGuiNavInput_Menu] = 1.0;
- }
-
- MAP_ANALOG(ImGuiNavInput_LStickLeft, CONTROLLER_AXIS_LSTICK_X, -thumb_dead_zone, -32768);
- MAP_ANALOG(ImGuiNavInput_LStickRight, CONTROLLER_AXIS_LSTICK_X, +thumb_dead_zone, +32767);
- MAP_ANALOG(ImGuiNavInput_LStickUp, CONTROLLER_AXIS_LSTICK_Y, +thumb_dead_zone, +32767);
- MAP_ANALOG(ImGuiNavInput_LStickDown, CONTROLLER_AXIS_LSTICK_Y, -thumb_dead_zone, -32767);
-
- ImGui::NewFrame();
- process_keyboard_shortcuts();
-
-#if defined(DEBUG_NV2A_GL) && defined(CONFIG_RENDERDOC)
- if (capture_renderdoc_frame) {
- nv2a_dbg_renderdoc_capture_frames(1);
- capture_renderdoc_frame = false;
- }
-#endif
-
- bool show_main_menu = true;
-
- if (first_boot_window.is_open) {
- show_main_menu = false;
- }
-
- if (show_main_menu) {
- // Auto-hide main menu after 5s of inactivity
- static uint32_t last_check = 0;
- float alpha = 1.0;
- const uint32_t timeout = 5000;
- const float fade_duration = 1000.0;
- if (ui_wakeup) {
- last_check = now;
- }
- if ((now-last_check) > timeout) {
- float t = fmin((float)((now-last_check)-timeout)/fade_duration, 1.0);
- alpha = 1.0-t;
- if (t >= 1.0) {
- alpha = 0.0;
- }
- }
- if (alpha > 0.0) {
- ImVec4 tc = ImGui::GetStyle().Colors[ImGuiCol_Text];
- tc.w = alpha;
- ImGui::PushStyleColor(ImGuiCol_Text, tc);
- ImGui::SetNextWindowBgAlpha(alpha);
- ShowMainMenu();
- ImGui::PopStyleColor();
- } else {
- g_main_menu_height = 0;
- }
- }
-
- first_boot_window.Draw();
- input_window.Draw();
- settings_window.Draw();
- monitor_window.Draw();
- apu_window.Draw();
- video_window.Draw();
- about_window.Draw();
- network_window.Draw();
- compatibility_reporter_window.Draw();
- notification_manager.Draw();
-#if defined(_WIN32)
- update_window.Draw();
-#endif
-
- // Very rudimentary error notification API
- if (g_errors.size() > 0) {
- ImGui::OpenPopup("Error");
- }
- if (ImGui::BeginPopupModal("Error", NULL, ImGuiWindowFlags_AlwaysAutoResize))
- {
- ImGui::Text("%s", g_errors[0]);
- ImGui::Dummy(ImVec2(0,16));
- ImGui::SetItemDefaultFocus();
- ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+10));
- if (ImGui::Button("Ok", ImVec2(120, 0))) {
- ImGui::CloseCurrentPopup();
- free((void*)g_errors[0]);
- g_errors.pop_front();
- }
- ImGui::EndPopup();
- }
-
- ImGui::Render();
- ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
-}
-
-/* External interface, exposed via xemu-notifications.h */
-
-void xemu_queue_notification(const char *msg)
-{
- notification_manager.QueueNotification(msg);
-}
-
-void xemu_queue_error_message(const char *msg)
-{
- g_errors.push_back(strdup(msg));
-}
diff --git a/ui/xemu-input.c b/ui/xemu-input.c
index e5060c3ac4..29ee3d2737 100644
--- a/ui/xemu-input.c
+++ b/ui/xemu-input.c
@@ -96,7 +96,9 @@ static const char **port_index_to_settings_key_map[] = {
void xemu_input_init(void)
{
- SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
+ if (g_config.input.background_input_capture) {
+ SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
+ }
if (SDL_Init(SDL_INIT_GAMECONTROLLER) < 0) {
fprintf(stderr, "Failed to initialize SDL gamecontroller subsystem: %s\n", SDL_GetError());
@@ -191,25 +193,42 @@ void xemu_input_process_sdl_events(const SDL_Event *event)
// upside in this case is that a person can use the same GUID on all
// ports and just needs to bind to the receiver and never needs to hit
// this dialog.
+
+
+ // Attempt to re-bind to port previously bound to
int port = 0;
- while (1) {
+ bool did_bind = false;
+ while (!did_bind) {
port = xemu_input_get_controller_default_bind_port(new_con, port);
if (port < 0) {
// No (additional) default mappings
break;
- }
- if (xemu_input_get_bound(port) != NULL) {
- // Something already bound here, try again for another port
+ } else if (!xemu_input_get_bound(port)) {
+ xemu_input_bind(port, new_con, 0);
+ did_bind = true;
+ break;
+ } else {
+ // Try again for another port
port++;
- continue;
}
- xemu_input_bind(port, new_con, 0);
+ }
+
+ // Try to bind to any open port, and if so remember the binding
+ if (!did_bind && g_config.input.auto_bind) {
+ for (port = 0; port < 4; port++) {
+ if (!xemu_input_get_bound(port)) {
+ xemu_input_bind(port, new_con, 1);
+ did_bind = true;
+ break;
+ }
+ }
+ }
+
+ if (did_bind) {
char buf[128];
snprintf(buf, sizeof(buf), "Connected '%s' to port %d", new_con->name, port+1);
xemu_queue_notification(buf);
- break;
}
-
} else if (event->type == SDL_CONTROLLERDEVICEREMOVED) {
DPRINTF("Controller Removed: %d\n", event->cdevice.which);
int handled = 0;
diff --git a/ui/xemu-net.c b/ui/xemu-net.c
index a6cd9efb22..9d2bdce3f8 100644
--- a/ui/xemu-net.c
+++ b/ui/xemu-net.c
@@ -33,6 +33,8 @@
#include "qemu/config-file.h"
#include "net/net.h"
#include "net/hub.h"
+#include "net/slirp.h"
+#include
#if defined(_WIN32)
#include
#endif
@@ -41,6 +43,8 @@
static const char *id = "xemu-netdev";
static const char *id_hubport = "xemu-netdev-hubport";
+void *slirp_get_state_from_netdev(const char *id);
+
void xemu_net_enable(void)
{
Error *local_err = NULL;
@@ -102,6 +106,38 @@ void xemu_net_enable(void)
// error_propagate(errp, local_err);
xemu_queue_error_message(error_get_pretty(local_err));
error_report_err(local_err);
+ return;
+ }
+
+ if (g_config.net.backend == CONFIG_NET_BACKEND_NAT) {
+ void *s = slirp_get_state_from_netdev(id);
+ assert(s != NULL);
+
+ struct in_addr host_addr = { .s_addr = INADDR_ANY };
+ struct in_addr guest_addr = { .s_addr = 0 };
+ inet_aton("10.0.2.15", &guest_addr);
+
+ for (int i = 0; i < g_config.net.nat.forward_ports_count; i++) {
+ bool is_udp = g_config.net.nat.forward_ports[i].protocol ==
+ CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_UDP;
+ int host_port = g_config.net.nat.forward_ports[i].host;
+ int guest_port = g_config.net.nat.forward_ports[i].guest;
+
+ if (slirp_add_hostfwd(s, is_udp, host_addr, host_port, guest_addr,
+ guest_port) < 0) {
+ error_setg(&local_err,
+ "Could not set host forwarding rule "
+ "%d -> %d (%s)\n",
+ host_port, guest_port, is_udp ? "udp" : "tcp");
+ xemu_queue_error_message(error_get_pretty(local_err));
+ break;
+ }
+
+ }
+ }
+
+ if (local_err) {
+ xemu_net_disable();
}
g_config.net.enable = true;
@@ -124,13 +160,25 @@ static void remove_netdev(const char *name)
// error_setg(errp, "Device '%s' is not a netdev", name);
return;
}
-
qemu_opts_del(opts);
qemu_del_net_client(nc);
}
void xemu_net_disable(void)
{
+ if (g_config.net.backend == CONFIG_NET_BACKEND_NAT) {
+ void *s = slirp_get_state_from_netdev(id);
+ assert(s != NULL);
+ struct in_addr host_addr = { .s_addr = INADDR_ANY };
+ for (int i = 0; i < g_config.net.nat.forward_ports_count; i++) {
+ slirp_remove_hostfwd(s,
+ g_config.net.nat.forward_ports[i].protocol ==
+ CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_UDP,
+ host_addr,
+ g_config.net.nat.forward_ports[i].host);
+ }
+ }
+
remove_netdev(id);
remove_netdev(id_hubport);
g_config.net.enable = false;
diff --git a/ui/xemu-os-utils.h b/ui/xemu-os-utils.h
index e95af1f31b..d2dde9370d 100644
--- a/ui/xemu-os-utils.h
+++ b/ui/xemu-os-utils.h
@@ -25,9 +25,46 @@ extern "C" {
#endif
const char *xemu_get_os_info(void);
-const char *xemu_get_cpu_info(void);
void xemu_open_web_browser(const char *url);
+#ifndef _WIN32
+#ifdef CONFIG_CPUID_H
+#include
+#endif
+#endif
+
+static inline const char *xemu_get_os_platform(void)
+{
+ const char *platform_name;
+
+#if defined(__linux__)
+ platform_name = "Linux";
+#elif defined(_WIN32)
+ platform_name = "Windows";
+#elif defined(__APPLE__)
+ platform_name = "macOS";
+#else
+ platform_name = "Unknown";
+#endif
+ return platform_name;
+}
+
+static inline const char *xemu_get_cpu_info(void)
+{
+ const char *cpu_info = "";
+#ifdef CONFIG_CPUID_H
+ static uint32_t brand[12];
+ if (__get_cpuid_max(0x80000004, NULL)) {
+ __get_cpuid(0x80000002, brand+0x0, brand+0x1, brand+0x2, brand+0x3);
+ __get_cpuid(0x80000003, brand+0x4, brand+0x5, brand+0x6, brand+0x7);
+ __get_cpuid(0x80000004, brand+0x8, brand+0x9, brand+0xa, brand+0xb);
+ }
+ cpu_info = (const char *)brand;
+#endif
+ // FIXME: Support other architectures (e.g. ARM)
+ return cpu_info;
+}
+
#ifdef __cplusplus
}
#endif
diff --git a/ui/xemu-settings.cc b/ui/xemu-settings.cc
index a591214338..42f2cfbaf1 100644
--- a/ui/xemu-settings.cc
+++ b/ui/xemu-settings.cc
@@ -187,3 +187,31 @@ void xemu_settings_save(void)
fprintf(fd, "%s", config_tree.generate_delta_toml().c_str());
fclose(fd);
}
+
+void add_net_nat_forward_ports(int host, int guest, CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol)
+{
+ // FIXME: - Realloc the arrays instead of free/alloc
+ // - Don't need to copy as much
+ auto cnode = config_tree.child("net")
+ ->child("nat")
+ ->child("forward_ports");
+ cnode->update_from_struct(&g_config);
+ cnode->children.push_back(*cnode->array_item_type);
+ auto &e = cnode->children.back();
+ e.child("host")->set_integer(host);
+ e.child("guest")->set_integer(guest);
+ e.child("protocol")->set_enum_by_index(protocol);
+ cnode->free_allocations(&g_config);
+ cnode->store_to_struct(&g_config);
+}
+
+void remove_net_nat_forward_ports(unsigned int index)
+{
+ auto cnode = config_tree.child("net")
+ ->child("nat")
+ ->child("forward_ports");
+ cnode->update_from_struct(&g_config);
+ cnode->children.erase(cnode->children.begin()+index);
+ cnode->free_allocations(&g_config);
+ cnode->store_to_struct(&g_config);
+}
diff --git a/ui/xemu-settings.h b/ui/xemu-settings.h
index 0175409d4e..c6ba76f1ff 100644
--- a/ui/xemu-settings.h
+++ b/ui/xemu-settings.h
@@ -59,6 +59,9 @@ static inline void xemu_settings_set_string(const char **str, const char *new_st
*str = strdup(new_str);
}
+void add_net_nat_forward_ports(int host, int guest, CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol);
+void remove_net_nat_forward_ports(unsigned int index);
+
#ifdef __cplusplus
}
#endif
diff --git a/ui/xemu-shaders.c b/ui/xemu-shaders.c
deleted file mode 100644
index 04279e412d..0000000000
--- a/ui/xemu-shaders.c
+++ /dev/null
@@ -1,371 +0,0 @@
-/*
- * xemu User Interface Rendering Helpers
- *
- * Copyright (C) 2020-2021 Matt Borgerson
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include
-#include
-#include
-#include "xemu-shaders.h"
-#include "ui/shader/xemu-logo-frag.h"
-
-#define STB_IMAGE_IMPLEMENTATION
-#include "stb_image.h"
-
-GLuint compile_shader(GLenum type, const char *src)
-{
- GLint status;
- char err_buf[512];
- GLuint shader = glCreateShader(type);
- if (shader == 0) {
- fprintf(stderr, "ERROR: Failed to create shader\n");
- return 0;
- }
- glShaderSource(shader, 1, &src, NULL);
- glCompileShader(shader);
- glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
- if (status != GL_TRUE) {
- glGetShaderInfoLog(shader, sizeof(err_buf), NULL, err_buf);
- fprintf(stderr, "ERROR: Shader compilation failed!\n\n");
- fprintf(stderr, "[Shader Info Log]\n");
- fprintf(stderr, "%s\n", err_buf);
- fprintf(stderr, "[Shader Source]\n");
- fprintf(stderr, "%s\n", src);
- return 0;
- }
-
- return shader;
-}
-
-struct decal_shader *create_decal_shader(enum SHADER_TYPE type)
-{
- // Allocate shader wrapper object
- struct decal_shader *s = (struct decal_shader *)malloc(sizeof(struct decal_shader));
- assert(s != NULL);
- s->flip = 0;
- s->scale = 1.4;
- s->smoothing = 1.0;
- s->outline_dist = 1.0;
- s->time = 0;
-
- const char *vert_src =
- "#version 150 core\n"
- "uniform bool in_FlipY;\n"
- "uniform vec4 in_ScaleOffset;\n"
- "uniform vec4 in_TexScaleOffset;\n"
- "in vec2 in_Position;\n"
- "in vec2 in_Texcoord;\n"
- "out vec2 Texcoord;\n"
- "void main() {\n"
- " vec2 t = in_Texcoord;\n"
- " if (in_FlipY) t.y = 1-t.y;\n"
- " Texcoord = t*in_TexScaleOffset.xy + in_TexScaleOffset.zw;\n"
- " gl_Position = vec4(in_Position*in_ScaleOffset.xy+in_ScaleOffset.zw, 0.0, 1.0);\n"
- "}\n";
- GLuint vert = compile_shader(GL_VERTEX_SHADER, vert_src);
- assert(vert != 0);
-
- const char *image_frag_src =
- "#version 150 core\n"
- "uniform sampler2D tex;\n"
- "in vec2 Texcoord;\n"
- "out vec4 out_Color;\n"
- "void main() {\n"
- " out_Color.rgba = texture(tex, Texcoord);\n"
- "}\n";
-
- const char *image_gamma_frag_src =
- "#version 400 core\n"
- "uniform sampler2D tex;\n"
- "uniform uint palette[256];\n"
- "float gamma_ch(int ch, float col)\n"
- "{\n"
- " return float(bitfieldExtract(palette[uint(col * 255.0)], ch*8, 8)) / 255.0;\n"
- "}\n"
- "\n"
- "vec4 gamma(vec4 col)\n"
- "{\n"
- " return vec4(gamma_ch(0, col.r), gamma_ch(1, col.g), gamma_ch(2, col.b), col.a);\n"
- "}\n"
- "in vec2 Texcoord;\n"
- "out vec4 out_Color;\n"
- "void main() {\n"
- " out_Color.rgba = gamma(texture(tex, Texcoord));\n"
- "}\n";
-
- // Simple 2-color decal shader
- // - in_ColorFill is first pass
- // - Red channel of the texture is used as primary color, mixed with 1-Red for
- // secondary color.
- // - Blue is a lazy alpha removal for now
- // - Alpha channel passed through
- const char *mask_frag_src =
- "#version 150 core\n"
- "uniform sampler2D tex;\n"
- "uniform vec4 in_ColorPrimary;\n"
- "uniform vec4 in_ColorSecondary;\n"
- "uniform vec4 in_ColorFill;\n"
- "in vec2 Texcoord;\n"
- "out vec4 out_Color;\n"
- "void main() {\n"
- " vec4 t = texture(tex, Texcoord);\n"
- " out_Color.rgba = in_ColorFill.rgba;\n"
- " out_Color.rgb += mix(in_ColorSecondary.rgb, in_ColorPrimary.rgb, t.r);\n"
- " out_Color.a += t.a - t.b;\n"
- "}\n";
-
- const char *frag_src = NULL;
- if (type == SHADER_TYPE_MASK) {
- frag_src = mask_frag_src;
- } else if (type == SHADER_TYPE_BLIT) {
- frag_src = image_frag_src;
- } else if (type == SHADER_TYPE_BLIT_GAMMA) {
- frag_src = image_gamma_frag_src;
- } else if (type == SHADER_TYPE_LOGO) {
- frag_src = xemu_logo_frag_src;
- } else {
- assert(0);
- }
- GLuint frag = compile_shader(GL_FRAGMENT_SHADER, frag_src);
- assert(frag != 0);
-
- // Link vertex and fragment shaders
- s->prog = glCreateProgram();
- glAttachShader(s->prog, vert);
- glAttachShader(s->prog, frag);
- glBindFragDataLocation(s->prog, 0, "out_Color");
- glLinkProgram(s->prog);
- glUseProgram(s->prog);
-
- // Flag shaders for deletion when program is deleted
- glDeleteShader(vert);
- glDeleteShader(frag);
-
- s->FlipY_loc = glGetUniformLocation(s->prog, "in_FlipY");
- s->ScaleOffset_loc = glGetUniformLocation(s->prog, "in_ScaleOffset");
- s->TexScaleOffset_loc = glGetUniformLocation(s->prog, "in_TexScaleOffset");
- s->tex_loc = glGetUniformLocation(s->prog, "tex");
- s->ColorPrimary_loc = glGetUniformLocation(s->prog, "in_ColorPrimary");
- s->ColorSecondary_loc = glGetUniformLocation(s->prog, "in_ColorSecondary");
- s->ColorFill_loc = glGetUniformLocation(s->prog, "in_ColorFill");
- s->time_loc = glGetUniformLocation(s->prog, "iTime");
- s->scale_loc = glGetUniformLocation(s->prog, "scale");
- for (int i = 0; i < 256; i++) {
- char name[64];
- snprintf(name, sizeof(name), "palette[%d]", i);
- s->palette_loc[i] = glGetUniformLocation(s->prog, name);
- }
-
- // Create a vertex array object
- glGenVertexArrays(1, &s->vao);
- glBindVertexArray(s->vao);
-
- // Populate vertex buffer
- glGenBuffers(1, &s->vbo);
- glBindBuffer(GL_ARRAY_BUFFER, s->vbo);
- const GLfloat verts[6][4] = {
- // x y s t
- { -1.0f, -1.0f, 0.0f, 0.0f }, // BL
- { -1.0f, 1.0f, 0.0f, 1.0f }, // TL
- { 1.0f, 1.0f, 1.0f, 1.0f }, // TR
- { 1.0f, -1.0f, 1.0f, 0.0f }, // BR
- };
- glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_COPY);
-
- // Populate element buffer
- glGenBuffers(1, &s->ebo);
- glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->ebo);
- const GLint indicies[] = { 0, 1, 2, 3 };
- glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indicies), indicies, GL_STATIC_DRAW);
-
- // Bind vertex position attribute
- GLint pos_attr_loc = glGetAttribLocation(s->prog, "in_Position");
- glVertexAttribPointer(pos_attr_loc, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)0);
- glEnableVertexAttribArray(pos_attr_loc);
-
- // Bind vertex texture coordinate attribute
- GLint tex_attr_loc = glGetAttribLocation(s->prog, "in_Texcoord");
- if (tex_attr_loc >= 0) {
- glVertexAttribPointer(tex_attr_loc, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
- glEnableVertexAttribArray(tex_attr_loc);
- }
-
- return s;
-}
-
-static GLuint load_texture(unsigned char *data, int width, int height, int channels)
-{
- GLuint tex;
- glGenTextures(1, &tex);
- assert(tex != 0);
- glBindTexture(GL_TEXTURE_2D, tex);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
- return tex;
-}
-
-GLuint load_texture_from_file(const char *name)
-{
- // Flip vertically so textures are loaded according to GL convention.
- stbi_set_flip_vertically_on_load(1);
-
- // Read file into memory
- int width, height, channels = 0;
- unsigned char *data = stbi_load(name, &width, &height, &channels, 4);
- assert(data != NULL);
-
- GLuint tex = load_texture(data, width, height, channels);
- stbi_image_free(data);
-
- return tex;
-}
-
-GLuint load_texture_from_memory(const unsigned char *buf, unsigned int size)
-{
- // Flip vertically so textures are loaded according to GL convention.
- stbi_set_flip_vertically_on_load(1);
-
- int width, height, channels = 0;
- unsigned char *data = stbi_load_from_memory(buf, size, &width, &height, &channels, 4);
- assert(data != NULL);
-
- GLuint tex = load_texture(data, width, height, channels);
- stbi_image_free(data);
-
- return tex;
-}
-
-void render_decal(
- struct decal_shader *s,
- float x, float y, float w, float h,
- float tex_x, float tex_y, float tex_w, float tex_h,
- uint32_t primary, uint32_t secondary, uint32_t fill
- )
-{
- GLint vp[4];
- glGetIntegerv(GL_VIEWPORT, vp);
- float ww = vp[2], wh = vp[3];
-
- x = (int)x;
- y = (int)y;
- w = (int)w;
- h = (int)h;
- tex_x = (int)tex_x;
- tex_y = (int)tex_y;
- tex_w = (int)tex_w;
- tex_h = (int)tex_h;
-
- int tw_i, th_i;
- glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw_i);
- glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th_i);
- float tw = tw_i, th = th_i;
-
- #define COL(color, c) (float)(((color)>>((c)*8)) & 0xff)/255.0
- glUniform1i(s->FlipY_loc, s->flip);
- glUniform4f(s->ScaleOffset_loc, w/ww, h/wh, -1+((2*x+w)/ww), -1+((2*y+h)/wh));
- glUniform4f(s->TexScaleOffset_loc, tex_w/tw, tex_h/th, tex_x/tw, tex_y/th);
- glUniform1i(s->tex_loc, 0);
- glUniform4f(s->ColorPrimary_loc, COL(primary, 3), COL(primary, 2), COL(primary, 1), COL(primary, 0));
- glUniform4f(s->ColorSecondary_loc, COL(secondary, 3), COL(secondary, 2), COL(secondary, 1), COL(secondary, 0));
- glUniform4f(s->ColorFill_loc, COL(fill, 3), COL(fill, 2), COL(fill, 1), COL(fill, 0));
- if (s->time_loc >= 0) glUniform1f(s->time_loc, s->time/1000.0f);
- if (s->scale_loc >= 0) glUniform1f(s->scale_loc, s->scale);
- #undef COL
- glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
-}
-
-void render_decal_image(
- struct decal_shader *s,
- float x, float y, float w, float h,
- float tex_x, float tex_y, float tex_w, float tex_h
- )
-{
- GLint vp[4];
- glGetIntegerv(GL_VIEWPORT, vp);
- float ww = vp[2], wh = vp[3];
-
- int tw_i, th_i;
- glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw_i);
- glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th_i);
- float tw = tw_i, th = th_i;
-
- glUniform1i(s->FlipY_loc, s->flip);
- glUniform4f(s->ScaleOffset_loc, w/ww, h/wh, -1+((2*x+w)/ww), -1+((2*y+h)/wh));
- glUniform4f(s->TexScaleOffset_loc, tex_w/tw, tex_h/th, tex_x/tw, tex_y/th);
- glUniform1i(s->tex_loc, 0);
- glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
-}
-
-struct fbo *create_fbo(int width, int height)
-{
- struct fbo *fbo = (struct fbo *)malloc(sizeof(struct fbo));
- assert(fbo != NULL);
-
- fbo->w = width;
- fbo->h = height;
-
- // Allocate the texture
- glGenTextures(1, &fbo->tex);
- glBindTexture(GL_TEXTURE_2D, fbo->tex);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
- glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
- glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fbo->w, fbo->h, 0, GL_RGBA, GL_UNSIGNED_BYTE, NULL);
-
- // Allocate the framebuffer object
- glGenFramebuffers(1, &fbo->fbo);
- glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo);
- glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, fbo->tex, 0);
- GLenum DrawBuffers[1] = {GL_COLOR_ATTACHMENT0};
- glDrawBuffers(1, DrawBuffers);
-
- return fbo;
-}
-
-static GLboolean m_blend;
-
-void render_to_default_fb(void)
-{
- if (!m_blend) {
- glDisable(GL_BLEND);
- }
-
- // Restore default framebuffer, viewport, blending funciton
- glBindFramebuffer(GL_FRAMEBUFFER, main_fb);
- glViewport(vp[0], vp[1], vp[2], vp[3]);
- glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
-}
-
-GLuint render_to_fbo(struct fbo *fbo)
-{
- m_blend = glIsEnabled(GL_BLEND);
- if (!m_blend) {
- glEnable(GL_BLEND);
- }
- glBindFramebuffer(GL_FRAMEBUFFER, fbo->fbo);
- glViewport(0, 0, fbo->w, fbo->h);
- glClearColor(0, 0, 0, 0);
- glClear(GL_COLOR_BUFFER_BIT);
- return fbo->tex;
-}
diff --git a/ui/xemu-shaders.h b/ui/xemu-shaders.h
deleted file mode 100644
index 34bf1b8ad3..0000000000
--- a/ui/xemu-shaders.h
+++ /dev/null
@@ -1,103 +0,0 @@
-/*
- * xemu User Interface Rendering Helpers
- *
- * Copyright (C) 2020-2021 Matt Borgerson
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#ifndef XEMU_SHADERS_H
-#define XEMU_SHADERS_H
-
-#include
-#include
-
-#include "stb_image.h"
-
-enum SHADER_TYPE {
- SHADER_TYPE_BLIT,
- SHADER_TYPE_BLIT_GAMMA,
- SHADER_TYPE_MASK,
- SHADER_TYPE_LOGO,
-};
-
-struct decal_shader
-{
- int flip;
- float scale;
- float smoothing;
- float outline_dist;
- uint32_t time;
-
- // GL object handles
- GLuint prog, vao, vbo, ebo;
-
- // Uniform locations
- GLint Mat_loc;
- GLint FlipY_loc;
- GLint tex_loc;
- GLint ScaleOffset_loc;
- GLint TexScaleOffset_loc;
-
- GLint ColorPrimary_loc;
- GLint ColorSecondary_loc;
- GLint ColorFill_loc;
- GLint time_loc;
- GLint scale_loc;
- GLint palette_loc[256];
-};
-
-struct fbo {
- GLuint fbo;
- GLuint tex;
- int w, h;
-};
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-extern GLuint main_fb;
-extern GLint vp[4];
-
-GLuint compile_shader(GLenum type, const char *src);
-
-struct decal_shader *create_decal_shader(enum SHADER_TYPE type);
-void delete_decal_shader(struct decal_shader *s);
-
-GLuint load_texture_from_file(const char *name);
-GLuint load_texture_from_memory(const unsigned char *buf, unsigned int size);
-
-struct fbo *create_fbo(int width, int height);
-void render_to_default_fb(void);
-GLuint render_to_fbo(struct fbo *fbo);
-
-void render_decal(
- struct decal_shader *s,
- float x, float y, float w, float h,
- float tex_x, float tex_y, float tex_w, float tex_h,
- uint32_t primary, uint32_t secondary, uint32_t fill
- );
-
-void render_decal_image(
- struct decal_shader *s,
- float x, float y, float w, float h,
- float tex_x, float tex_y, float tex_w, float tex_h
- );
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif
diff --git a/ui/xemu-update.cc b/ui/xemu-update.cc
deleted file mode 100644
index 88aadb3514..0000000000
--- a/ui/xemu-update.cc
+++ /dev/null
@@ -1,195 +0,0 @@
-/*
- * xemu Automatic Update
- *
- * Copyright (C) 2021 Matt Borgerson
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#include
-#include
-#include
-
-#include "util/miniz/miniz.h"
-
-#include "xemu-update.h"
-#include "xemu-version.h"
-
-#if defined(_WIN32)
-const char *version_host = "raw.githubusercontent.com";
-const char *version_uri = "/mborgerson/xemu/ppa-snapshot/XEMU_VERSION";
-const char *download_host = "github.com";
-const char *download_uri = "/mborgerson/xemu/releases/latest/download/xemu-win-release.zip";
-#else
-FIXME
-#endif
-
-#define CPPHTTPLIB_OPENSSL_SUPPORT 1
-#include "httplib.h"
-
-#define DPRINTF(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__);
-
-Updater::Updater()
-{
- m_status = UPDATER_IDLE;
- m_update_availability = UPDATE_AVAILABILITY_UNKNOWN;
- m_update_percentage = 0;
- m_latest_version = "Unknown";
- m_should_cancel = false;
-}
-
-void Updater::check_for_update(UpdaterCallback on_complete)
-{
- if (m_status == UPDATER_IDLE || m_status == UPDATER_ERROR) {
- m_on_complete = on_complete;
- qemu_thread_create(&m_thread, "update_worker",
- &Updater::checker_thread_worker_func,
- this, QEMU_THREAD_JOINABLE);
- }
-}
-
-void *Updater::checker_thread_worker_func(void *updater)
-{
- ((Updater *)updater)->check_for_update_internal();
- return NULL;
-}
-
-void Updater::check_for_update_internal()
-{
- httplib::SSLClient cli(version_host, 443);
- cli.set_follow_location(true);
- cli.set_timeout_sec(5);
- auto res = cli.Get(version_uri, [this](uint64_t len, uint64_t total) {
- m_update_percentage = len*100/total;
- return !m_should_cancel;
- });
- if (m_should_cancel) {
- m_should_cancel = false;
- m_status = UPDATER_IDLE;
- goto finished;
- } else if (!res || res->status != 200) {
- m_status = UPDATER_ERROR;
- goto finished;
- }
-
- if (strcmp(xemu_version, res->body.c_str())) {
- m_update_availability = UPDATE_AVAILABLE;
- } else {
- m_update_availability = UPDATE_NOT_AVAILABLE;
- }
-
- m_latest_version = res->body;
- m_status = UPDATER_IDLE;
-finished:
- if (m_on_complete) {
- m_on_complete();
- }
-}
-
-void Updater::update()
-{
- if (m_status == UPDATER_IDLE || m_status == UPDATER_ERROR) {
- m_status = UPDATER_UPDATING;
- qemu_thread_create(&m_thread, "update_worker",
- &Updater::update_thread_worker_func,
- this, QEMU_THREAD_JOINABLE);
- }
-}
-
-void *Updater::update_thread_worker_func(void *updater)
-{
- ((Updater *)updater)->update_internal();
- return NULL;
-}
-
-void Updater::update_internal()
-{
- httplib::SSLClient cli(download_host, 443);
- cli.set_follow_location(true);
- cli.set_timeout_sec(5);
- auto res = cli.Get(download_uri, [this](uint64_t len, uint64_t total) {
- m_update_percentage = len*100/total;
- return !m_should_cancel;
- });
-
- if (m_should_cancel) {
- m_should_cancel = false;
- m_status = UPDATER_IDLE;
- return;
- } else if (!res || res->status != 200) {
- m_status = UPDATER_ERROR;
- return;
- }
-
- mz_zip_archive zip;
- mz_zip_zero_struct(&zip);
- if (!mz_zip_reader_init_mem(&zip, res->body.data(), res->body.size(), 0)) {
- DPRINTF("mz_zip_reader_init_mem failed\n");
- m_status = UPDATER_ERROR;
- return;
- }
-
- mz_uint num_files = mz_zip_reader_get_num_files(&zip);
- for (mz_uint file_idx = 0; file_idx < num_files; file_idx++) {
- mz_zip_archive_file_stat fstat;
- if (!mz_zip_reader_file_stat(&zip, file_idx, &fstat)) {
- DPRINTF("mz_zip_reader_file_stat failed for file #%d\n", file_idx);
- goto errored;
- }
-
- if (fstat.m_filename[strlen(fstat.m_filename)-1] == '/') {
- /* FIXME: mkdirs */
- DPRINTF("FIXME: subdirs not handled yet\n");
- goto errored;
- }
-
- char *dst_path = g_strdup_printf("%s%s", SDL_GetBasePath(), fstat.m_filename);
- DPRINTF("extracting %s to %s\n", fstat.m_filename, dst_path);
-
- if (!strcmp(fstat.m_filename, "xemu.exe")) {
- // We cannot overwrite current executable, but we can move it
- char *renamed_path = g_strdup_printf("%s%s", SDL_GetBasePath(), "xemu-previous.exe");
- MoveFileExA(dst_path, renamed_path, MOVEFILE_REPLACE_EXISTING);
- g_free(renamed_path);
- }
-
- if (!mz_zip_reader_extract_to_file(&zip, file_idx, dst_path, 0)) {
- DPRINTF("mz_zip_reader_extract_to_file failed to create %s\n", dst_path);
- g_free(dst_path);
- goto errored;
- }
-
- g_free(dst_path);
- }
-
- m_status = UPDATER_UPDATE_SUCCESSFUL;
- goto cleanup_zip;
-errored:
- m_status = UPDATER_ERROR;
-cleanup_zip:
- mz_zip_reader_end(&zip);
-}
-
-extern "C" {
-extern char **gArgv;
-}
-
-void Updater::restart_to_updated()
-{
- char *target_exec = g_strdup_printf("%s%s", SDL_GetBasePath(), "xemu.exe");
- DPRINTF("Restarting to updated executable %s\n", target_exec);
- _execv(target_exec, gArgv);
- DPRINTF("Launching updated executable failed\n");
- exit(1);
-}
diff --git a/ui/xemu-update.h b/ui/xemu-update.h
deleted file mode 100644
index b124ba18c8..0000000000
--- a/ui/xemu-update.h
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * xemu Automatic Update
- *
- * Copyright (C) 2021 Matt Borgerson
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#ifndef XEMU_UPDATE_H
-#define XEMU_UPDATE_H
-
-#include
-#include
-#include
-
-extern "C" {
-#include "qemu/osdep.h"
-#include "qemu-common.h"
-#include "qemu/thread.h"
-}
-
-typedef enum {
- UPDATE_AVAILABILITY_UNKNOWN,
- UPDATE_NOT_AVAILABLE,
- UPDATE_AVAILABLE
-} UpdateAvailability;
-
-typedef enum {
- UPDATER_IDLE,
- UPDATER_ERROR,
- UPDATER_CHECKING_FOR_UPDATE,
- UPDATER_UPDATING,
- UPDATER_UPDATE_SUCCESSFUL
-} UpdateStatus;
-
-using UpdaterCallback = std::function;
-
-class Updater {
-private:
- UpdateAvailability m_update_availability;
- int m_update_percentage;
- QemuThread m_thread;
- std::string m_latest_version;
- bool m_should_cancel;
- UpdateStatus m_status;
- UpdaterCallback m_on_complete;
-
-public:
- Updater();
- UpdateStatus get_status() { return m_status; }
- UpdateAvailability get_update_availability() { return m_update_availability; }
- bool is_errored() { return m_status == UPDATER_ERROR; }
- bool is_pending_restart() { return m_status == UPDATER_UPDATE_SUCCESSFUL; }
- bool is_update_available() { return m_update_availability == UPDATE_AVAILABLE; }
- bool is_checking_for_update() { return m_status == UPDATER_CHECKING_FOR_UPDATE; }
- bool is_updating() { return m_status == UPDATER_UPDATING; }
- std::string get_update_version() { return m_latest_version; }
- void cancel() { m_should_cancel = true; }
- void update();
- void update_internal();
- void check_for_update(UpdaterCallback on_complete = nullptr);
- void check_for_update_internal();
- int get_update_progress_percentage() { return m_update_percentage; }
- static void *update_thread_worker_func(void *updater);
- static void *checker_thread_worker_func(void *updater);
- void restart_to_updated(void);
-};
-
-#endif
diff --git a/ui/xemu.c b/ui/xemu.c
index 466115d772..5f86b9bda9 100644
--- a/ui/xemu.c
+++ b/ui/xemu.c
@@ -43,10 +43,10 @@
#include "sysemu/runstate.h"
#include "sysemu/runstate-action.h"
#include "sysemu/sysemu.h"
-#include "xemu-hud.h"
+#include "xui/xemu-hud.h"
#include "xemu-input.h"
#include "xemu-settings.h"
-#include "xemu-shaders.h"
+// #include "xemu-shaders.h"
#include "xemu-version.h"
#include "xemu-os-utils.h"
@@ -55,6 +55,8 @@
#include "hw/xbox/smbus.h" // For eject, drive tray
#include "hw/xbox/nv2a/nv2a.h"
+#include
+
#ifdef _WIN32
// Provide hint to prefer high-performance graphics for hybrid systems
// https://gpuopen.com/learn/amdpowerxpressrequesthighperformance/
@@ -108,7 +110,7 @@ static SDL_Cursor *guest_sprite;
static Notifier mouse_mode_notifier;
static SDL_Window *m_window;
static SDL_GLContext m_context;
-struct decal_shader *blit;
+// struct decal_shader *blit;
static QemuSemaphore display_init_sem;
@@ -845,28 +847,58 @@ static void sdl2_display_very_early_init(DisplayOptions *o)
#endif
, xemu_version);
+ // Decide window size
+ int min_window_width = 640;
+ int min_window_height = 480;
+ int window_width = min_window_width;
+ int window_height = min_window_height;
+
+ const int res_table[][2] = {
+ {640, 480},
+ {1280, 720},
+ {1280, 800},
+ {1280, 960},
+ {1920, 1080},
+ {2560, 1440},
+ {2560, 1600},
+ {2560, 1920},
+ {3840, 2160}
+ };
+
+ if (g_config.display.window.startup_size == CONFIG_DISPLAY_WINDOW_STARTUP_SIZE_LAST_USED) {
+ window_width = g_config.display.window.last_width;
+ window_height = g_config.display.window.last_height;
+ } else {
+ window_width = res_table[g_config.display.window.startup_size-1][0];
+ window_height = res_table[g_config.display.window.startup_size-1][1];
+ }
+
+ if (window_width < min_window_width) {
+ window_width = min_window_width;
+ }
+ if (window_height < min_window_height) {
+ window_height = min_window_height;
+ }
+
+ SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
+
// Create main window
m_window = SDL_CreateWindow(
- title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 640, 480,
- SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
+ title, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, window_width, window_height,
+ window_flags);
if (m_window == NULL) {
fprintf(stderr, "Failed to create main window\n");
SDL_Quit();
exit(1);
}
g_free(title);
+ SDL_SetWindowMinimumSize(m_window, min_window_width, min_window_height);
SDL_DisplayMode disp_mode;
SDL_GetCurrentDisplayMode(SDL_GetWindowDisplayIndex(m_window), &disp_mode);
-
- int win_w = g_config.display.window.last_width,
- win_h = g_config.display.window.last_height;
-
- if (win_w > 0 && win_h > 0) {
- if (disp_mode.w >= win_w && disp_mode.h >= win_h) {
- SDL_SetWindowSize(m_window, win_w, win_h);
- SDL_SetWindowPosition(m_window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
- }
+ if (disp_mode.w < window_width || disp_mode.h < window_height) {
+ SDL_SetWindowSize(m_window, min_window_width, min_window_height);
+ SDL_SetWindowPosition(m_window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED);
}
m_context = SDL_GL_CreateContext(m_window);
@@ -923,9 +955,9 @@ static void sdl2_display_early_init(DisplayOptions *o)
display_opengl = 1;
SDL_GL_MakeCurrent(m_window, m_context);
- SDL_GL_SetSwapInterval(0);
+ SDL_GL_SetSwapInterval(g_config.display.window.vsync ? 1 : 0);
xemu_hud_init(m_window, m_context);
- blit = create_decal_shader(SHADER_TYPE_BLIT_GAMMA);
+ // blit = create_decal_shader(SHADER_TYPE_BLIT_GAMMA);
}
static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
@@ -942,6 +974,8 @@ static void sdl2_display_init(DisplayState *ds, DisplayOptions *o)
gui_fullscreen = o->has_full_screen && o->full_screen;
+ gui_fullscreen |= g_config.display.window.fullscreen_on_startup;
+
#if 1
// Explicitly set number of outputs to 1 for a single screen. We don't need
// multiple for now, but maybe in the future debug stuff can go on a second
@@ -1145,6 +1179,7 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
*/
GLuint tex = nv2a_get_framebuffer_surface();
if (tex == 0) {
+ // FIXME: Don't upload if notdirty
xb_surface_gl_create_texture(scon->surface);
scon->updates++;
tex = scon->surface->texture;
@@ -1160,72 +1195,9 @@ void sdl2_gl_refresh(DisplayChangeListener *dcl)
qemu_mutex_lock_iothread();
sdl2_poll_events(scon);
- glActiveTexture(GL_TEXTURE0);
- glBindTexture(GL_TEXTURE_2D, tex);
-
- // Get texture dimensions
- int tw, th;
- glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw);
- glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th);
-
- // Get window dimensions
- int ww, wh;
- SDL_GL_GetDrawableSize(scon->real_window, &ww, &wh);
-
- // Calculate scaling factors
- float scale[2];
- if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) {
- // Stretch to fit
- scale[0] = 1.0;
- scale[1] = 1.0;
- } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_CENTER) {
- // Centered
- scale[0] = (float)tw/(float)ww;
- scale[1] = (float)th/(float)wh;
- } else {
- float t_ratio;
- if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
- // Scale to fit window using a fixed 16:9 aspect ratio
- t_ratio = 16.0f/9.0f;
- } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
- t_ratio = 4.0f/3.0f;
- } else {
- // Scale to fit, preserving framebuffer aspect ratio
- t_ratio = (float)tw/(float)th;
- }
-
- float w_ratio = (float)ww/(float)wh;
- if (w_ratio >= t_ratio) {
- scale[0] = t_ratio/w_ratio;
- scale[1] = 1.0;
- } else {
- scale[0] = 1.0;
- scale[1] = w_ratio/t_ratio;
- }
- }
-
- // Render framebuffer and GUI
- struct decal_shader *s = blit;
- s->flip = flip_required;
- glViewport(0, 0, ww, wh);
- glUseProgram(s->prog);
- glBindVertexArray(s->vao);
- glUniform1i(s->FlipY_loc, s->flip);
- glUniform4f(s->ScaleOffset_loc, scale[0], scale[1], 0, 0);
- glUniform4f(s->TexScaleOffset_loc, 1.0, 1.0, 0, 0);
- glUniform1i(s->tex_loc, 0);
-
- const uint8_t *palette = nv2a_get_dac_palette();
- for (int i = 0; i < 256; i++) {
- uint32_t e = (palette[i * 3 + 2] << 16) | (palette[i * 3 + 1] << 8) |
- palette[i * 3];
- glUniform1ui(s->palette_loc[i], e);
- }
-
glClearColor(0, 0, 0, 0);
glClear(GL_COLOR_BUFFER_BIT);
- glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
-
+ xemu_hud_set_framebuffer_texture(tex, flip_required);
xemu_hud_render();
// Release BQL before swapping (which may sleep if swap interval is not immediate)
diff --git a/ui/xui/actions.cc b/ui/xui/actions.cc
new file mode 100644
index 0000000000..e4d9e49005
--- /dev/null
+++ b/ui/xui/actions.cc
@@ -0,0 +1,65 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "common.hh"
+#include "misc.hh"
+#include "xemu-hud.h"
+
+void ActionEjectDisc(void)
+{
+ xemu_settings_set_string(&g_config.sys.files.dvd_path, "");
+ xemu_eject_disc();
+}
+
+void ActionLoadDisc(void)
+{
+ const char *iso_file_filters = ".iso Files\0*.iso\0All Files\0*.*\0";
+ const char *new_disc_path =
+ PausedFileOpen(NOC_FILE_DIALOG_OPEN, iso_file_filters,
+ g_config.sys.files.dvd_path, NULL);
+ if (new_disc_path == NULL) {
+ /* Cancelled */
+ return;
+ }
+ xemu_settings_set_string(&g_config.sys.files.dvd_path, new_disc_path);
+ xemu_load_disc(new_disc_path);
+}
+
+void ActionTogglePause(void)
+{
+ if (runstate_is_running()) {
+ vm_stop(RUN_STATE_PAUSED);
+ } else {
+ vm_start();
+ }
+}
+
+void ActionReset(void)
+{
+ qemu_system_reset_request(SHUTDOWN_CAUSE_GUEST_RESET);
+}
+
+void ActionShutdown(void)
+{
+ qemu_system_shutdown_request(SHUTDOWN_CAUSE_HOST_UI);
+}
+
+void ActionScreenshot(void)
+{
+ g_screenshot_pending = true;
+}
\ No newline at end of file
diff --git a/ui/xui/actions.hh b/ui/xui/actions.hh
new file mode 100644
index 0000000000..2ac680af78
--- /dev/null
+++ b/ui/xui/actions.hh
@@ -0,0 +1,26 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+
+void ActionEjectDisc();
+void ActionLoadDisc();
+void ActionTogglePause();
+void ActionReset();
+void ActionShutdown();
+void ActionScreenshot();
diff --git a/ui/xui/animation.cc b/ui/xui/animation.cc
new file mode 100644
index 0000000000..a947d6f3dd
--- /dev/null
+++ b/ui/xui/animation.cc
@@ -0,0 +1,161 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include
+#include "common.hh"
+#include "animation.hh"
+
+Animation::Animation(float duration)
+: m_duration(duration)
+{
+ Reset();
+}
+
+void Animation::Reset()
+{
+ m_acc = 0;
+}
+
+void Animation::SetDuration(float duration)
+{
+ m_duration = duration;
+}
+
+void Animation::Step()
+{
+ if (g_config.display.ui.use_animations) {
+ ImGuiIO &io = ImGui::GetIO();
+ m_acc += io.DeltaTime;
+ } else {
+ m_acc = m_duration;
+ }
+}
+
+bool Animation::IsComplete()
+{
+ return m_acc >= m_duration;
+}
+
+float Animation::GetLinearValue()
+{
+ if (m_acc < m_duration) {
+ return m_acc / m_duration;
+ } else {
+ return 1.0;
+ }
+}
+
+void Animation::SetLinearValue(float t)
+{
+ m_acc = t * m_duration;
+}
+
+float Animation::GetSinInterpolatedValue()
+{
+ return sin(GetLinearValue() * M_PI * 0.5);
+}
+
+EasingAnimation::EasingAnimation(float ease_in_duration, float ease_out_duration)
+: m_state(AnimationState::PreEasingIn),
+ m_duration_out(ease_out_duration),
+ m_duration_in(ease_in_duration) {}
+
+void EasingAnimation::EaseIn()
+{
+ EaseIn(m_duration_in);
+}
+
+void EasingAnimation::EaseIn(float duration)
+{
+ if (duration == 0) {
+ m_state = AnimationState::Idle;
+ return;
+ }
+ float t = m_animation.GetLinearValue();
+ m_animation.SetDuration(duration);
+ if (m_state == AnimationState::EasingOut) {
+ m_animation.SetLinearValue(1-t);
+ } else if (m_state != AnimationState::EasingIn) {
+ m_animation.Reset();
+ }
+ m_state = AnimationState::EasingIn;
+}
+
+void EasingAnimation::EaseOut()
+{
+ EaseOut(m_duration_out);
+}
+
+void EasingAnimation::EaseOut(float duration)
+{
+ if (duration == 0) {
+ m_state = AnimationState::PostEasingOut;
+ return;
+ }
+ float t = m_animation.GetLinearValue();
+ m_animation.SetDuration(duration);
+ if (m_state == AnimationState::EasingIn) {
+ m_animation.SetLinearValue(1-t);
+ } else if (m_state != AnimationState::EasingOut) {
+ m_animation.Reset();
+ }
+ m_state = AnimationState::EasingOut;
+}
+
+void EasingAnimation::Step()
+{
+ if (m_state == AnimationState::EasingIn ||
+ m_state == AnimationState::EasingOut) {
+ m_animation.Step();
+ if (m_animation.IsComplete()) {
+ if (m_state == AnimationState::EasingIn) {
+ m_state = AnimationState::Idle;
+ } else if (m_state == AnimationState::EasingOut) {
+ m_state = AnimationState::PostEasingOut;
+ }
+ }
+ }
+}
+
+float EasingAnimation::GetLinearValue()
+{
+ switch (m_state) {
+ case AnimationState::PreEasingIn: return 0;
+ case AnimationState::EasingIn: return m_animation.GetLinearValue();
+ case AnimationState::Idle: return 1;
+ case AnimationState::EasingOut: return 1 - m_animation.GetLinearValue();
+ case AnimationState::PostEasingOut: return 0;
+ default: return 0;
+ }
+}
+
+float EasingAnimation::GetSinInterpolatedValue()
+{
+ return sin(GetLinearValue() * M_PI * 0.5);
+}
+
+bool EasingAnimation::IsAnimating()
+{
+ return m_state == AnimationState::EasingIn ||
+ m_state == AnimationState::EasingOut;
+}
+
+bool EasingAnimation::IsComplete()
+{
+ return m_state == AnimationState::PostEasingOut;
+}
diff --git a/ui/xui/animation.hh b/ui/xui/animation.hh
new file mode 100644
index 0000000000..f0f213e49a
--- /dev/null
+++ b/ui/xui/animation.hh
@@ -0,0 +1,73 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include "common.hh"
+
+const ImVec2 EASE_VECTOR_DOWN = ImVec2(0, -25);
+const ImVec2 EASE_VECTOR_LEFT = ImVec2(25, 0);
+const ImVec2 EASE_VECTOR_RIGHT = ImVec2(-25, 0);
+
+enum AnimationState
+{
+ PreEasingIn,
+ EasingIn,
+ Idle,
+ EasingOut,
+ PostEasingOut
+};
+
+// Step a value from 0 to 1 over some duration of time.
+class Animation
+{
+protected:
+ float m_duration;
+ float m_acc;
+
+public:
+ Animation(float duration = 0);
+ void Reset();
+ void SetDuration(float duration);
+ void Step();
+ bool IsComplete();
+ float GetLinearValue();
+ void SetLinearValue(float t);
+ float GetSinInterpolatedValue();
+};
+
+// Stateful animation sequence for easing in and out: 0->1->0
+class EasingAnimation
+{
+protected:
+ AnimationState m_state;
+ Animation m_animation;
+ float m_duration_out;
+ float m_duration_in;
+
+public:
+ EasingAnimation(float ease_in_duration = 1.0, float ease_out_duration = 1.0);
+ void EaseIn();
+ void EaseIn(float duration);
+ void EaseOut();
+ void EaseOut(float duration);
+ void Step();
+ float GetLinearValue();
+ float GetSinInterpolatedValue();
+ bool IsAnimating();
+ bool IsComplete();
+};
diff --git a/ui/xui/common.hh b/ui/xui/common.hh
new file mode 100644
index 0000000000..c937ed5deb
--- /dev/null
+++ b/ui/xui/common.hh
@@ -0,0 +1,55 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+
+#include
+#include
+#include "ui/xemu-settings.h"
+
+#define IMGUI_DEFINE_MATH_OPERATORS
+#include
+#include
+#include
+#include
+#include
+#include
+
+extern "C" {
+#include
+
+// Include necessary QEMU headers
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "sysemu/sysemu.h"
+#include "sysemu/runstate.h"
+#include "hw/xbox/mcpx/apu_debug.h"
+#include "hw/xbox/nv2a/debug.h"
+#include "hw/xbox/nv2a/nv2a.h"
+
+#undef typename
+#undef atomic_fetch_add
+#undef atomic_fetch_and
+#undef atomic_fetch_xor
+#undef atomic_fetch_or
+#undef atomic_fetch_sub
+}
+
+extern bool g_screenshot_pending;
+extern float g_main_menu_height;
diff --git a/ui/xui/compat.cc b/ui/xui/compat.cc
new file mode 100644
index 0000000000..d0405389f5
--- /dev/null
+++ b/ui/xui/compat.cc
@@ -0,0 +1,220 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include
+#include "common.hh"
+#include "compat.hh"
+#include "widgets.hh"
+#include "viewport-manager.hh"
+#include "font-manager.hh"
+#include "xemu-version.h"
+#include "reporting.hh"
+#include "../xemu-settings.h"
+#include "../xemu-os-utils.h"
+
+CompatibilityReporter::CompatibilityReporter()
+{
+ is_open = false;
+
+ report.token = "";
+ report.xemu_version = xemu_version;
+ report.xemu_branch = xemu_branch;
+ report.xemu_commit = xemu_commit;
+ report.xemu_date = xemu_date;
+ report.os_platform = xemu_get_os_platform();
+ report.os_version = xemu_get_os_info();
+ report.cpu = xemu_get_cpu_info();
+ dirty = true;
+ is_xbe_identified = false;
+ did_send = send_result = false;
+}
+
+CompatibilityReporter::~CompatibilityReporter()
+{
+}
+
+void CompatibilityReporter::Draw()
+{
+ if (!is_open) return;
+
+ const char *playability_names[] = {
+ "Broken",
+ "Intro",
+ "Starts",
+ "Playable",
+ "Perfect",
+ };
+
+ const char *playability_descriptions[] = {
+ "This title crashes very soon after launching, or displays nothing at all.",
+ "This title displays an intro sequence, but fails to make it to gameplay.",
+ "This title starts, but may crash or have significant issues.",
+ "This title is playable, but may have minor issues.",
+ "This title is playable from start to finish with no noticable issues."
+ };
+
+ ImGui::SetNextWindowContentSize(ImVec2(550.0f*g_viewport_mgr.m_scale, 0.0f));
+ if (!ImGui::Begin("Report Compatibility", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) {
+ ImGui::End();
+ return;
+ }
+
+ if (ImGui::IsWindowAppearing()) {
+ report.gl_vendor = (const char *)glGetString(GL_VENDOR);
+ report.gl_renderer = (const char *)glGetString(GL_RENDERER);
+ report.gl_version = (const char *)glGetString(GL_VERSION);
+ report.gl_shading_language_version = (const char *)glGetString(GL_SHADING_LANGUAGE_VERSION);
+ struct xbe *xbe = xemu_get_xbe_info();
+ is_xbe_identified = xbe != NULL;
+ if (is_xbe_identified) {
+ report.SetXbeData(xbe);
+ }
+ did_send = send_result = false;
+
+ playability = 3; // Playable
+ report.compat_rating = playability_names[playability];
+ description[0] = '\x00';
+ report.compat_comments = description;
+
+ strncpy(token_buf, g_config.general.user_token, sizeof(token_buf)-1);
+ report.token = token_buf;
+
+ dirty = true;
+ }
+
+ if (!is_xbe_identified) {
+ ImGui::TextWrapped(
+ "An XBE could not be identified. Please launch an official "
+ "Xbox title to submit a compatibility report.");
+ ImGui::End();
+ return;
+ }
+
+ ImGui::TextWrapped(
+ "If you would like to help improve xemu by submitting a compatibility report for this "
+ "title, please select an appropriate playability level, enter a "
+ "brief description, then click 'Send'."
+ "\n\n"
+ "Note: By submitting a report, you acknowledge and consent to "
+ "collection, archival, and publication of information as outlined "
+ "in 'Privacy Disclosure' below.");
+
+ ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
+ ImGui::Separator();
+ ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
+
+ ImGui::Columns(2, "", false);
+ ImGui::SetColumnWidth(0, ImGui::GetWindowWidth()*0.25);
+
+ ImGui::Text("User Token");
+ ImGui::SameLine();
+ HelpMarker("This is a unique access token used to authorize submission of the report. To request a token, click 'Get Token'.");
+ ImGui::NextColumn();
+ float item_width = ImGui::GetColumnWidth()*0.75-20*g_viewport_mgr.m_scale;
+ ImGui::SetNextItemWidth(item_width);
+ ImGui::PushFont(g_font_mgr.m_fixed_width_font);
+ if (ImGui::InputText("###UserToken", token_buf, sizeof(token_buf), 0)) {
+ xemu_settings_set_string(&g_config.general.user_token, token_buf);
+ report.token = token_buf;
+ dirty = true;
+ }
+ ImGui::PopFont();
+ ImGui::SameLine();
+ if (ImGui::Button("Get Token")) {
+ xemu_open_web_browser("https://reports.xemu.app");
+ }
+ ImGui::NextColumn();
+
+ ImGui::Text("Playability");
+ ImGui::NextColumn();
+ ImGui::SetNextItemWidth(item_width);
+ if (ImGui::Combo("###PlayabilityRating", &playability,
+ "Broken\0" "Intro/Menus\0" "Starts\0" "Playable\0" "Perfect\0")) {
+ report.compat_rating = playability_names[playability];
+ dirty = true;
+ }
+ ImGui::SameLine();
+ HelpMarker(playability_descriptions[playability]);
+ ImGui::NextColumn();
+
+ ImGui::Columns(1);
+
+ ImGui::Text("Description");
+ if (ImGui::InputTextMultiline("###desc", description, sizeof(description), ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 6), 0)) {
+ report.compat_comments = description;
+ dirty = true;
+ }
+
+ if (ImGui::TreeNode("Report Details")) {
+ ImGui::PushFont(g_font_mgr.m_fixed_width_font);
+ if (dirty) {
+ serialized_report = report.GetSerializedReport();
+ dirty = false;
+ }
+ ImGui::InputTextMultiline("##build_info", (char*)serialized_report.c_str(), strlen(serialized_report.c_str())+1, ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 7), ImGuiInputTextFlags_ReadOnly);
+ ImGui::PopFont();
+ ImGui::TreePop();
+ }
+
+ if (ImGui::TreeNode("Privacy Disclosure (Please read before submission!)")) {
+ ImGui::TextWrapped(
+ "By volunteering to submit a compatibility report, basic information about your "
+ "computer is collected, including: your operating system version, CPU model, "
+ "graphics card/driver information, and details about the title which are "
+ "extracted from the executable in memory. The contents of this report can be "
+ "seen before submission by expanding 'Report Details'."
+ "\n\n"
+ "Like many websites, upon submission, the public IP address of your computer is "
+ "also recorded with your report. If provided, the identity associated with your "
+ "token is also recorded."
+ "\n\n"
+ "This information will be archived and used to analyze, resolve problems with, "
+ "and improve the application. This information may be made publicly visible, "
+ "for example: to anyone who wishes to see the playability status of a title, as "
+ "indicated by your report.");
+ ImGui::TreePop();
+ }
+
+ ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
+ ImGui::Separator();
+ ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
+
+ if (did_send) {
+ if (send_result) {
+ ImGui::Text("Sent! Thanks.");
+ } else {
+ ImGui::Text("Error: %s (%d)", report.GetResultMessage().c_str(), report.GetResultCode());
+ }
+ ImGui::SameLine();
+ }
+
+ ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+10)*g_viewport_mgr.m_scale);
+
+ ImGui::SetItemDefaultFocus();
+ if (ImGui::Button("Send", ImVec2(120*g_viewport_mgr.m_scale, 0))) {
+ did_send = true;
+ send_result = report.Send();
+ if (send_result) {
+ is_open = false;
+ }
+ }
+
+ ImGui::End();
+}
+
+CompatibilityReporter compatibility_reporter_window;
diff --git a/ui/xui/compat.hh b/ui/xui/compat.hh
new file mode 100644
index 0000000000..edff540a84
--- /dev/null
+++ b/ui/xui/compat.hh
@@ -0,0 +1,41 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include
+#include "reporting.hh"
+
+class CompatibilityReporter
+{
+public:
+ CompatibilityReport report;
+ bool dirty;
+ bool is_open;
+ bool is_xbe_identified;
+ bool did_send, send_result;
+ char token_buf[512];
+ int playability;
+ char description[1024];
+ std::string serialized_report;
+
+ CompatibilityReporter();
+ ~CompatibilityReporter();
+ void Draw();
+};
+
+extern CompatibilityReporter compatibility_reporter_window;
diff --git a/ui/xui/debug.cc b/ui/xui/debug.cc
new file mode 100644
index 0000000000..392b4f35ab
--- /dev/null
+++ b/ui/xui/debug.cc
@@ -0,0 +1,323 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "debug.hh"
+#include "common.hh"
+#include "misc.hh"
+#include "font-manager.hh"
+#include "viewport-manager.hh"
+
+DebugApuWindow::DebugApuWindow() : m_is_open(false)
+{
+}
+
+void DebugApuWindow::Draw()
+{
+ if (!m_is_open)
+ return;
+
+ ImGui::SetNextWindowContentSize(ImVec2(600.0f*g_viewport_mgr.m_scale, 0.0f));
+ if (!ImGui::Begin("Audio Debug", &m_is_open,
+ ImGuiWindowFlags_NoCollapse |
+ ImGuiWindowFlags_AlwaysAutoResize)) {
+ ImGui::End();
+ return;
+ }
+
+ const struct McpxApuDebug *dbg = mcpx_apu_get_debug_info();
+
+
+ ImGui::Columns(2, "", false);
+ int now = SDL_GetTicks() % 1000;
+ float t = now/1000.0f;
+ float freq = 1;
+ float v = fabs(sin(M_PI*t*freq));
+ float c_active = mix(0.4, 0.97, v);
+ float c_inactive = 0.2f;
+
+ int voice_monitor = -1;
+ int voice_info = -1;
+ int voice_mute = -1;
+
+ ImGui::PushFont(g_font_mgr.m_fixed_width_font);
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(2, 2));
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4, 4));
+ for (int i = 0; i < 256; i++)
+ {
+ if (i % 16) {
+ ImGui::SameLine();
+ }
+
+ float c, s, h;
+ h = 0.6;
+ if (dbg->vp.v[i].active) {
+ if (dbg->vp.v[i].paused) {
+ c = c_inactive;
+ s = 0.4;
+ } else {
+ c = c_active;
+ s = 0.7;
+ }
+ if (mcpx_apu_debug_is_muted(i)) {
+ h = 1.0;
+ }
+ } else {
+ c = c_inactive;
+ s = 0;
+ }
+
+ ImGui::PushID(i);
+ ImGui::PushStyleColor(ImGuiCol_Button, (ImVec4)ImColor::HSV(h, s, c));
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, (ImVec4)ImColor::HSV(h, s, 0.8));
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, (ImVec4)ImColor::HSV(h, 0.8f, 1.0));
+ char buf[12];
+ snprintf(buf, sizeof(buf), "%02x", i);
+ ImGui::Button(buf);
+ if (/*dbg->vp.v[i].active &&*/ ImGui::IsItemHovered()) {
+ voice_monitor = i;
+ voice_info = i;
+ }
+ if (ImGui::IsItemClicked(1)) {
+ voice_mute = i;
+ }
+ ImGui::PopStyleColor(3);
+ ImGui::PopID();
+ }
+ ImGui::PopStyleVar(3);
+ ImGui::PopFont();
+
+ if (voice_info >= 0) {
+ const struct McpxApuDebugVoice *voice = &dbg->vp.v[voice_info];
+ ImGui::BeginTooltip();
+ bool is_paused = voice->paused;
+ ImGui::Text("Voice 0x%x/%d %s", voice_info, voice_info, is_paused ? "(Paused)" : "");
+ ImGui::SameLine();
+ ImGui::Text(voice->stereo ? "Stereo" : "Mono");
+
+ ImGui::Separator();
+ ImGui::PushFont(g_font_mgr.m_fixed_width_font);
+
+ const char *noyes[2] = { "NO", "YES" };
+ ImGui::Text("Stream: %-3s Loop: %-3s Persist: %-3s Multipass: %-3s "
+ "Linked: %-3s",
+ noyes[voice->stream], noyes[voice->loop],
+ noyes[voice->persist], noyes[voice->multipass],
+ noyes[voice->linked]);
+
+ const char *cs[4] = { "1 byte", "2 bytes", "ADPCM", "4 bytes" };
+ const char *ss[4] = {
+ "Unsigned 8b PCM",
+ "Signed 16b PCM",
+ "Signed 24b PCM",
+ "Signed 32b PCM"
+ };
+
+ assert(voice->container_size < 4);
+ assert(voice->sample_size < 4);
+ ImGui::Text("Container Size: %s, Sample Size: %s, Samples per Block: %d",
+ cs[voice->container_size], ss[voice->sample_size], voice->samples_per_block);
+ ImGui::Text("Rate: %f (%d Hz)", voice->rate, (int)(48000.0/voice->rate));
+ ImGui::Text("EBO=%d CBO=%d LBO=%d BA=%x",
+ voice->ebo, voice->cbo, voice->lbo, voice->ba);
+ ImGui::Text("Mix: ");
+ for (int i = 0; i < 8; i++) {
+ if (i == 4) ImGui::Text(" ");
+ ImGui::SameLine();
+ char buf[64];
+ if (voice->vol[i] == 0xFFF) {
+ snprintf(buf, sizeof(buf),
+ "Bin %2d (MUTE) ", voice->bin[i]);
+ } else {
+ snprintf(buf, sizeof(buf),
+ "Bin %2d (-%.3f) ", voice->bin[i],
+ (float)((voice->vol[i] >> 6) & 0x3f) +
+ (float)((voice->vol[i] >> 0) & 0x3f) / 64.0);
+ }
+ ImGui::Text("%-17s", buf);
+ }
+ ImGui::PopFont();
+ ImGui::EndTooltip();
+ }
+
+ if (voice_monitor >= 0) {
+ mcpx_apu_debug_isolate_voice(voice_monitor);
+ } else {
+ mcpx_apu_debug_clear_isolations();
+ }
+ if (voice_mute >= 0) {
+ mcpx_apu_debug_toggle_mute(voice_mute);
+ }
+
+ ImGui::SameLine();
+ ImGui::SetColumnWidth(0, ImGui::GetCursorPosX());
+ ImGui::NextColumn();
+
+ ImGui::PushFont(g_font_mgr.m_fixed_width_font);
+ ImGui::Text("Frames: %04d", dbg->frames_processed);
+ ImGui::Text("GP Cycles: %04d", dbg->gp.cycles);
+ ImGui::Text("EP Cycles: %04d", dbg->ep.cycles);
+ bool color = (dbg->utilization > 0.9);
+ if (color) ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(1,0,0,1));
+ ImGui::Text("Utilization: %.2f%%", (dbg->utilization*100));
+ if (color) ImGui::PopStyleColor();
+ ImGui::PopFont();
+
+ static int mon = 0;
+ mon = mcpx_apu_debug_get_monitor();
+ if (ImGui::Combo("Monitor", &mon, "AC97\0VP Only\0GP Only\0EP Only\0GP/EP if enabled\0")) {
+ mcpx_apu_debug_set_monitor(mon);
+ }
+
+ static bool gp_realtime;
+ gp_realtime = dbg->gp_realtime;
+ if (ImGui::Checkbox("GP Realtime\n", &gp_realtime)) {
+ mcpx_apu_debug_set_gp_realtime_enabled(gp_realtime);
+ }
+
+ static bool ep_realtime;
+ ep_realtime = dbg->ep_realtime;
+ if (ImGui::Checkbox("EP Realtime\n", &ep_realtime)) {
+ mcpx_apu_debug_set_ep_realtime_enabled(ep_realtime);
+ }
+
+ ImGui::Columns(1);
+ ImGui::End();
+}
+
+// Utility structure for realtime plot
+struct ScrollingBuffer {
+ int MaxSize;
+ int Offset;
+ ImVector Data;
+ ScrollingBuffer() {
+ MaxSize = 2000;
+ Offset = 0;
+ Data.reserve(MaxSize);
+ }
+ void AddPoint(float x, float y) {
+ if (Data.size() < MaxSize)
+ Data.push_back(ImVec2(x,y));
+ else {
+ Data[Offset] = ImVec2(x,y);
+ Offset = (Offset + 1) % MaxSize;
+ }
+ }
+ void Erase() {
+ if (Data.size() > 0) {
+ Data.shrink(0);
+ Offset = 0;
+ }
+ }
+};
+
+DebugVideoWindow::DebugVideoWindow()
+{
+ m_is_open = false;
+ m_transparent = false;
+}
+
+void DebugVideoWindow::Draw()
+{
+ if (!m_is_open)
+ return;
+
+ float alpha = m_transparent ? 0.2 : 1.0;
+ PushWindowTransparencySettings(m_transparent, 0.2);
+ ImGui::SetNextWindowSize(ImVec2(600.0f*g_viewport_mgr.m_scale, 150.0f*g_viewport_mgr.m_scale), ImGuiCond_Once);
+ if (ImGui::Begin("Video Debug", &m_is_open)) {
+ double x_start, x_end;
+ static ImPlotAxisFlags rt_axis = ImPlotAxisFlags_NoTickLabels;
+ ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(5,5));
+ ImPlot::PushStyleVar(ImPlotStyleVar_FillAlpha, 0.25f);
+ static ScrollingBuffer fps;
+ static float t = 0;
+ if (runstate_is_running()) {
+ t += ImGui::GetIO().DeltaTime;
+ fps.AddPoint(t, g_nv2a_stats.increment_fps);
+ }
+ x_start = t - 10.0;
+ x_end = t;
+
+ float plot_width = 0.5 * (ImGui::GetWindowSize().x -
+ 2 * ImGui::GetStyle().WindowPadding.x -
+ ImGui::GetStyle().ItemSpacing.x);
+
+ ImGui::SetNextWindowBgAlpha(alpha);
+ if (ImPlot::BeginPlot("##ScrollingFPS", ImVec2(plot_width,75*g_viewport_mgr.m_scale))) {
+ ImPlot::SetupAxes(NULL, NULL, rt_axis, rt_axis | ImPlotAxisFlags_Lock);
+ ImPlot::SetupAxesLimits(x_start, x_end, 0, 65, ImPlotCond_Always);
+ if (fps.Data.size() > 0) {
+ ImPlot::PlotShaded("##fps", &fps.Data[0].x, &fps.Data[0].y, fps.Data.size(), 0, fps.Offset, 2 * sizeof(float));
+ ImPlot::PlotLine("##fps", &fps.Data[0].x, &fps.Data[0].y, fps.Data.size(), fps.Offset, 2 * sizeof(float));
+ }
+ ImPlot::Annotation(x_start, 65, ImPlot::GetLastItemColor(), ImVec2(0,0), true, "FPS: %d", g_nv2a_stats.increment_fps);
+ ImPlot::EndPlot();
+ }
+
+ ImGui::SameLine();
+
+ x_end = g_nv2a_stats.frame_count;
+ x_start = x_end - NV2A_PROF_NUM_FRAMES;
+
+ ImPlot::PushStyleColor(ImPlotCol_Line, ImPlot::GetColormapColor(1));
+ ImGui::SetNextWindowBgAlpha(alpha);
+ if (ImPlot::BeginPlot("##ScrollingMSPF", ImVec2(plot_width,75*g_viewport_mgr.m_scale))) {
+ ImPlot::SetupAxes(NULL, NULL, rt_axis, rt_axis | ImPlotAxisFlags_Lock);
+ ImPlot::SetupAxesLimits(x_start, x_end, 0, 100, ImPlotCond_Always);
+ ImPlot::PlotShaded("##mspf", &g_nv2a_stats.frame_history[0].mspf, NV2A_PROF_NUM_FRAMES, 0, 1, x_start, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
+ ImPlot::PlotLine("##mspf", &g_nv2a_stats.frame_history[0].mspf, NV2A_PROF_NUM_FRAMES, 1, x_start, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
+ ImPlot::Annotation(x_start, 100, ImPlot::GetLastItemColor(), ImVec2(0,0), true, "MSPF: %d", g_nv2a_stats.frame_history[(g_nv2a_stats.frame_ptr - 1) % NV2A_PROF_NUM_FRAMES].mspf);
+ ImPlot::EndPlot();
+ }
+ ImPlot::PopStyleColor();
+
+ if (ImGui::TreeNode("Advanced")) {
+ ImGui::SetNextWindowBgAlpha(alpha);
+ if (ImPlot::BeginPlot("##ScrollingDraws", ImVec2(-1,-1))) {
+ ImPlot::SetupAxes(NULL, NULL, rt_axis, rt_axis | ImPlotAxisFlags_Lock);
+ ImPlot::SetupAxesLimits(x_start, x_end, 0, 1500, ImPlotCond_Always);
+ for (int i = 0; i < NV2A_PROF__COUNT; i++) {
+ ImGui::PushID(i);
+ char title[64];
+ snprintf(title, sizeof(title), "%s: %d",
+ nv2a_profile_get_counter_name(i),
+ nv2a_profile_get_counter_value(i));
+ ImPlot::PushStyleColor(ImPlotCol_Line, ImPlot::GetColormapColor(i));
+ ImPlot::PushStyleColor(ImPlotCol_Fill, ImPlot::GetColormapColor(i));
+ ImPlot::PlotLine(title, &g_nv2a_stats.frame_history[0].counters[i], NV2A_PROF_NUM_FRAMES, 1, x_start, g_nv2a_stats.frame_ptr, sizeof(g_nv2a_stats.frame_working));
+ ImPlot::PopStyleColor(2);
+ ImGui::PopID();
+ }
+ ImPlot::EndPlot();
+ }
+ ImGui::TreePop();
+ }
+
+ if (ImGui::IsWindowHovered() && ImGui::IsMouseClicked(2)) {
+ m_transparent = !m_transparent;
+ }
+
+ ImPlot::PopStyleVar(2);
+ }
+ ImGui::End();
+ ImGui::PopStyleColor(5);
+}
+
+DebugApuWindow apu_window;
+DebugVideoWindow video_window;
diff --git a/ui/xui/debug.hh b/ui/xui/debug.hh
new file mode 100644
index 0000000000..92671dceff
--- /dev/null
+++ b/ui/xui/debug.hh
@@ -0,0 +1,40 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+
+class DebugApuWindow
+{
+public:
+ bool m_is_open;
+ DebugApuWindow();
+ void Draw();
+};
+
+class DebugVideoWindow
+{
+public:
+ bool m_is_open;
+ bool m_transparent;
+
+ DebugVideoWindow();
+ void Draw();
+};
+
+extern DebugApuWindow apu_window;
+extern DebugVideoWindow video_window;
diff --git a/ui/xui/font-manager.cc b/ui/xui/font-manager.cc
new file mode 100644
index 0000000000..6dadbc74b0
--- /dev/null
+++ b/ui/xui/font-manager.cc
@@ -0,0 +1,118 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "font-manager.hh"
+#include "viewport-manager.hh"
+
+#include "data/Roboto-Medium.ttf.h"
+#include "data/RobotoCondensed-Regular.ttf.h"
+#include "data/font_awesome_6_1_1_solid.otf.h"
+#include "data/abxy.ttf.h"
+
+FontManager g_font_mgr;
+
+FontManager::FontManager()
+{
+ m_last_viewport_scale = 1;
+ m_font_scale = 1;
+}
+
+void FontManager::Rebuild()
+{
+ ImGuiIO &io = ImGui::GetIO();
+
+ // FIXME: Trim FA to only glyphs in use
+
+ io.Fonts->Clear();
+
+ {
+ ImFontConfig config;
+ config.FontDataOwnedByAtlas = false;
+ m_default_font = io.Fonts->AddFontFromMemoryTTF(
+ (void *)Roboto_Medium_data, Roboto_Medium_size,
+ 16.0f * g_viewport_mgr.m_scale * m_font_scale, &config);
+ m_menu_font_small = io.Fonts->AddFontFromMemoryTTF(
+ (void *)RobotoCondensed_Regular_data, RobotoCondensed_Regular_size,
+ 22.0f * g_viewport_mgr.m_scale * m_font_scale, &config);
+ }
+ {
+ ImFontConfig config;
+ config.FontDataOwnedByAtlas = false;
+ config.MergeMode = true;
+ config.GlyphOffset =
+ ImVec2(0, 13 * g_viewport_mgr.m_scale * m_font_scale);
+ config.GlyphMaxAdvanceX = 24.0f * g_viewport_mgr.m_scale * m_font_scale;
+ static const ImWchar icon_ranges[] = { 0xf900, 0xf903, 0 };
+ io.Fonts->AddFontFromMemoryTTF((void *)abxy_data, abxy_size,
+ 40.0f * g_viewport_mgr.m_scale *
+ m_font_scale,
+ &config, icon_ranges);
+ }
+ {
+ ImFontConfig config;
+ config.FontDataOwnedByAtlas = false;
+ m_menu_font_medium = io.Fonts->AddFontFromMemoryTTF(
+ (void *)RobotoCondensed_Regular_data, RobotoCondensed_Regular_size,
+ 26.0f * g_viewport_mgr.m_scale * m_font_scale, &config);
+ m_menu_font = io.Fonts->AddFontFromMemoryTTF(
+ (void *)RobotoCondensed_Regular_data, RobotoCondensed_Regular_size,
+ 34.0f * g_viewport_mgr.m_scale * m_font_scale, &config);
+ }
+ {
+ ImFontConfig config;
+ config.FontDataOwnedByAtlas = false;
+ config.MergeMode = true;
+ config.GlyphOffset =
+ ImVec2(0, -3 * g_viewport_mgr.m_scale * m_font_scale);
+ config.GlyphMinAdvanceX = 32.0f * g_viewport_mgr.m_scale * m_font_scale;
+ static const ImWchar icon_ranges[] = { ICON_MIN_FA, ICON_MAX_FA, 0 };
+ io.Fonts->AddFontFromMemoryTTF((void *)font_awesome_6_1_1_solid_data,
+ font_awesome_6_1_1_solid_size,
+ 18.0f * g_viewport_mgr.m_scale *
+ m_font_scale,
+ &config, icon_ranges);
+ }
+
+ // {
+ // ImFontConfig config;
+ // config.FontDataOwnedByAtlas = false;
+ // static const ImWchar icon_ranges[] = { 0xf04c, 0xf04c, 0 };
+ // m_big_state_icon_font = io.Fonts->AddFontFromMemoryTTF(
+ // (void *)font_awesome_6_1_1_solid_data,
+ // font_awesome_6_1_1_solid_size,
+ // 64.0f * g_viewport_mgr.m_scale * m_font_scale, &config,
+ // icon_ranges);
+ // }
+ {
+ ImFontConfig config = ImFontConfig();
+ config.OversampleH = config.OversampleV = 1;
+ config.PixelSnapH = true;
+ config.SizePixels = 13.0f*g_viewport_mgr.m_scale;
+ m_fixed_width_font = io.Fonts->AddFontDefault(&config);
+ }
+
+ ImGui_ImplOpenGL3_CreateFontsTexture();
+}
+
+void FontManager::Update()
+{
+ if (g_viewport_mgr.m_scale != m_last_viewport_scale) {
+ Rebuild();
+ m_last_viewport_scale = g_viewport_mgr.m_scale;
+ }
+}
diff --git a/ui/xui/font-manager.hh b/ui/xui/font-manager.hh
new file mode 100644
index 0000000000..5787f2d4ad
--- /dev/null
+++ b/ui/xui/font-manager.hh
@@ -0,0 +1,45 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include "common.hh"
+
+#include "IconsFontAwesome6.h"
+#define ICON_BUTTON_A "\xef\xa4\x80"
+#define ICON_BUTTON_B "\xef\xa4\x81"
+#define ICON_BUTTON_X "\xef\xa4\x82"
+#define ICON_BUTTON_Y "\xef\xa4\x83"
+
+class FontManager
+{
+public:
+ ImFont *m_default_font;
+ ImFont *m_fixed_width_font;
+ ImFont *m_menu_font;
+ ImFont *m_menu_font_small;
+ ImFont *m_menu_font_medium;
+ // ImFont *m_big_state_icon_font;
+ float m_last_viewport_scale;
+ float m_font_scale;
+
+ FontManager();
+ void Rebuild();
+ void Update();
+};
+
+extern FontManager g_font_mgr;
diff --git a/ui/xui/gl-helpers.cc b/ui/xui/gl-helpers.cc
new file mode 100644
index 0000000000..34f04b9395
--- /dev/null
+++ b/ui/xui/gl-helpers.cc
@@ -0,0 +1,777 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "common.hh"
+#include
+#include
+#include
+#include
+#include "gl-helpers.hh"
+#include "stb_image.h"
+#include "data/controller_mask.png.h"
+#include "data/logo_sdf.png.h"
+#include "ui/shader/xemu-logo-frag.h"
+#include "notifications.hh"
+
+Fbo *controller_fbo,
+ *logo_fbo;
+GLuint g_controller_tex,
+ g_logo_tex;
+
+enum ShaderType {
+ Blit,
+ BlitGamma, // FIMXE: Move to nv2a_get_framebuffer_surface
+ Mask,
+ Logo,
+};
+
+typedef struct DecalShader_
+{
+ int flip;
+ float scale;
+ uint32_t time;
+ GLuint prog, vao, vbo, ebo;
+ GLint flipy_loc;
+ GLint tex_loc;
+ GLint scale_offset_loc;
+ GLint tex_scale_offset_loc;
+ GLint color_primary_loc;
+ GLint color_secondary_loc;
+ GLint color_fill_loc;
+ GLint time_loc;
+ GLint scale_loc;
+ GLint palette_loc[256];
+} DecalShader;
+
+static DecalShader *g_decal_shader,
+ *g_logo_shader,
+ *g_framebuffer_shader;
+
+GLint Fbo::vp[4];
+GLint Fbo::original_fbo;
+bool Fbo::blend;
+
+DecalShader *NewDecalShader(enum ShaderType type);
+void DeleteDecalShader(DecalShader *s);
+
+static GLint GetCurrentFbo()
+{
+ GLint fbo;
+ glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, (GLint*)&fbo);
+ return fbo;
+}
+
+Fbo::Fbo(int width, int height)
+{
+ w = width;
+ h = height;
+
+ // Allocate the texture
+ glGenTextures(1, &tex);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA,
+ GL_UNSIGNED_BYTE, NULL);
+
+ GLint original = GetCurrentFbo();
+
+ // Allocate the framebuffer object
+ glGenFramebuffers(1, &fbo);
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D,
+ tex, 0);
+ GLenum DrawBuffers[1] = { GL_COLOR_ATTACHMENT0 };
+ glDrawBuffers(1, DrawBuffers);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, original);
+}
+
+Fbo::~Fbo()
+{
+ glDeleteTextures(1, &tex);
+ glDeleteFramebuffers(1, &fbo);
+}
+
+void Fbo::Target()
+{
+ GLint vp[4];
+ glGetIntegerv(GL_VIEWPORT, vp);
+
+ original_fbo = GetCurrentFbo();
+ blend = glIsEnabled(GL_BLEND);
+ if (!blend) {
+ glEnable(GL_BLEND);
+ }
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glViewport(0, 0, w, h);
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+}
+
+void Fbo::Restore()
+{
+ if (!blend) {
+ glDisable(GL_BLEND);
+ }
+
+ // Restore default framebuffer, viewport, blending function
+ glBindFramebuffer(GL_FRAMEBUFFER, original_fbo);
+ glViewport(vp[0], vp[1], vp[2], vp[3]);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+}
+
+static GLuint InitTexture(unsigned char *data, int width, int height,
+ int channels)
+{
+ GLuint tex;
+ glGenTextures(1, &tex);
+ assert(tex != 0);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ return tex;
+}
+
+static GLuint LoadTextureFromMemory(const unsigned char *buf, unsigned int size)
+{
+ // Flip vertically so textures are loaded according to GL convention.
+ stbi_set_flip_vertically_on_load(1);
+
+ int width, height, channels = 0;
+ unsigned char *data = stbi_load_from_memory(buf, size, &width, &height, &channels, 4);
+ assert(data != NULL);
+
+ GLuint tex = InitTexture(data, width, height, channels);
+ stbi_image_free(data);
+
+ return tex;
+}
+
+static GLuint Shader(GLenum type, const char *src)
+{
+ char err_buf[512];
+ GLuint shader = glCreateShader(type);
+ assert(shader && "Failed to create shader");
+
+ glShaderSource(shader, 1, &src, NULL);
+ glCompileShader(shader);
+
+ GLint status;
+ glGetShaderiv(shader, GL_COMPILE_STATUS, &status);
+ if (status != GL_TRUE) {
+ glGetShaderInfoLog(shader, sizeof(err_buf), NULL, err_buf);
+ fprintf(stderr, "Shader compilation failed: %s\n\n"
+ "[Shader Source]\n"
+ "%s\n", err_buf, src);
+ assert(0);
+ }
+
+ return shader;
+}
+
+DecalShader *NewDecalShader(enum ShaderType type)
+{
+ // Allocate shader wrapper object
+ DecalShader *s = new DecalShader;
+ assert(s != NULL);
+ s->flip = 0;
+ s->scale = 1.4;
+ s->time = 0;
+
+ const char *vert_src = R"(
+#version 150 core
+uniform bool in_FlipY;
+uniform vec4 in_ScaleOffset;
+uniform vec4 in_TexScaleOffset;
+in vec2 in_Position;
+in vec2 in_Texcoord;
+out vec2 Texcoord;
+void main() {
+ vec2 t = in_Texcoord;
+ if (in_FlipY) t.y = 1-t.y;
+ Texcoord = t*in_TexScaleOffset.xy + in_TexScaleOffset.zw;
+ gl_Position = vec4(in_Position*in_ScaleOffset.xy+in_ScaleOffset.zw, 0.0, 1.0);
+}
+)";
+ GLuint vert = Shader(GL_VERTEX_SHADER, vert_src);
+ assert(vert != 0);
+
+// const char *image_frag_src = R"(
+// #version 150 core
+// uniform sampler2D tex;
+// in vec2 Texcoord;
+// out vec4 out_Color;
+// void main() {
+// out_Color.rgba = texture(tex, Texcoord);
+// }
+// )";
+
+ const char *image_gamma_frag_src = R"(
+#version 400 core
+uniform sampler2D tex;
+uniform uint palette[256];
+float gamma_ch(int ch, float col)
+{
+ return float(bitfieldExtract(palette[uint(col * 255.0)], ch*8, 8)) / 255.0;
+}
+
+vec4 gamma(vec4 col)
+{
+ return vec4(gamma_ch(0, col.r), gamma_ch(1, col.g), gamma_ch(2, col.b), col.a);
+}
+in vec2 Texcoord;
+out vec4 out_Color;
+void main() {
+ out_Color.rgba = gamma(texture(tex, Texcoord));
+}
+)";
+
+ // Simple 2-color decal shader
+ // - in_ColorFill is first pass
+ // - Red channel of the texture is used as primary color, mixed with 1-Red for
+ // secondary color.
+ // - Blue is a lazy alpha removal for now
+ // - Alpha channel passed through
+ const char *mask_frag_src = R"(
+#version 150 core
+uniform sampler2D tex;
+uniform vec4 in_ColorPrimary;
+uniform vec4 in_ColorSecondary;
+uniform vec4 in_ColorFill;
+in vec2 Texcoord;
+out vec4 out_Color;
+void main() {
+ vec4 t = texture(tex, Texcoord);
+ out_Color.rgba = in_ColorFill.rgba;
+ out_Color.rgb += mix(in_ColorSecondary.rgb, in_ColorPrimary.rgb, t.r);
+ out_Color.a += t.a - t.b;
+}
+)";
+
+ const char *frag_src = NULL;
+ switch (type) {
+ case ShaderType::Mask: frag_src = mask_frag_src; break;
+ // case ShaderType::Blit: frag_src = image_frag_src; break;
+ case ShaderType::BlitGamma: frag_src = image_gamma_frag_src; break;
+ case ShaderType::Logo: frag_src = xemu_logo_frag_src; break;
+ default: assert(0);
+ }
+ GLuint frag = Shader(GL_FRAGMENT_SHADER, frag_src);
+ assert(frag != 0);
+
+ // Link vertex and fragment shaders
+ s->prog = glCreateProgram();
+ glAttachShader(s->prog, vert);
+ glAttachShader(s->prog, frag);
+ glBindFragDataLocation(s->prog, 0, "out_Color");
+ glLinkProgram(s->prog);
+ glUseProgram(s->prog);
+
+ // Flag shaders for deletion when program is deleted
+ glDeleteShader(vert);
+ glDeleteShader(frag);
+
+ s->flipy_loc = glGetUniformLocation(s->prog, "in_FlipY");
+ s->scale_offset_loc = glGetUniformLocation(s->prog, "in_ScaleOffset");
+ s->tex_scale_offset_loc =
+ glGetUniformLocation(s->prog, "in_TexScaleOffset");
+ s->tex_loc = glGetUniformLocation(s->prog, "tex");
+ s->color_primary_loc = glGetUniformLocation(s->prog, "in_ColorPrimary");
+ s->color_secondary_loc = glGetUniformLocation(s->prog, "in_ColorSecondary");
+ s->color_fill_loc = glGetUniformLocation(s->prog, "in_ColorFill");
+ s->time_loc = glGetUniformLocation(s->prog, "iTime");
+ s->scale_loc = glGetUniformLocation(s->prog, "scale");
+ for (int i = 0; i < 256; i++) {
+ char name[64];
+ snprintf(name, sizeof(name), "palette[%d]", i);
+ s->palette_loc[i] = glGetUniformLocation(s->prog, name);
+ }
+
+ const GLfloat verts[6][4] = {
+ // x y s t
+ { -1.0f, -1.0f, 0.0f, 0.0f }, // BL
+ { -1.0f, 1.0f, 0.0f, 1.0f }, // TL
+ { 1.0f, 1.0f, 1.0f, 1.0f }, // TR
+ { 1.0f, -1.0f, 1.0f, 0.0f }, // BR
+ };
+ const GLint indicies[] = { 0, 1, 2, 3 };
+
+ glGenVertexArrays(1, &s->vao);
+ glBindVertexArray(s->vao);
+
+ glGenBuffers(1, &s->vbo);
+ glBindBuffer(GL_ARRAY_BUFFER, s->vbo);
+ glBufferData(GL_ARRAY_BUFFER, sizeof(verts), verts, GL_STATIC_COPY);
+
+ glGenBuffers(1, &s->ebo);
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, s->ebo);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indicies), indicies, GL_STATIC_DRAW);
+
+ GLint loc = glGetAttribLocation(s->prog, "in_Position");
+ if (loc >= 0) {
+ glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)0);
+ glEnableVertexAttribArray(loc);
+ }
+
+ loc = glGetAttribLocation(s->prog, "in_Texcoord");
+ if (loc >= 0) {
+ glVertexAttribPointer(loc, 2, GL_FLOAT, GL_FALSE, 4*sizeof(GLfloat), (void*)(2*sizeof(GLfloat)));
+ glEnableVertexAttribArray(loc);
+ }
+
+ return s;
+}
+
+void RenderDecal(DecalShader *s, float x, float y, float w, float h,
+ float tex_x, float tex_y, float tex_w, float tex_h,
+ uint32_t primary, uint32_t secondary, uint32_t fill)
+{
+ GLint vp[4];
+ glGetIntegerv(GL_VIEWPORT, vp);
+ float ww = vp[2], wh = vp[3];
+
+ x = (int)x;
+ y = (int)y;
+ w = (int)w;
+ h = (int)h;
+ tex_x = (int)tex_x;
+ tex_y = (int)tex_y;
+ tex_w = (int)tex_w;
+ tex_h = (int)tex_h;
+
+ int tw_i, th_i;
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw_i);
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th_i);
+ float tw = tw_i, th = th_i;
+
+ #define COL(color, c) (float)(((color)>>((c)*8)) & 0xff)/255.0
+ glUniform1i(s->flipy_loc, s->flip);
+ glUniform4f(s->scale_offset_loc, w / ww, h / wh, -1 + ((2 * x + w) / ww),
+ -1 + ((2 * y + h) / wh));
+ glUniform4f(s->tex_scale_offset_loc, tex_w / tw, tex_h / th, tex_x / tw,
+ tex_y / th);
+ glUniform1i(s->tex_loc, 0);
+ glUniform4f(s->color_primary_loc, COL(primary, 3), COL(primary, 2),
+ COL(primary, 1), COL(primary, 0));
+ glUniform4f(s->color_secondary_loc, COL(secondary, 3), COL(secondary, 2),
+ COL(secondary, 1), COL(secondary, 0));
+ glUniform4f(s->color_fill_loc, COL(fill, 3), COL(fill, 2), COL(fill, 1),
+ COL(fill, 0));
+ if (s->time_loc >= 0) glUniform1f(s->time_loc, s->time/1000.0f);
+ if (s->scale_loc >= 0) glUniform1f(s->scale_loc, s->scale);
+ #undef COL
+ glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
+}
+
+struct rect {
+ int x, y, w, h;
+};
+
+static const struct rect tex_items[] = {
+ { 0, 148, 467, 364 }, // obj_controller
+ { 0, 81, 67, 67 }, // obj_lstick
+ { 0, 14, 67, 67 }, // obj_rstick
+ { 67, 104, 68, 44 }, // obj_port_socket
+ { 67, 76, 28, 28 }, // obj_port_lbl_1
+ { 67, 48, 28, 28 }, // obj_port_lbl_2
+ { 67, 20, 28, 28 }, // obj_port_lbl_3
+ { 95, 76, 28, 28 }, // obj_port_lbl_4
+};
+
+enum tex_item_names {
+ obj_controller,
+ obj_lstick,
+ obj_rstick,
+ obj_port_socket,
+ obj_port_lbl_1,
+ obj_port_lbl_2,
+ obj_port_lbl_3,
+ obj_port_lbl_4,
+};
+
+void InitCustomRendering(void)
+{
+ glActiveTexture(GL_TEXTURE0);
+ g_controller_tex =
+ LoadTextureFromMemory(controller_mask_data, controller_mask_size);
+ g_decal_shader = NewDecalShader(ShaderType::Mask);
+ controller_fbo = new Fbo(512, 512);
+
+ g_logo_tex = LoadTextureFromMemory(logo_sdf_data, logo_sdf_size);
+ g_logo_shader = NewDecalShader(ShaderType::Logo);
+ logo_fbo = new Fbo(512, 512);
+
+ g_framebuffer_shader = NewDecalShader(ShaderType::BlitGamma);
+}
+
+static void RenderMeter(DecalShader *s, float x, float y, float width,
+ float height, float p, uint32_t color_bg,
+ uint32_t color_fg)
+{
+ RenderDecal(s, x, y, width, height, 0, 0, 1, 1, 0, 0, color_bg);
+ RenderDecal(s, x, y, width * p, height, 0, 0, 1, 1, 0, 0, color_fg);
+}
+
+void RenderController(float frame_x, float frame_y, uint32_t primary_color,
+ uint32_t secondary_color, ControllerState *state)
+{
+ // Location within the controller texture of masked button locations,
+ // relative to the origin of the controller
+ const struct rect jewel = { 177, 172, 113, 118 };
+ const struct rect lstick_ctr = { 93, 246, 0, 0 };
+ const struct rect rstick_ctr = { 342, 148, 0, 0 };
+ const struct rect buttons[12] = {
+ { 367, 187, 30, 38 }, // A
+ { 368, 229, 30, 38 }, // B
+ { 330, 204, 30, 38 }, // X
+ { 331, 247, 30, 38 }, // Y
+ { 82, 121, 31, 47 }, // D-Left
+ { 104, 160, 44, 25 }, // D-Up
+ { 141, 121, 31, 47 }, // D-Right
+ { 104, 105, 44, 25 }, // D-Down
+ { 187, 94, 34, 24 }, // Back
+ { 246, 94, 36, 26 }, // Start
+ { 348, 288, 30, 38 }, // White
+ { 386, 268, 30, 38 }, // Black
+ };
+
+ uint8_t alpha = 0;
+ uint32_t now = SDL_GetTicks();
+ float t;
+
+ glUseProgram(g_decal_shader->prog);
+ glBindVertexArray(g_decal_shader->vao);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, g_controller_tex);
+
+ // Add a 5 pixel space around the controller so we can wiggle the controller
+ // around to visualize rumble in action
+ frame_x += 5;
+ frame_y += 5;
+ float original_frame_x = frame_x;
+ float original_frame_y = frame_y;
+
+ // Floating point versions that will get scaled
+ float rumble_l = 0;
+ float rumble_r = 0;
+
+ glBlendEquation(GL_FUNC_ADD);
+ glBlendFunc(GL_ONE, GL_ZERO);
+
+ uint32_t jewel_color = secondary_color;
+
+ // Check to see if the guide button is pressed
+ const uint32_t animate_guide_button_duration = 2000;
+ if (state->buttons & CONTROLLER_BUTTON_GUIDE) {
+ state->animate_guide_button_end = now + animate_guide_button_duration;
+ }
+
+ if (now < state->animate_guide_button_end) {
+ t = 1.0f - (float)(state->animate_guide_button_end-now)/(float)animate_guide_button_duration;
+ float sin_wav = (1-sin(M_PI * t / 2.0f));
+
+ // Animate guide button by highlighting logo jewel and fading out over time
+ alpha = sin_wav * 255.0f;
+ jewel_color = primary_color + alpha;
+
+ // Add a little extra flare: wiggle the frame around while we rumble
+ frame_x += ((float)(rand() % 5)-2.5) * (1-t);
+ frame_y += ((float)(rand() % 5)-2.5) * (1-t);
+ rumble_l = rumble_r = sin_wav;
+ }
+
+ // Render controller texture
+ RenderDecal(g_decal_shader, frame_x + 0, frame_y + 0,
+ tex_items[obj_controller].w, tex_items[obj_controller].h,
+ tex_items[obj_controller].x, tex_items[obj_controller].y,
+ tex_items[obj_controller].w, tex_items[obj_controller].h,
+ primary_color, secondary_color, 0);
+
+ glBlendFunc(GL_ONE_MINUS_DST_ALPHA, GL_ONE); // Blend with controller cutouts
+ RenderDecal(g_decal_shader, frame_x + jewel.x, frame_y + jewel.y, jewel.w,
+ jewel.h, 0, 0, 1, 1, 0, 0, jewel_color);
+
+ // The controller has alpha cutouts where the buttons are. Draw a surface
+ // behind the buttons if they are activated
+ for (int i = 0; i < 12; i++) {
+ if (state->buttons & (1 << i)) {
+ RenderDecal(g_decal_shader, frame_x + buttons[i].x,
+ frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0,
+ 0, 1, 1, 0, 0, primary_color + 0xff);
+ }
+ }
+
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Blend with controller
+
+ // Render left thumbstick
+ float w = tex_items[obj_lstick].w;
+ float h = tex_items[obj_lstick].h;
+ float c_x = frame_x+lstick_ctr.x;
+ float c_y = frame_y+lstick_ctr.y;
+ float lstick_x = (float)state->axis[CONTROLLER_AXIS_LSTICK_X]/32768.0;
+ float lstick_y = (float)state->axis[CONTROLLER_AXIS_LSTICK_Y]/32768.0;
+ RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * lstick_x),
+ (int)(c_y - h / 2.0f + 10.0f * lstick_y), w, h,
+ tex_items[obj_lstick].x, tex_items[obj_lstick].y, w, h,
+ (state->buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color :
+ primary_color,
+ (state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color :
+ secondary_color,
+ 0);
+
+ // Render right thumbstick
+ w = tex_items[obj_rstick].w;
+ h = tex_items[obj_rstick].h;
+ c_x = frame_x+rstick_ctr.x;
+ c_y = frame_y+rstick_ctr.y;
+ float rstick_x = (float)state->axis[CONTROLLER_AXIS_RSTICK_X]/32768.0;
+ float rstick_y = (float)state->axis[CONTROLLER_AXIS_RSTICK_Y]/32768.0;
+ RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * rstick_x),
+ (int)(c_y - h / 2.0f + 10.0f * rstick_y), w, h,
+ tex_items[obj_rstick].x, tex_items[obj_rstick].y, w, h,
+ (state->buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color :
+ primary_color,
+ (state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color :
+ secondary_color,
+ 0);
+
+ glBlendFunc(GL_ONE, GL_ZERO); // Don't blend, just overwrite values in buffer
+
+ // Render trigger bars
+ float ltrig = state->axis[CONTROLLER_AXIS_LTRIG] / 32767.0;
+ float rtrig = state->axis[CONTROLLER_AXIS_RTRIG] / 32767.0;
+ const uint32_t animate_trigger_duration = 1000;
+ if ((ltrig > 0) || (rtrig > 0)) {
+ state->animate_trigger_end = now + animate_trigger_duration;
+ rumble_l = fmax(rumble_l, ltrig);
+ rumble_r = fmax(rumble_r, rtrig);
+ }
+
+ // Animate trigger alpha down after a period of inactivity
+ alpha = 0x80;
+ if (state->animate_trigger_end > now) {
+ t = 1.0f - (float)(state->animate_trigger_end-now)/(float)animate_trigger_duration;
+ float sin_wav = (1-sin(M_PI * t / 2.0f));
+ alpha += fmin(sin_wav * 0x40, 0x80);
+ }
+
+ RenderMeter(g_decal_shader, original_frame_x + 10,
+ original_frame_y + tex_items[obj_controller].h + 20, 150, 5,
+ ltrig, primary_color + alpha, primary_color + 0xff);
+ RenderMeter(g_decal_shader,
+ original_frame_x + tex_items[obj_controller].w - 160,
+ original_frame_y + tex_items[obj_controller].h + 20, 150, 5,
+ rtrig, primary_color + alpha, primary_color + 0xff);
+
+ // Apply rumble updates
+ state->rumble_l = (int)(rumble_l * (float)0xffff);
+ state->rumble_r = (int)(rumble_r * (float)0xffff);
+
+ glBindVertexArray(0);
+ glUseProgram(0);
+}
+
+void RenderControllerPort(float frame_x, float frame_y, int i,
+ uint32_t port_color)
+{
+ glUseProgram(g_decal_shader->prog);
+ glBindVertexArray(g_decal_shader->vao);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, g_controller_tex);
+ glBlendFunc(GL_ONE, GL_ZERO);
+
+ // Render port socket
+ RenderDecal(g_decal_shader, frame_x, frame_y, tex_items[obj_port_socket].w,
+ tex_items[obj_port_socket].h, tex_items[obj_port_socket].x,
+ tex_items[obj_port_socket].y, tex_items[obj_port_socket].w,
+ tex_items[obj_port_socket].h, port_color, port_color, 0);
+
+ frame_x += (tex_items[obj_port_socket].w-tex_items[obj_port_lbl_1].w)/2;
+ frame_y += tex_items[obj_port_socket].h + 8;
+
+ // Render port label
+ RenderDecal(
+ g_decal_shader, frame_x, frame_y, tex_items[obj_port_lbl_1 + i].w,
+ tex_items[obj_port_lbl_1 + i].h, tex_items[obj_port_lbl_1 + i].x,
+ tex_items[obj_port_lbl_1 + i].y, tex_items[obj_port_lbl_1 + i].w,
+ tex_items[obj_port_lbl_1 + i].h, port_color, port_color, 0);
+
+ glBindVertexArray(0);
+ glUseProgram(0);
+}
+
+void RenderLogo(uint32_t time, uint32_t primary_color, uint32_t secondary_color,
+ uint32_t fill_color)
+{
+ g_logo_shader->time = time;
+ glUseProgram(g_logo_shader->prog);
+ glBindVertexArray(g_decal_shader->vao);
+ glBlendFunc(GL_ONE, GL_ZERO);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, g_logo_tex);
+ RenderDecal(g_logo_shader, 0, 0, 512, 512, 0, 0, 128, 128, primary_color,
+ secondary_color, fill_color);
+ glBindVertexArray(0);
+ glUseProgram(0);
+}
+
+void RenderFramebuffer(GLint tex, int width, int height, bool flip)
+{
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, tex);
+
+ int tw, th;
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &tw);
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &th);
+
+ // Calculate scaling factors
+ float scale[2];
+ if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_STRETCH) {
+ // Stretch to fit
+ scale[0] = 1.0;
+ scale[1] = 1.0;
+ } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_CENTER) {
+ // Centered
+ scale[0] = (float)tw/(float)width;
+ scale[1] = (float)th/(float)height;
+ } else {
+ float t_ratio;
+ if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
+ // Scale to fit window using a fixed 16:9 aspect ratio
+ t_ratio = 16.0f/9.0f;
+ } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
+ t_ratio = 4.0f/3.0f;
+ } else {
+ // Scale to fit, preserving framebuffer aspect ratio
+ t_ratio = (float)tw/(float)th;
+ }
+
+ float w_ratio = (float)width/(float)height;
+ if (w_ratio >= t_ratio) {
+ scale[0] = t_ratio/w_ratio;
+ scale[1] = 1.0;
+ } else {
+ scale[0] = 1.0;
+ scale[1] = w_ratio/t_ratio;
+ }
+ }
+
+ DecalShader *s = g_framebuffer_shader;
+ s->flip = flip;
+ glViewport(0, 0, width, height);
+ glUseProgram(s->prog);
+ glBindVertexArray(s->vao);
+ glUniform1i(s->flipy_loc, s->flip);
+ glUniform4f(s->scale_offset_loc, scale[0], scale[1], 0, 0);
+ glUniform4f(s->tex_scale_offset_loc, 1.0, 1.0, 0, 0);
+ glUniform1i(s->tex_loc, 0);
+
+ const uint8_t *palette = nv2a_get_dac_palette();
+ for (int i = 0; i < 256; i++) {
+ uint32_t e = (palette[i * 3 + 2] << 16) | (palette[i * 3 + 1] << 8) |
+ palette[i * 3];
+ glUniform1ui(s->palette_loc[i], e);
+ }
+
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+ glDrawElements(GL_TRIANGLE_FAN, 4, GL_UNSIGNED_INT, NULL);
+}
+
+void SaveScreenshot(GLuint tex, bool flip)
+{
+ int width, height;
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, tex);
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_WIDTH, &width);
+ glGetTexLevelParameteriv(GL_TEXTURE_2D, 0, GL_TEXTURE_HEIGHT, &height);
+ glBindTexture(GL_TEXTURE_2D, 0);
+
+ if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_16_9) {
+ width = height * (16.0f / 9.0f);
+ } else if (g_config.display.ui.fit == CONFIG_DISPLAY_UI_FIT_SCALE_4_3) {
+ width = height * (4.0f / 3.0f);
+ }
+
+ std::vector pixels;
+ pixels.resize(width * height * 4);
+
+ Fbo fbo(width, height);
+ fbo.Target();
+ bool blend = glIsEnabled(GL_BLEND);
+ if (blend) glDisable(GL_BLEND);
+ RenderFramebuffer(tex, width, height, !flip);
+ if (blend) glEnable(GL_BLEND);
+ glPixelStorei(GL_PACK_ROW_LENGTH, width);
+ glPixelStorei(GL_PACK_IMAGE_HEIGHT, height);
+ glPixelStorei(GL_PACK_ALIGNMENT, 1);
+ glReadnPixels(0, 0, width, height, GL_RGB, GL_UNSIGNED_BYTE, pixels.size(),
+ pixels.data());
+ fbo.Restore();
+
+ char fname[128];
+ Error *err = NULL;
+ std::vector png;
+ if (fpng::fpng_encode_image_to_memory(pixels.data(), width, height, 3, png)) {
+ time_t t = time(NULL);
+ struct tm *tmp = localtime(&t);
+ if (tmp) {
+ strftime(fname, sizeof(fname), "xemu-%Y-%m-%d-%H-%M-%S.png",
+ tmp);
+ } else {
+ strcpy(fname, "xemu.png");
+ }
+
+ const char *output_dir = g_config.general.screenshot_dir;
+ if (!strlen(output_dir)) {
+ output_dir = ".";
+ }
+ // FIXME: Check for existing path
+ char *path = g_strdup_printf("%s/%s", output_dir, fname);
+ FILE *fd = qemu_fopen(path, "wb");
+ if (fd) {
+ int s = fwrite(png.data(), png.size(), 1, fd);
+ if (s != 1) {
+ error_setg(&err, "Failed to write %s", path);
+ }
+ fclose(fd);
+ } else {
+ error_setg(&err, "Failed to open %s for writing", path);
+ }
+ g_free(path);
+ } else {
+ error_setg(&err, "Failed to encode PNG image");
+ }
+
+ if (err) {
+ xemu_queue_error_message(error_get_pretty(err));
+ error_report_err(err);
+ } else {
+ char *msg = g_strdup_printf("Screenshot Saved: %s", fname);
+ xemu_queue_notification(msg);
+ free(msg);
+ }
+}
diff --git a/ui/xui/gl-helpers.hh b/ui/xui/gl-helpers.hh
new file mode 100644
index 0000000000..d51985cf71
--- /dev/null
+++ b/ui/xui/gl-helpers.hh
@@ -0,0 +1,50 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include "common.hh"
+#include "../xemu-input.h"
+
+class Fbo
+{
+public:
+ static GLint vp[4];
+ static GLint original_fbo;
+ static bool blend;
+
+ int w, h;
+ GLuint fbo, tex;
+
+ Fbo(int width, int height);
+ ~Fbo();
+ inline GLuint Texture() { return tex; }
+ void Target();
+ void Restore();
+};
+
+extern Fbo *controller_fbo, *logo_fbo;
+
+void InitCustomRendering(void);
+void RenderLogo(uint32_t time, uint32_t primary_color, uint32_t secondary_color,
+ uint32_t fill_color);
+void RenderController(float frame_x, float frame_y, uint32_t primary_color,
+ uint32_t secondary_color, ControllerState *state);
+void RenderControllerPort(float frame_x, float frame_y, int i,
+ uint32_t port_color);
+void RenderFramebuffer(GLint tex, int width, int height, bool flip);
+void SaveScreenshot(GLuint tex, bool flip);
diff --git a/ui/xui/input-manager.cc b/ui/xui/input-manager.cc
new file mode 100644
index 0000000000..aaf64e4237
--- /dev/null
+++ b/ui/xui/input-manager.cc
@@ -0,0 +1,116 @@
+#include "common.hh"
+#include "input-manager.hh"
+#include "../xemu-input.h"
+
+InputManager g_input_mgr;
+
+InputManager::InputManager()
+{
+ m_last_mouse_pos = ImVec2(0, 0);
+ m_navigating_with_controller = false;
+}
+
+void InputManager::Update()
+{
+ ImGuiIO& io = ImGui::GetIO();
+
+ // Combine all controller states to allow any controller to navigate
+ m_buttons = 0;
+ int16_t axis[CONTROLLER_AXIS__COUNT] = {0};
+
+ ControllerState *iter;
+ QTAILQ_FOREACH(iter, &available_controllers, entry) {
+ if (iter->type != INPUT_DEVICE_SDL_GAMECONTROLLER) continue;
+ m_buttons |= iter->buttons;
+ // We simply take any axis that is >10 % activation
+ for (int i = 0; i < CONTROLLER_AXIS__COUNT; i++) {
+ if ((iter->axis[i] > 3276) || (iter->axis[i] < -3276)) {
+ axis[i] = iter->axis[i];
+ }
+ }
+ }
+
+ // If the mouse is moved, wake the ui
+ ImVec2 current_mouse_pos = ImGui::GetMousePos();
+ m_mouse_moved = false;
+ if ((current_mouse_pos.x != m_last_mouse_pos.x) ||
+ (current_mouse_pos.y != m_last_mouse_pos.y) ||
+ ImGui::IsMouseDown(0) || ImGui::IsMouseDown(1) || ImGui::IsMouseDown(2)) {
+ m_mouse_moved = true;
+ m_last_mouse_pos = current_mouse_pos;
+ m_navigating_with_controller = false;
+ }
+
+ // If mouse capturing is enabled (we are in a dialog), ensure the UI is alive
+ bool controller_focus_capture = false;
+ if (io.NavActive) {
+ controller_focus_capture = true;
+ m_navigating_with_controller |= !!m_buttons;
+ }
+
+
+ // Prevent controller events from going to the guest if they are being used
+ // to navigate the HUD
+ xemu_input_set_test_mode(controller_focus_capture); // FIXME: Rename 'test mode'
+
+ // Update gamepad inputs
+ #define IM_SATURATE(V) (V < 0.0f ? 0.0f : V > 1.0f ? 1.0f : V)
+ #define MAP_BUTTON(KEY_NO, BUTTON_NO) { io.AddKeyEvent(KEY_NO, !!(m_buttons & BUTTON_NO)); }
+ #define MAP_ANALOG(KEY_NO, AXIS_NO, V0, V1) { float vn = (float)(axis[AXIS_NO] - V0) / (float)(V1 - V0); vn = IM_SATURATE(vn); io.AddKeyAnalogEvent(KEY_NO, vn > 0.1f, vn); }
+ const int thumb_dead_zone = 8000; // SDL_gamecontroller.h suggests using this value.
+ MAP_BUTTON(ImGuiKey_GamepadStart, CONTROLLER_BUTTON_START);
+ MAP_BUTTON(ImGuiKey_GamepadBack, CONTROLLER_BUTTON_BACK);
+ MAP_BUTTON(ImGuiKey_GamepadFaceDown, CONTROLLER_BUTTON_A); // Xbox A, PS Cross
+ MAP_BUTTON(ImGuiKey_GamepadFaceRight, CONTROLLER_BUTTON_B); // Xbox B, PS Circle
+ MAP_BUTTON(ImGuiKey_GamepadFaceLeft, CONTROLLER_BUTTON_X); // Xbox X, PS Square
+ MAP_BUTTON(ImGuiKey_GamepadFaceUp, CONTROLLER_BUTTON_Y); // Xbox Y, PS Triangle
+ MAP_BUTTON(ImGuiKey_GamepadDpadLeft, CONTROLLER_BUTTON_DPAD_LEFT);
+ MAP_BUTTON(ImGuiKey_GamepadDpadRight, CONTROLLER_BUTTON_DPAD_RIGHT);
+ MAP_BUTTON(ImGuiKey_GamepadDpadUp, CONTROLLER_BUTTON_DPAD_UP);
+ MAP_BUTTON(ImGuiKey_GamepadDpadDown, CONTROLLER_BUTTON_DPAD_DOWN);
+ MAP_BUTTON(ImGuiKey_GamepadL1, CONTROLLER_BUTTON_WHITE);
+ MAP_BUTTON(ImGuiKey_GamepadR1, CONTROLLER_BUTTON_BLACK);
+ //MAP_ANALOG(ImGuiKey_GamepadL2, SDL_CONTROLLER_AXIS_TRIGGERLEFT, 0.0f, 32767);
+ //MAP_ANALOG(ImGuiKey_GamepadR2, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, 0.0f, 32767);
+ //MAP_BUTTON(ImGuiKey_GamepadL3, SDL_CONTROLLER_BUTTON_LEFTSTICK);
+ //MAP_BUTTON(ImGuiKey_GamepadR3, SDL_CONTROLLER_BUTTON_RIGHTSTICK);
+ MAP_ANALOG(ImGuiKey_GamepadLStickLeft, CONTROLLER_AXIS_LSTICK_X, -thumb_dead_zone, -32768);
+ MAP_ANALOG(ImGuiKey_GamepadLStickRight, CONTROLLER_AXIS_LSTICK_X, +thumb_dead_zone, +32767);
+ MAP_ANALOG(ImGuiKey_GamepadLStickUp, CONTROLLER_AXIS_LSTICK_Y, +thumb_dead_zone, +32768);
+ MAP_ANALOG(ImGuiKey_GamepadLStickDown, CONTROLLER_AXIS_LSTICK_Y, -thumb_dead_zone, -32767);
+ MAP_ANALOG(ImGuiKey_GamepadRStickLeft, CONTROLLER_AXIS_RSTICK_X, -thumb_dead_zone, -32768);
+ MAP_ANALOG(ImGuiKey_GamepadRStickRight, CONTROLLER_AXIS_RSTICK_X, +thumb_dead_zone, +32767);
+ MAP_ANALOG(ImGuiKey_GamepadRStickUp, CONTROLLER_AXIS_RSTICK_Y, +thumb_dead_zone, +32768);
+ MAP_ANALOG(ImGuiKey_GamepadRStickDown, CONTROLLER_AXIS_RSTICK_Y, -thumb_dead_zone, -32767);
+ #undef MAP_BUTTON
+ #undef MAP_ANALOG
+ #undef IM_SATURATE
+
+ io.BackendUsingLegacyNavInputArray = true;
+
+ // Map to nav inputs
+ #define NAV_MAP_KEY(_KEY, _NAV_INPUT, _ACTIVATE_NAV) \
+ do { \
+ io.NavInputs[_NAV_INPUT] = io.KeysData[_KEY - ImGuiKey_KeysData_OFFSET].AnalogValue; \
+ if (_ACTIVATE_NAV && io.NavInputs[_NAV_INPUT] > 0.0f) { \
+ ImGui::GetCurrentContext()->NavInputSource = ImGuiInputSource_Gamepad; \
+ } \
+ } while (0)
+ NAV_MAP_KEY(ImGuiKey_GamepadFaceDown, ImGuiNavInput_Activate, true);
+ NAV_MAP_KEY(ImGuiKey_GamepadFaceRight, ImGuiNavInput_Cancel, true);
+ //NAV_MAP_KEY(ImGuiKey_Menu, ImGuiNavInput_Menu, true);
+ NAV_MAP_KEY(ImGuiKey_GamepadFaceUp, ImGuiNavInput_Input, true);
+ NAV_MAP_KEY(ImGuiKey_GamepadDpadLeft, ImGuiNavInput_DpadLeft, true);
+ NAV_MAP_KEY(ImGuiKey_GamepadDpadRight, ImGuiNavInput_DpadRight, true);
+ NAV_MAP_KEY(ImGuiKey_GamepadDpadUp, ImGuiNavInput_DpadUp, true);
+ NAV_MAP_KEY(ImGuiKey_GamepadDpadDown, ImGuiNavInput_DpadDown, true);
+ NAV_MAP_KEY(ImGuiKey_GamepadL1, ImGuiNavInput_FocusPrev, false);
+ NAV_MAP_KEY(ImGuiKey_GamepadR1, ImGuiNavInput_FocusNext, false);
+ NAV_MAP_KEY(ImGuiKey_GamepadL1, ImGuiNavInput_TweakSlow, false);
+ NAV_MAP_KEY(ImGuiKey_GamepadR1, ImGuiNavInput_TweakFast, false);
+ NAV_MAP_KEY(ImGuiKey_GamepadLStickLeft, ImGuiNavInput_LStickLeft, false);
+ NAV_MAP_KEY(ImGuiKey_GamepadLStickRight, ImGuiNavInput_LStickRight, false);
+ NAV_MAP_KEY(ImGuiKey_GamepadLStickUp, ImGuiNavInput_LStickUp, false);
+ NAV_MAP_KEY(ImGuiKey_GamepadLStickDown, ImGuiNavInput_LStickDown, false);
+ #undef NAV_MAP_KEY
+}
diff --git a/ui/xui/input-manager.hh b/ui/xui/input-manager.hh
new file mode 100644
index 0000000000..4d604dae76
--- /dev/null
+++ b/ui/xui/input-manager.hh
@@ -0,0 +1,20 @@
+#pragma once
+#include "common.hh"
+
+class InputManager
+{
+protected:
+ ImVec2 m_last_mouse_pos;
+ bool m_navigating_with_controller;
+ uint32_t m_buttons;
+ bool m_mouse_moved;
+
+public:
+ InputManager();
+ void Update();
+ inline bool IsNavigatingWithController() { return m_navigating_with_controller; }
+ inline bool MouseMoved() { return m_mouse_moved; }
+ inline uint32_t CombinedButtons() { return m_buttons; }
+};
+
+extern InputManager g_input_mgr;
diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc
new file mode 100644
index 0000000000..78f435956f
--- /dev/null
+++ b/ui/xui/main-menu.cc
@@ -0,0 +1,1142 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "common.hh"
+#include "scene-manager.hh"
+#include "widgets.hh"
+#include "main-menu.hh"
+#include "font-manager.hh"
+#include "input-manager.hh"
+#include "viewport-manager.hh"
+#include "xemu-hud.h"
+#include "misc.hh"
+#include "gl-helpers.hh"
+#include "reporting.hh"
+
+#include "../xemu-input.h"
+#include "../xemu-notifications.h"
+#include "../xemu-settings.h"
+#include "../xemu-monitor.h"
+#include "../xemu-version.h"
+#include "../xemu-net.h"
+#include "../xemu-os-utils.h"
+#include "../xemu-xbe.h"
+
+MainMenuTabView::~MainMenuTabView() {}
+void MainMenuTabView::Draw() {}
+
+void MainMenuGeneralView::Draw()
+{
+ SectionTitle("Updates");
+ Toggle("Check for updates", &g_config.general.updates.check,
+ "Check for updates whenever xemu is opened");
+
+ SectionTitle("Performance");
+ Toggle("Hard FPU emulation", &g_config.perf.hard_fpu,
+ "Use hardware-accelerated floating point emulation (requires restart)");
+ // toggle("Cache shaders to disk", &g_config.perf.cache_shaders,
+ // "Reduce stutter in games by caching previously generated shaders");
+
+ SectionTitle("Miscellaneous");
+ Toggle("Skip startup animation", &g_config.general.skip_boot_anim,
+ "Skip the full Xbox boot animation sequence");
+ FilePicker("Screenshot output directory", &g_config.general.screenshot_dir,
+ NULL, true);
+ // toggle("Throttle DVD/HDD speeds", &g_config.general.throttle_io,
+ // "Limit DVD/HDD throughput to approximate Xbox load times");
+}
+
+void MainMenuInputView::Draw()
+{
+ SectionTitle("Controllers");
+ ImGui::PushFont(g_font_mgr.m_menu_font_small);
+
+ static int active = 0;
+
+ // Output dimensions of texture
+ float t_w = 512, t_h = 512;
+ // Dimensions of (port+label)s
+ float b_x = 0, b_x_stride = 100, b_y = 400;
+ float b_w = 68, b_h = 81;
+ // Dimensions of controller (rendered at origin)
+ float controller_width = 477.0f;
+ float controller_height = 395.0f;
+
+ // Setup rendering to fbo for controller and port images
+ controller_fbo->Target();
+ ImTextureID id = (ImTextureID)(intptr_t)controller_fbo->Texture();
+
+ //
+ // Render buttons with icons of the Xbox style port sockets with
+ // circular numbers above them. These buttons can be activated to
+ // configure the associated port, like a tabbed interface.
+ //
+ ImVec4 color_active(0.50, 0.86, 0.54, 0.12);
+ ImVec4 color_inactive(0, 0, 0, 0);
+
+ // Begin a 4-column layout to render the ports
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing,
+ g_viewport_mgr.Scale(ImVec2(0, 12)));
+ ImGui::Columns(4, "mixed", false);
+
+ const int port_padding = 8;
+ for (int i = 0; i < 4; i++) {
+ bool is_selected = (i == active);
+ bool port_is_bound = (xemu_input_get_bound(i) != NULL);
+
+ // Set an X offset to center the image button within the column
+ ImGui::SetCursorPosX(
+ ImGui::GetCursorPosX() +
+ (int)((ImGui::GetColumnWidth() - b_w * g_viewport_mgr.m_scale -
+ 2 * port_padding * g_viewport_mgr.m_scale) /
+ 2));
+
+ // We are using the same texture for all buttons, but ImageButton
+ // uses the texture as a unique ID. Push a new ID now to resolve
+ // the conflict.
+ ImGui::PushID(i);
+ float x = b_x+i*b_x_stride;
+ ImGui::PushStyleColor(ImGuiCol_Button, is_selected ?
+ color_active :
+ color_inactive);
+ bool activated = ImGui::ImageButton(id,
+ ImVec2(b_w*g_viewport_mgr.m_scale,b_h*g_viewport_mgr.m_scale),
+ ImVec2(x/t_w, (b_y+b_h)/t_h),
+ ImVec2((x+b_w)/t_w, b_y/t_h),
+ port_padding);
+ ImGui::PopStyleColor();
+
+ if (activated) {
+ active = i;
+ }
+
+ uint32_t port_color = 0xafafafff;
+ bool is_hovered = ImGui::IsItemHovered();
+ if (is_hovered) {
+ port_color = 0xffffffff;
+ } else if (is_selected || port_is_bound) {
+ port_color = 0x81dc8a00;
+ }
+
+ RenderControllerPort(x, b_y, i, port_color);
+
+ ImGui::PopID();
+ ImGui::NextColumn();
+ }
+ ImGui::PopStyleVar(); // ItemSpacing
+ ImGui::Columns(1);
+
+ //
+ // Render input device combo
+ //
+
+ // List available input devices
+ const char *not_connected = "Not Connected";
+ ControllerState *bound_state = xemu_input_get_bound(active);
+
+ // Get current controller name
+ const char *name;
+ if (bound_state == NULL) {
+ name = not_connected;
+ } else {
+ name = bound_state->name;
+ }
+
+ ImGui::SetNextItemWidth(-FLT_MIN);
+ if (ImGui::BeginCombo("###InputDevices", name, ImGuiComboFlags_NoArrowButton))
+ {
+ // Handle "Not connected"
+ bool is_selected = bound_state == NULL;
+ if (ImGui::Selectable(not_connected, is_selected)) {
+ xemu_input_bind(active, NULL, 1);
+ bound_state = NULL;
+ }
+ if (is_selected) {
+ ImGui::SetItemDefaultFocus();
+ }
+
+ // Handle all available input devices
+ ControllerState *iter;
+ QTAILQ_FOREACH(iter, &available_controllers, entry) {
+ is_selected = bound_state == iter;
+ ImGui::PushID(iter);
+ const char *selectable_label = iter->name;
+ char buf[128];
+ if (iter->bound >= 0) {
+ snprintf(buf, sizeof(buf), "%s (Port %d)", iter->name, iter->bound+1);
+ selectable_label = buf;
+ }
+ if (ImGui::Selectable(selectable_label, is_selected)) {
+ xemu_input_bind(active, iter, 1);
+ bound_state = iter;
+ }
+ if (is_selected) {
+ ImGui::SetItemDefaultFocus();
+ }
+ ImGui::PopID();
+ }
+
+ ImGui::EndCombo();
+ }
+ DrawComboChevron();
+
+ ImGui::Columns(1);
+
+ //
+ // Add a separator between input selection and controller graphic
+ //
+ ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y / 2));
+
+ //
+ // Render controller image
+ //
+ bool device_selected = false;
+
+ if (bound_state) {
+ device_selected = true;
+ RenderController(0, 0, 0x81dc8a00, 0x0f0f0f00, bound_state);
+ } else {
+ static ControllerState state = { 0 };
+ RenderController(0, 0, 0x1f1f1f00, 0x0f0f0f00, &state);
+ }
+
+ ImVec2 cur = ImGui::GetCursorPos();
+
+ ImVec2 controller_display_size;
+ if (ImGui::GetContentRegionMax().x < controller_width*g_viewport_mgr.m_scale) {
+ controller_display_size.x = ImGui::GetContentRegionMax().x;
+ controller_display_size.y =
+ controller_display_size.x * controller_height / controller_width;
+ } else {
+ controller_display_size =
+ ImVec2(controller_width * g_viewport_mgr.m_scale,
+ controller_height * g_viewport_mgr.m_scale);
+ }
+
+ ImGui::SetCursorPosX(
+ ImGui::GetCursorPosX() +
+ (int)((ImGui::GetColumnWidth() - controller_display_size.x) / 2.0));
+
+ ImGui::Image(id,
+ controller_display_size,
+ ImVec2(0, controller_height/t_h),
+ ImVec2(controller_width/t_w, 0));
+ ImVec2 pos = ImGui::GetCursorPos();
+ if (!device_selected) {
+ const char *msg = "Please select an available input device";
+ ImVec2 dim = ImGui::CalcTextSize(msg);
+ ImGui::SetCursorPosX(cur.x + (controller_display_size.x-dim.x)/2);
+ ImGui::SetCursorPosY(cur.y + (controller_display_size.y-dim.y)/2);
+ ImGui::Text("%s", msg);
+ }
+
+ controller_fbo->Restore();
+
+ ImGui::PopFont();
+ ImGui::SetCursorPos(pos);
+
+ SectionTitle("Options");
+ Toggle("Auto-bind controllers", &g_config.input.auto_bind,
+ "Bind newly connected controllers to any open port");
+ Toggle("Background controller input capture",
+ &g_config.input.background_input_capture,
+ "Capture even if window is unfocused (requires restart)");
+}
+
+void MainMenuDisplayView::Draw()
+{
+ SectionTitle("Quality");
+ int rendering_scale = nv2a_get_surface_scale_factor() - 1;
+ if (ChevronCombo("Internal resolution scale", &rendering_scale,
+ "1x\0"
+ "2x\0"
+ "3x\0"
+ "4x\0"
+ "5x\0"
+ "6x\0"
+ "7x\0"
+ "8x\0"
+ "9x\0"
+ "10x\0",
+ "Increase surface scaling factor for higher quality")) {
+ nv2a_set_surface_scale_factor(rendering_scale+1);
+ }
+
+ SectionTitle("Window");
+ bool fs = xemu_is_fullscreen();
+ if (Toggle("Fullscreen", &fs, "Enable fullscreen now")) {
+ xemu_toggle_fullscreen();
+ }
+ Toggle("Fullscreen on startup",
+ &g_config.display.window.fullscreen_on_startup,
+ "Start xemu in fullscreen when opened");
+ if (ChevronCombo("Window size", &g_config.display.window.startup_size,
+ "Last Used\0"
+ "640x480\0"
+ "1280x720\0"
+ "1280x800\0"
+ "1280x960\0"
+ "1920x1080\0"
+ "2560x1440\0"
+ "2560x1600\0"
+ "2560x1920\0"
+ "3840x2160\0",
+ "Select preferred startup window size")) {
+ }
+ Toggle("Vertical refresh sync", &g_config.display.window.vsync,
+ "Sync to screen vertical refresh to reduce tearing artifacts");
+
+ SectionTitle("Interface");
+ Toggle("Show main menu bar", &g_config.display.ui.show_menubar,
+ "Show main menu bar when mouse is activated");
+
+ int ui_scale_idx;
+ if (g_config.display.ui.auto_scale) {
+ ui_scale_idx = 0;
+ } else {
+ ui_scale_idx = g_config.display.ui.scale;
+ if (ui_scale_idx < 0) ui_scale_idx = 0;
+ else if (ui_scale_idx > 2) ui_scale_idx = 2;
+ }
+ if (ChevronCombo("UI scale", &ui_scale_idx,
+ "Auto\0"
+ "1x\0"
+ "2x\0",
+ "Interface element scale")) {
+ if (ui_scale_idx == 0) {
+ g_config.display.ui.auto_scale = true;
+ } else {
+ g_config.display.ui.auto_scale = false;
+ g_config.display.ui.scale = ui_scale_idx;
+ }
+ }
+ Toggle("Animations", &g_config.display.ui.use_animations,
+ "Enable xemu user interface animations");
+ ChevronCombo("Display mode", &g_config.display.ui.fit,
+ "Center\0"
+ "Scale\0"
+ "Scale (Widescreen 16:9)\0"
+ "Scale (4:3)\0"
+ "Stretch\0",
+ "Select how the framebuffer should fit or scale into the window");
+}
+
+void MainMenuAudioView::Draw()
+{
+ SectionTitle("Volume");
+ char buf[32];
+ snprintf(buf, sizeof(buf), "Limit output volume (%d%%)",
+ (int)(g_config.audio.volume_limit * 100));
+ Slider("Output volume limit", &g_config.audio.volume_limit, buf);
+
+ SectionTitle("Quality");
+ Toggle("Real-time DSP processing", &g_config.audio.use_dsp,
+ "Enable improved audio accuracy (experimental)");
+
+}
+
+NetworkInterface::NetworkInterface(pcap_if_t *pcap_desc, char *_friendlyname)
+{
+ m_pcap_name = pcap_desc->name;
+ m_description = pcap_desc->description ?: pcap_desc->name;
+ if (_friendlyname) {
+ char *tmp =
+ g_strdup_printf("%s (%s)", _friendlyname, m_description.c_str());
+ m_friendly_name = tmp;
+ g_free((gpointer)tmp);
+ } else {
+ m_friendly_name = m_description;
+ }
+}
+
+NetworkInterfaceManager::NetworkInterfaceManager()
+{
+ m_current_iface = NULL;
+ m_failed_to_load_lib = false;
+}
+
+void NetworkInterfaceManager::Refresh(void)
+{
+ pcap_if_t *alldevs, *iter;
+ char err[PCAP_ERRBUF_SIZE];
+
+ if (xemu_net_is_enabled()) {
+ return;
+ }
+
+#if defined(_WIN32)
+ if (pcap_load_library()) {
+ m_failed_to_load_lib = true;
+ return;
+ }
+#endif
+
+ m_ifaces.clear();
+ m_current_iface = NULL;
+
+ if (pcap_findalldevs(&alldevs, err)) {
+ return;
+ }
+
+ for (iter=alldevs; iter != NULL; iter=iter->next) {
+#if defined(_WIN32)
+ char *friendly_name = get_windows_interface_friendly_name(iter->name);
+ m_ifaces.emplace_back(new NetworkInterface(iter, friendly_name));
+ if (friendly_name) {
+ g_free((gpointer)friendly_name);
+ }
+#else
+ m_ifaces.emplace_back(new NetworkInterface(iter));
+#endif
+ if (!strcmp(g_config.net.pcap.netif, iter->name)) {
+ m_current_iface = m_ifaces.back().get();
+ }
+ }
+
+ pcap_freealldevs(alldevs);
+}
+
+void NetworkInterfaceManager::Select(NetworkInterface &iface)
+{
+ m_current_iface = &iface;
+ xemu_settings_set_string(&g_config.net.pcap.netif,
+ iface.m_pcap_name.c_str());
+}
+
+bool NetworkInterfaceManager::IsCurrent(NetworkInterface &iface)
+{
+ return &iface == m_current_iface;
+}
+
+MainMenuNetworkView::MainMenuNetworkView()
+{
+ should_refresh = true;
+}
+
+void MainMenuNetworkView::Draw()
+{
+ SectionTitle("Adapter");
+ bool enabled = xemu_net_is_enabled();
+ g_config.net.enable = enabled;
+ if (Toggle("Enable", &g_config.net.enable,
+ enabled ? "Virtual network connected (disable to change network "
+ "settings)" :
+ "Connect virtual network cable to machine")) {
+ if (enabled) {
+ xemu_net_disable();
+ } else {
+ xemu_net_enable();
+ }
+ }
+
+ bool appearing = ImGui::IsWindowAppearing();
+ if (enabled) ImGui::BeginDisabled();
+ if (ChevronCombo(
+ "Attached to", &g_config.net.backend,
+ "NAT\0"
+ "UDP Tunnel\0"
+ "Bridged Adapter\0",
+ "Controls what the virtual network controller interfaces with")) {
+ appearing = true;
+ }
+ SectionTitle("Options");
+ switch (g_config.net.backend) {
+ case CONFIG_NET_BACKEND_PCAP:
+ DrawPcapOptions(appearing);
+ break;
+ case CONFIG_NET_BACKEND_NAT:
+ DrawNatOptions(appearing);
+ break;
+ case CONFIG_NET_BACKEND_UDP:
+ DrawUdpOptions(appearing);
+ break;
+ default: break;
+ }
+ if (enabled) ImGui::EndDisabled();
+}
+
+void MainMenuNetworkView::DrawPcapOptions(bool appearing)
+{
+ if (iface_mgr.get() == nullptr) {
+ iface_mgr.reset(new NetworkInterfaceManager());
+ iface_mgr->Refresh();
+ }
+
+ if (iface_mgr->m_failed_to_load_lib) {
+#if defined(_WIN32)
+ const char *msg = "npcap library could not be loaded.\n"
+ "To use this backend, please install npcap.";
+ ImGui::Text("%s", msg);
+ ImGui::Dummy(ImVec2(0,10*g_viewport_mgr.m_scale));
+ ImGui::SetCursorPosX((ImGui::GetWindowWidth()-120*g_viewport_mgr.m_scale)/2);
+ if (ImGui::Button("Install npcap", ImVec2(120*g_viewport_mgr.m_scale, 0))) {
+ xemu_open_web_browser("https://nmap.org/npcap/");
+ }
+#endif
+ } else {
+ const char *selected_display_name =
+ (iface_mgr->m_current_iface ?
+ iface_mgr->m_current_iface->m_friendly_name.c_str() :
+ g_config.net.pcap.netif);
+ float combo_width = ImGui::GetColumnWidth();
+ float combo_size_ratio = 0.5;
+ combo_width *= combo_size_ratio;
+ PrepareComboTitleDescription("Network interface",
+ "Host network interface to bridge with",
+ combo_size_ratio);
+ ImGui::SetNextItemWidth(combo_width);
+ ImGui::PushFont(g_font_mgr.m_menu_font_small);
+ if (ImGui::BeginCombo("###network_iface", selected_display_name,
+ ImGuiComboFlags_NoArrowButton)) {
+ if (should_refresh) {
+ iface_mgr->Refresh();
+ should_refresh = false;
+ }
+
+ int i = 0;
+ for (auto &iface : iface_mgr->m_ifaces) {
+ bool is_selected = iface_mgr->IsCurrent((*iface));
+ ImGui::PushID(i++);
+ if (ImGui::Selectable(iface->m_friendly_name.c_str(),
+ is_selected)) {
+ iface_mgr->Select((*iface));
+ }
+ if (is_selected) ImGui::SetItemDefaultFocus();
+ ImGui::PopID();
+ }
+ ImGui::EndCombo();
+ } else {
+ should_refresh = true;
+ }
+ ImGui::PopFont();
+ DrawComboChevron();
+ }
+}
+
+void MainMenuNetworkView::DrawNatOptions(bool appearing)
+{
+ static ImGuiTableFlags flags = ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg;
+ WidgetTitleDescriptionItem(
+ "Port Forwarding",
+ "Configure xemu to forward connections to guest on these ports");
+ float p = ImGui::GetFrameHeight() * 0.3;
+ ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(p, p));
+ if (ImGui::BeginTable("port_forward_tbl", 4, flags))
+ {
+ ImGui::TableSetupColumn("Host Port");
+ ImGui::TableSetupColumn("Guest Port");
+ ImGui::TableSetupColumn("Protocol");
+ ImGui::TableSetupColumn("Action");
+ ImGui::TableHeadersRow();
+
+ for (unsigned int row = 0; row < g_config.net.nat.forward_ports_count; row++)
+ {
+ ImGui::TableNextRow();
+
+ ImGui::TableSetColumnIndex(0);
+ ImGui::Text("%d", g_config.net.nat.forward_ports[row].host);
+
+ ImGui::TableSetColumnIndex(1);
+ ImGui::Text("%d", g_config.net.nat.forward_ports[row].guest);
+
+ ImGui::TableSetColumnIndex(2);
+ switch (g_config.net.nat.forward_ports[row].protocol) {
+ case CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_TCP:
+ ImGui::TextUnformatted("TCP"); break;
+ case CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_UDP:
+ ImGui::TextUnformatted("UDP"); break;
+ default: assert(0);
+ }
+
+ ImGui::TableSetColumnIndex(3);
+ ImGui::PushID(row);
+ if (ImGui::Button("Remove")) {
+ remove_net_nat_forward_ports(row);
+ }
+ ImGui::PopID();
+ }
+
+ ImGui::TableNextRow();
+
+ ImGui::TableSetColumnIndex(0);
+ static char buf[8] = {"1234"};
+ ImGui::SetNextItemWidth(ImGui::GetColumnWidth());
+ ImGui::InputText("###hostport", buf, sizeof(buf));
+
+ ImGui::TableSetColumnIndex(1);
+ static char buf2[8] = {"1234"};
+ ImGui::SetNextItemWidth(ImGui::GetColumnWidth());
+ ImGui::InputText("###guestport", buf2, sizeof(buf2));
+
+ ImGui::TableSetColumnIndex(2);
+ static CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL protocol =
+ CONFIG_NET_NAT_FORWARD_PORTS_PROTOCOL_TCP;
+ assert(sizeof(protocol) >= sizeof(int));
+ ImGui::SetNextItemWidth(ImGui::GetColumnWidth());
+ ImGui::Combo("###protocol", &protocol, "TCP\0UDP\0");
+
+ ImGui::TableSetColumnIndex(3);
+ if (ImGui::Button("Add")) {
+ int host, guest;
+ if (sscanf(buf, "%d", &host) == 1 &&
+ sscanf(buf2, "%d", &guest) == 1) {
+ add_net_nat_forward_ports(host, guest, protocol);
+ }
+ }
+
+ ImGui::EndTable();
+ }
+ ImGui::PopStyleVar();
+}
+
+void MainMenuNetworkView::DrawUdpOptions(bool appearing)
+{
+ if (appearing) {
+ strncpy(remote_addr, g_config.net.udp.remote_addr, sizeof(remote_addr)-1);
+ strncpy(local_addr, g_config.net.udp.bind_addr, sizeof(local_addr)-1);
+ }
+
+ float size_ratio = 0.5;
+ float width = ImGui::GetColumnWidth() * size_ratio;
+ ImGui::PushFont(g_font_mgr.m_menu_font_small);
+ PrepareComboTitleDescription(
+ "Remote Address",
+ "Destination addr:port to forward packets to (1.2.3.4:9968)",
+ size_ratio);
+ ImGui::SetNextItemWidth(width);
+ if (ImGui::InputText("###remote_host", remote_addr, sizeof(remote_addr))) {
+ xemu_settings_set_string(&g_config.net.udp.remote_addr, remote_addr);
+ }
+ PrepareComboTitleDescription(
+ "Bind Address", "Local addr:port to receive packets on (0.0.0.0:9968)",
+ size_ratio);
+ ImGui::SetNextItemWidth(width);
+ if (ImGui::InputText("###local_host", local_addr, sizeof(local_addr))) {
+ xemu_settings_set_string(&g_config.net.udp.bind_addr, local_addr);
+ }
+ ImGui::PopFont();
+}
+
+#if 0
+class MainMenuSnapshotsView : public virtual MainMenuTabView
+{
+protected:
+ GLuint screenshot;
+
+public:
+ void initScreenshot()
+ {
+ if (screenshot == 0) {
+ glGenTextures(1, &screenshot);
+ int w, h, n;
+ stbi_set_flip_vertically_on_load(0);
+ unsigned char *data = stbi_load("./data/screenshot.png", &w, &h, &n, 4);
+ assert(n == 4);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, screenshot);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ stbi_image_free(data);
+ }
+ }
+
+ void snapshotBigButton(const char *name, const char *title_name, GLuint screenshot)
+ {
+ ImGuiStyle &style = ImGui::GetStyle();
+ ImVec2 pos = ImGui::GetCursorPos();
+ ImDrawList *draw_list = ImGui::GetWindowDrawList();
+
+ ImGui::PushFont(g_font_mgr.m_menuFont);
+ const char *icon = ICON_FA_CIRCLE_XMARK;
+ ImVec2 ts_icon = ImGui::CalcTextSize(icon);
+ ts_icon.x += 2*style.FramePadding.x;
+ ImGui::PopFont();
+
+ ImGui::PushFont(g_font_mgr.m_menuFontSmall);
+ ImVec2 ts_sub = ImGui::CalcTextSize(name);
+ ImGui::PopFont();
+
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, g_viewport_mgr.scale(ImVec2(5, 5)));
+ ImGui::PushFont(g_font_mgr.m_menuFontMedium);
+
+ ImVec2 ts_title = ImGui::CalcTextSize(name);
+ ImVec2 thumbnail_size = g_viewport_mgr.scale(ImVec2(160, 120));
+ ImVec2 thumbnail_pos(style.FramePadding.x, style.FramePadding.y);
+ ImVec2 text_pos(thumbnail_pos.x + thumbnail_size.x + style.FramePadding.x * 2, thumbnail_pos.y);
+ ImVec2 subtext_pos(text_pos.x, text_pos.y + ts_title.y + style.FramePadding.x);
+
+ ImGui::Button("###button", ImVec2(ImGui::GetContentRegionAvail().x, fmax(thumbnail_size.y + style.FramePadding.y * 2,
+ ts_title.y + ts_sub.y + style.FramePadding.y * 3)));
+ ImGui::PopFont();
+ const ImVec2 sz = ImGui::GetItemRectSize();
+ const ImVec2 p0 = ImGui::GetItemRectMin();
+ const ImVec2 p1 = ImGui::GetItemRectMax();
+ ts_icon.y = sz.y;
+
+ // Snapshot thumbnail
+ ImGui::SetItemAllowOverlap();
+ ImGui::SameLine();
+ ImGui::SetCursorPosX(pos.x + thumbnail_pos.x);
+ ImGui::SetCursorPosY(pos.y + thumbnail_pos.y);
+ ImGui::Image((ImTextureID)screenshot, thumbnail_size, ImVec2(0,0), ImVec2(1,1));
+
+ draw_list->PushClipRect(p0, p1, true);
+
+ // Snapshot title
+ ImGui::PushFont(g_font_mgr.m_menuFontMedium);
+ draw_list->AddText(ImVec2(p0.x + text_pos.x, p0.y + text_pos.y), IM_COL32(255, 255, 255, 255), name);
+ ImGui::PopFont();
+
+ // Snapshot subtitle
+ ImGui::PushFont(g_font_mgr.m_menuFontSmall);
+ draw_list->AddText(ImVec2(p0.x + subtext_pos.x, p0.y + subtext_pos.y), IM_COL32(255, 255, 255, 200), title_name);
+ ImGui::PopFont();
+
+ draw_list->PopClipRect();
+
+ // Delete button
+ ImGui::SameLine();
+ ImGui::SetCursorPosY(pos.y);
+ ImGui::SetCursorPosX(pos.x + sz.x - ts_icon.x);
+ ImGui::PushFont(g_font_mgr.m_menuFont);
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
+ ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
+ ImGui::Button(icon, ts_icon);
+ ImGui::PopStyleColor();
+ ImGui::PopStyleVar(1);
+ ImGui::PopFont();
+ ImGui::PopStyleVar(2);
+ }
+
+ void Draw()
+ {
+ initScreenshot();
+ for (int i = 0; i < 15; i++) {
+ char buf[64];
+ snprintf(buf, sizeof(buf), "%s", "Apr 9 2022 19:44");
+ ImGui::PushID(i);
+ snapshotBigButton(buf, "Halo: Combat Evolved", screenshot);
+ ImGui::PopID();
+ }
+ }
+};
+#endif
+
+MainMenuSystemView::MainMenuSystemView() : m_dirty(false)
+{
+}
+
+void MainMenuSystemView::Draw()
+{
+ const char *rom_file_filters = ".bin Files\0*.bin\0.rom Files\0*.rom\0All Files\0*.*\0";
+ const char *qcow_file_filters = ".qcow2 Files\0*.qcow2\0All Files\0*.*\0";
+
+ if (m_dirty) {
+ ImGui::TextColored(ImVec4(1,0,0,1), "Application restart required to apply settings");
+ }
+
+ SectionTitle("System Configuration");
+
+ if (ChevronCombo(
+ "System Memory", &g_config.sys.mem_limit,
+ "64 MiB (Default)\0""128 MiB\0",
+ "Increase to 128 MiB for debug or homebrew applications")) {
+ m_dirty = true;
+ }
+
+ if (ChevronCombo(
+ "AV Pack", &g_config.sys.avpack,
+ "SCART\0HDTV (Default)\0VGA\0RFU\0S-Video\0Composite\0None\0",
+ "Select the attached AV pack")) {
+ m_dirty = true;
+ }
+
+ SectionTitle("Files");
+ if (FilePicker("Boot ROM", &g_config.sys.files.bootrom_path,
+ rom_file_filters)) {
+ m_dirty = true;
+ }
+ if (FilePicker("Flash ROM", &g_config.sys.files.flashrom_path,
+ rom_file_filters)) {
+ m_dirty = true;
+ }
+ if (FilePicker("Hard Disk", &g_config.sys.files.hdd_path,
+ qcow_file_filters)) {
+ m_dirty = true;
+ }
+ if (FilePicker("EEPROM", &g_config.sys.files.eeprom_path,
+ rom_file_filters)) {
+ m_dirty = true;
+ }
+}
+
+void MainMenuAboutView::Draw()
+{
+ static const char *build_info_text = NULL;
+ if (build_info_text == NULL) {
+ build_info_text = g_strdup_printf(
+ "Version: %s\nBranch: %s\nCommit: %s\nDate: %s",
+ xemu_version, xemu_branch, xemu_commit, xemu_date);
+ }
+
+ static const char *sys_info_text = NULL;
+ if (sys_info_text == NULL) {
+ const char *gl_shader_version = (const char*)glGetString(GL_SHADING_LANGUAGE_VERSION);
+ const char *gl_version = (const char*)glGetString(GL_VERSION);
+ const char *gl_renderer = (const char*)glGetString(GL_RENDERER);
+ const char *gl_vendor = (const char*)glGetString(GL_VENDOR);
+ sys_info_text = g_strdup_printf(
+ "CPU: %s\nOS Platform: %s\nOS Version: %s\nManufacturer: %s\n"
+ "GPU Model: %s\nDriver: %s\nShader: %s",
+ xemu_get_cpu_info(), xemu_get_os_platform(), xemu_get_os_info(), gl_vendor,
+ gl_renderer, gl_version, gl_shader_version);
+ }
+
+ static uint32_t time_start = 0;
+ if (ImGui::IsWindowAppearing()) {
+ time_start = SDL_GetTicks();
+ }
+ uint32_t now = SDL_GetTicks() - time_start;
+
+ ImGui::SetCursorPosY(ImGui::GetCursorPosY()-50*g_viewport_mgr.m_scale);
+ ImGui::SetCursorPosX((ImGui::GetWindowWidth()-256*g_viewport_mgr.m_scale)/2);
+
+ logo_fbo->Target();
+ ImTextureID id = (ImTextureID)(intptr_t)logo_fbo->Texture();
+ float t_w = 256.0;
+ float t_h = 256.0;
+ float x_off = 0;
+ ImGui::Image(id,
+ ImVec2((t_w-x_off)*g_viewport_mgr.m_scale, t_h*g_viewport_mgr.m_scale),
+ ImVec2(x_off/t_w, t_h/t_h),
+ ImVec2(t_w/t_w, 0));
+ if (ImGui::IsItemClicked()) {
+ time_start = SDL_GetTicks();
+ }
+ RenderLogo(now, 0x42e335ff, 0x42e335ff, 0x00000000);
+ logo_fbo->Restore();
+
+ ImGui::SetCursorPosY(ImGui::GetCursorPosY()-75*g_viewport_mgr.m_scale);
+
+ SectionTitle("Build Information");
+ ImGui::PushFont(g_font_mgr.m_fixed_width_font);
+ ImGui::InputTextMultiline("##build_info", (char *)build_info_text,
+ strlen(build_info_text),
+ ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 5),
+ ImGuiInputTextFlags_ReadOnly);
+ ImGui::PopFont();
+
+ SectionTitle("System Information");
+ ImGui::PushFont(g_font_mgr.m_fixed_width_font);
+ ImGui::InputTextMultiline("###systeminformation", (char *)sys_info_text,
+ strlen(sys_info_text),
+ ImVec2(-FLT_MIN, ImGui::GetTextLineHeight() * 8),
+ ImGuiInputTextFlags_ReadOnly);
+ ImGui::PopFont();
+
+ SectionTitle("Community");
+
+ ImGui::Text("Visit");
+ ImGui::SameLine();
+ if (ImGui::SmallButton("https://xemu.app")) {
+ xemu_open_web_browser("https://xemu.app");
+ }
+ ImGui::SameLine();
+ ImGui::Text("for more information");
+}
+
+MainMenuTabButton::MainMenuTabButton(std::string text, std::string icon, MainMenuTabView *view)
+: m_icon(icon), m_text(text), m_view(view)
+{
+}
+
+MainMenuTabView *MainMenuTabButton::view()
+{
+ return m_view;
+}
+
+bool MainMenuTabButton::Draw(bool selected)
+{
+ ImGuiStyle &style = ImGui::GetStyle();
+
+ ImU32 col = selected ?
+ ImGui::GetColorU32(style.Colors[ImGuiCol_ButtonHovered]) :
+ IM_COL32(0, 0, 0, 0);
+
+ ImGui::PushStyleColor(ImGuiCol_Button, col);
+ ImGui::PushStyleColor(ImGuiCol_ButtonHovered, selected ? col : IM_COL32(32, 32, 32, 255));
+ ImGui::PushStyleColor(ImGuiCol_ButtonActive, selected ? col : IM_COL32(32, 32, 32, 255));
+ int p = ImGui::GetTextLineHeight() * 0.5;
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(p, p));
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0.5));
+ ImGui::PushFont(g_font_mgr.m_menu_font);
+
+ ImVec2 button_size = ImVec2(-FLT_MIN, 0);
+ auto text = string_format("%s %s", m_icon.c_str(), m_text.c_str());
+ ImGui::PushID(this);
+ bool status = ImGui::Button(text.c_str(), button_size);
+ ImGui::PopID();
+ ImGui::PopFont();
+ ImGui::PopStyleVar(3);
+ ImGui::PopStyleColor(3);
+ return status;
+}
+
+MainMenuScene::MainMenuScene()
+: m_animation(0.12, 0.12),
+ m_general_button("General", ICON_FA_GEARS, &m_general_view),
+ m_input_button("Input", ICON_FA_GAMEPAD, &m_input_view),
+ m_display_button("Display", ICON_FA_TV, &m_display_view),
+ m_audio_button("Audio", ICON_FA_VOLUME_HIGH, &m_audio_view),
+ m_network_button("Network", ICON_FA_NETWORK_WIRED, &m_network_view),
+ // m_snapshots_button("Snapshots", ICON_FA_CLOCK_ROTATE_LEFT, &m_snapshots_view),
+ m_system_button("System", ICON_FA_MICROCHIP, &m_system_view),
+ m_about_button("About", ICON_FA_CIRCLE_INFO, &m_about_view)
+{
+ m_had_focus_last_frame = false;
+ m_focus_view = false;
+ m_tabs.push_back(&m_general_button);
+ m_tabs.push_back(&m_input_button);
+ m_tabs.push_back(&m_display_button);
+ m_tabs.push_back(&m_audio_button);
+ m_tabs.push_back(&m_network_button);
+ // m_tabs.push_back(&m_snapshots_button);
+ m_tabs.push_back(&m_system_button);
+ m_tabs.push_back(&m_about_button);
+
+ m_current_view_index = 0;
+ m_next_view_index = m_current_view_index;
+}
+
+void MainMenuScene::ShowGeneral()
+{
+ SetNextViewIndexWithFocus(0);
+}
+void MainMenuScene::ShowInput()
+{
+ SetNextViewIndexWithFocus(1);
+}
+void MainMenuScene::ShowDisplay()
+{
+ SetNextViewIndexWithFocus(2);
+}
+void MainMenuScene::ShowAudio()
+{
+ SetNextViewIndexWithFocus(3);
+}
+void MainMenuScene::ShowNetwork()
+{
+ SetNextViewIndexWithFocus(4);
+}
+// void MainMenuScene::showSnapshots() { SetNextViewIndexWithFocus(5); }
+void MainMenuScene::ShowSystem()
+{
+ SetNextViewIndexWithFocus(5);
+}
+void MainMenuScene::ShowAbout()
+{
+ SetNextViewIndexWithFocus(6);
+}
+
+void MainMenuScene::SetNextViewIndexWithFocus(int i)
+{
+ m_focus_view = true;
+ SetNextViewIndex(i);
+
+ if (!g_scene_mgr.IsDisplayingScene()) {
+ g_scene_mgr.PushScene(*this);
+ }
+}
+
+void MainMenuScene::Show()
+{
+ m_background.Show();
+ m_nav_control_view.Show();
+ m_animation.EaseIn();
+}
+
+void MainMenuScene::Hide()
+{
+ m_background.Hide();
+ m_nav_control_view.Hide();
+ m_animation.EaseOut();
+}
+
+bool MainMenuScene::IsAnimating()
+{
+ return m_animation.IsAnimating();
+}
+
+void MainMenuScene::SetNextViewIndex(int i)
+{
+ m_next_view_index = i % m_tabs.size();
+ g_config.general.last_viewed_menu_index = i;
+}
+
+void MainMenuScene::HandleInput()
+{
+ bool nofocus = !ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow);
+ bool focus = ImGui::IsWindowFocused(ImGuiFocusedFlags_RootAndChildWindows |
+ ImGuiFocusedFlags_NoPopupHierarchy);
+
+ // XXX: Ensure we have focus for two frames. If a user cancels a popup window, we do not want to cancel main
+ // window as well.
+ if (nofocus || (focus && m_had_focus_last_frame &&
+ ImGui::IsNavInputTest(ImGuiNavInput_Cancel,
+ ImGuiInputReadMode_Pressed))) {
+ Hide();
+ return;
+ }
+
+ if (focus && m_had_focus_last_frame) {
+ if (ImGui::IsKeyPressed(ImGuiKey_GamepadL1)) {
+ SetNextViewIndex((m_current_view_index + m_tabs.size() - 1) %
+ m_tabs.size());
+ }
+
+ if (ImGui::IsKeyPressed(ImGuiKey_GamepadR1)) {
+ SetNextViewIndex((m_current_view_index + 1) % m_tabs.size());
+ }
+ }
+
+ m_had_focus_last_frame = focus;
+}
+
+bool MainMenuScene::Draw()
+{
+ m_animation.Step();
+ m_background.Draw();
+ m_nav_control_view.Draw();
+
+ ImGuiIO &io = ImGui::GetIO();
+ float t = m_animation.GetSinInterpolatedValue();
+ float window_alpha = t;
+
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, window_alpha);
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_ChildBorderSize, 0);
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
+
+ ImVec4 extents = g_viewport_mgr.GetExtents();
+ ImVec2 window_pos = ImVec2(io.DisplaySize.x / 2, extents.y);
+ ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, ImVec2(0.5, 0));
+
+ ImVec2 max_size = g_viewport_mgr.Scale(ImVec2(800, 0));
+ float x = fmin(io.DisplaySize.x - extents.x - extents.z, max_size.x);
+ float y = io.DisplaySize.y - extents.y - extents.w;
+ ImGui::SetNextWindowSize(ImVec2(x, y));
+
+ if (ImGui::Begin("###MainWindow", NULL,
+ ImGuiWindowFlags_NoDecoration |
+ ImGuiWindowFlags_NoSavedSettings)) {
+ //
+ // Nav menu
+ //
+
+ float width = ImGui::GetWindowWidth();
+ float nav_width = width * 0.3;
+ float content_width = width - nav_width;
+
+ ImGui::PushStyleColor(ImGuiCol_ChildBg, IM_COL32(26,26,26,255));
+
+ ImGui::BeginChild("###MainWindowNav", ImVec2(nav_width, -1), true, ImGuiWindowFlags_NavFlattened);
+
+ bool move_focus_to_tab = false;
+ if (m_current_view_index != m_next_view_index) {
+ m_current_view_index = m_next_view_index;
+ if (!m_focus_view) {
+ move_focus_to_tab = true;
+ }
+ }
+
+ int i = 0;
+ for (auto &button : m_tabs) {
+ if (move_focus_to_tab && i == m_current_view_index) {
+ ImGui::SetKeyboardFocusHere();
+ move_focus_to_tab = false;
+ }
+ if (button->Draw(i == m_current_view_index)) {
+ SetNextViewIndex(i);
+ }
+ if (i == m_current_view_index) {
+ ImGui::SetItemDefaultFocus();
+ }
+ i++;
+ }
+ ImGui::EndChild();
+ ImGui::PopStyleColor();
+
+ //
+ // Content
+ //
+ ImGui::SameLine();
+ int s = ImGui::GetTextLineHeight() * 0.75;
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(s, s));
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(s, s));
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 6*g_viewport_mgr.m_scale);
+
+ ImGui::PushID(m_current_view_index);
+ ImGui::BeginChild("###MainWindowContent", ImVec2(content_width, -1),
+ true,
+ ImGuiWindowFlags_AlwaysUseWindowPadding |
+ ImGuiWindowFlags_NavFlattened);
+
+ if (!g_input_mgr.IsNavigatingWithController()) {
+ // Close button
+ ImGui::PushFont(g_font_mgr.m_menu_font);
+ ImGuiStyle &style = ImGui::GetStyle();
+ ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 128));
+ ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
+ ImVec2 pos = ImGui::GetCursorPos();
+ ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - style.FramePadding.x * 2.0f - ImGui::GetTextLineHeight());
+ if (ImGui::Button(ICON_FA_XMARK)) {
+ Hide();
+ }
+ ImGui::SetCursorPos(pos);
+ ImGui::PopStyleColor(2);
+ ImGui::PopFont();
+ }
+
+ ImGui::PushFont(g_font_mgr.m_default_font);
+ if (m_focus_view) {
+ ImGui::SetKeyboardFocusHere();
+ m_focus_view = false;
+ }
+ m_tabs[m_current_view_index]->view()->Draw();
+
+ ImGui::PopFont();
+ ImGui::EndChild();
+ ImGui::PopID();
+ ImGui::PopStyleVar(3);
+
+ HandleInput();
+ }
+ ImGui::End();
+ ImGui::PopStyleVar(5);
+
+ return !m_animation.IsComplete();
+}
+
+MainMenuScene g_main_menu;
diff --git a/ui/xui/main-menu.hh b/ui/xui/main-menu.hh
new file mode 100644
index 0000000000..5a0efd01f3
--- /dev/null
+++ b/ui/xui/main-menu.hh
@@ -0,0 +1,187 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include
+#include
+#include
+#include "common.hh"
+#include "widgets.hh"
+#include "scene.hh"
+#include "scene-components.hh"
+
+extern "C" {
+#include "net/pcap.h"
+#undef snprintf // FIXME
+}
+
+class MainMenuTabView
+{
+public:
+ virtual ~MainMenuTabView();
+ virtual void Draw();
+};
+
+class MainMenuGeneralView : public virtual MainMenuTabView
+{
+public:
+ void Draw() override;
+};
+
+class MainMenuInputView : public virtual MainMenuTabView
+{
+public:
+ void Draw() override;
+};
+
+class MainMenuDisplayView : public virtual MainMenuTabView
+{
+public:
+ void Draw() override;
+};
+
+class MainMenuAudioView : public virtual MainMenuTabView
+{
+public:
+ void Draw() override;
+};
+
+class NetworkInterface
+{
+public:
+ std::string m_pcap_name;
+ std::string m_description;
+ std::string m_friendly_name;
+
+ NetworkInterface(pcap_if_t *pcap_desc, char *_friendlyname = NULL);
+};
+
+class NetworkInterfaceManager
+{
+public:
+ std::vector> m_ifaces;
+ NetworkInterface *m_current_iface;
+ bool m_failed_to_load_lib;
+
+ NetworkInterfaceManager();
+ void Refresh(void);
+ void Select(NetworkInterface &iface);
+ bool IsCurrent(NetworkInterface &iface);
+};
+
+class MainMenuNetworkView : public virtual MainMenuTabView
+{
+protected:
+ char remote_addr[64];
+ char local_addr[64];
+ bool should_refresh;
+ std::unique_ptr iface_mgr;
+
+public:
+ MainMenuNetworkView();
+ void Draw() override;
+ void DrawPcapOptions(bool appearing);
+ void DrawNatOptions(bool appearing);
+ void DrawUdpOptions(bool appearing);
+};
+
+class MainMenuSnapshotsView : public virtual MainMenuTabView
+{
+public:
+ void SnapshotBigButton(const char *name, const char *title_name,
+ GLuint screenshot);
+ void Draw() override;
+};
+
+class MainMenuSystemView : public virtual MainMenuTabView
+{
+protected:
+ bool m_dirty;
+
+public:
+ MainMenuSystemView();
+ void Draw() override;
+};
+
+class MainMenuAboutView : public virtual MainMenuTabView
+{
+public:
+ void Draw() override;
+};
+
+class MainMenuTabButton
+{
+protected:
+ std::string m_icon;
+ std::string m_text;
+ MainMenuTabView *m_view;
+
+public:
+ MainMenuTabButton(std::string text, std::string icon = "", MainMenuTabView *view = nullptr);
+ MainMenuTabView *view();
+ bool Draw(bool selected);
+};
+
+class MainMenuScene : public virtual Scene {
+protected:
+ EasingAnimation m_animation;
+ bool m_focus_view;
+ bool m_had_focus_last_frame;
+ int m_current_view_index;
+ int m_next_view_index;
+ BackgroundGradient m_background;
+ NavControlAnnotation m_nav_control_view;
+ std::vector m_tabs;
+ MainMenuTabButton m_general_button,
+ m_input_button,
+ m_display_button,
+ m_audio_button,
+ m_network_button,
+ // m_snapshots_button,
+ m_system_button,
+ m_about_button;
+ MainMenuGeneralView m_general_view;
+ MainMenuInputView m_input_view;
+ MainMenuDisplayView m_display_view;
+ MainMenuAudioView m_audio_view;
+ MainMenuNetworkView m_network_view;
+ // MainMenuSnapshotsView m_snapshots_view;
+ MainMenuSystemView m_system_view;
+ MainMenuAboutView m_about_view;
+
+
+public:
+ MainMenuScene();
+ void ShowGeneral();
+ void ShowInput();
+ void ShowDisplay();
+ void ShowAudio();
+ void ShowNetwork();
+ // void ShowSnapshots();
+ void ShowSystem();
+ void ShowAbout();
+ void SetNextViewIndexWithFocus(int i);
+ void Show() override;
+ void Hide() override;
+ bool IsAnimating() override;
+ void SetNextViewIndex(int i);
+ void HandleInput();
+ bool Draw() override;
+};
+
+extern MainMenuScene g_main_menu;
diff --git a/ui/xui/main.cc b/ui/xui/main.cc
new file mode 100644
index 0000000000..bdba750434
--- /dev/null
+++ b/ui/xui/main.cc
@@ -0,0 +1,306 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+#include
+
+#include "common.hh"
+#include "xemu-hud.h"
+#include "misc.hh"
+#include "gl-helpers.hh"
+#include "input-manager.hh"
+#include "viewport-manager.hh"
+#include "font-manager.hh"
+#include "scene.hh"
+#include "scene-manager.hh"
+#include "main-menu.hh"
+#include "popup-menu.hh"
+#include "notifications.hh"
+#include "monitor.hh"
+#include "debug.hh"
+#include "welcome.hh"
+#include "menubar.hh"
+#include "compat.hh"
+#if defined(_WIN32)
+#include "update.hh"
+#endif
+
+bool g_screenshot_pending;
+float g_main_menu_height;
+
+static ImGuiStyle g_base_style;
+static SDL_Window *g_sdl_window;
+static float g_last_scale;
+static int g_vsync;
+static GLuint g_tex;
+static bool g_flip_req;
+
+
+static void InitializeStyle()
+{
+ g_font_mgr.Rebuild();
+
+ ImGui::StyleColorsDark();
+ ImVec4 *c = ImGui::GetStyle().Colors;
+ c[ImGuiCol_Text] = ImVec4(0.94f, 0.94f, 0.94f, 1.00f);
+ c[ImGuiCol_TextDisabled] = ImVec4(0.86f, 0.93f, 0.89f, 0.28f);
+ c[ImGuiCol_WindowBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f);
+ c[ImGuiCol_ChildBg] = ImVec4(0.06f, 0.06f, 0.06f, 0.98f);
+ c[ImGuiCol_PopupBg] = ImVec4(0.10f, 0.10f, 0.10f, 1.00f);
+ c[ImGuiCol_Border] = ImVec4(0.11f, 0.11f, 0.11f, 0.60f);
+ c[ImGuiCol_BorderShadow] = ImVec4(0.16f, 0.16f, 0.16f, 0.00f);
+ c[ImGuiCol_FrameBg] = ImVec4(0.18f, 0.18f, 0.18f, 1.00f);
+ c[ImGuiCol_FrameBgHovered] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
+ c[ImGuiCol_FrameBgActive] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
+ c[ImGuiCol_TitleBg] = ImVec4(0.20f, 0.51f, 0.18f, 1.00f);
+ c[ImGuiCol_TitleBgActive] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
+ c[ImGuiCol_TitleBgCollapsed] = ImVec4(0.16f, 0.16f, 0.16f, 0.75f);
+ c[ImGuiCol_MenuBarBg] = ImVec4(0.14f, 0.14f, 0.14f, 0.00f);
+ c[ImGuiCol_ScrollbarBg] = ImVec4(0.16f, 0.16f, 0.16f, 0.00f);
+ c[ImGuiCol_ScrollbarGrab] = ImVec4(0.30f, 0.30f, 0.30f, 1.00f);
+ c[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
+ c[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
+ c[ImGuiCol_CheckMark] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
+ c[ImGuiCol_SliderGrab] = ImVec4(0.90f, 0.90f, 0.90f, 1.00f);
+ c[ImGuiCol_SliderGrabActive] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);
+ c[ImGuiCol_Button] = ImVec4(0.17f, 0.17f, 0.17f, 1.00f);
+ c[ImGuiCol_ButtonHovered] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
+ c[ImGuiCol_ButtonActive] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
+ c[ImGuiCol_Header] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
+ c[ImGuiCol_HeaderHovered] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
+ c[ImGuiCol_HeaderActive] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
+ c[ImGuiCol_Separator] = ImVec4(1.00f, 1.00f, 1.00f, 0.25f);
+ c[ImGuiCol_SeparatorHovered] = ImVec4(0.13f, 0.87f, 0.16f, 0.78f);
+ c[ImGuiCol_SeparatorActive] = ImVec4(0.25f, 0.75f, 0.10f, 1.00f);
+ c[ImGuiCol_ResizeGrip] = ImVec4(0.47f, 0.83f, 0.49f, 0.04f);
+ c[ImGuiCol_ResizeGripHovered] = ImVec4(0.28f, 0.71f, 0.25f, 0.78f);
+ c[ImGuiCol_ResizeGripActive] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
+ c[ImGuiCol_Tab] = ImVec4(0.26f, 0.67f, 0.23f, 0.95f);
+ c[ImGuiCol_TabHovered] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
+ c[ImGuiCol_TabActive] = ImVec4(0.24f, 0.60f, 0.00f, 1.00f);
+ c[ImGuiCol_TabUnfocused] = ImVec4(0.21f, 0.54f, 0.19f, 0.99f);
+ c[ImGuiCol_TabUnfocusedActive] = ImVec4(0.24f, 0.60f, 0.21f, 1.00f);
+ c[ImGuiCol_PlotLines] = ImVec4(0.86f, 0.93f, 0.89f, 0.63f);
+ c[ImGuiCol_PlotLinesHovered] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
+ c[ImGuiCol_PlotHistogram] = ImVec4(0.86f, 0.93f, 0.89f, 0.63f);
+ c[ImGuiCol_PlotHistogramHovered] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
+ c[ImGuiCol_TextSelectedBg] = ImVec4(0.26f, 0.66f, 0.23f, 1.00f);
+ c[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f);
+ c[ImGuiCol_NavHighlight] = ImVec4(0.28f, 0.71f, 0.25f, 1.00f);
+ c[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f);
+ c[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f);
+ c[ImGuiCol_ModalWindowDimBg] = ImVec4(0.16f, 0.16f, 0.16f, 0.73f);
+
+ ImGuiStyle &s = ImGui::GetStyle();
+ s.WindowRounding = 6.0;
+ s.FrameRounding = 6.0;
+ s.PopupRounding = 6.0;
+ g_base_style = s;
+}
+
+void xemu_hud_init(SDL_Window* window, void* sdl_gl_context)
+{
+ xemu_monitor_init();
+ g_vsync = g_config.display.window.vsync;
+
+ InitCustomRendering();
+
+ // Setup Dear ImGui context
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImGuiIO& io = ImGui::GetIO();
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard;
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
+ io.IniFilename = NULL;
+
+ // Setup Platform/Renderer bindings
+ ImGui_ImplSDL2_InitForOpenGL(window, sdl_gl_context);
+ ImGui_ImplOpenGL3_Init("#version 150");
+ g_sdl_window = window;
+ ImPlot::CreateContext();
+
+#if defined(_WIN32)
+ if (!g_config.general.show_welcome && g_config.general.updates.check) {
+ update_window.CheckForUpdates();
+ }
+#endif
+ g_last_scale = g_viewport_mgr.m_scale;
+ InitializeStyle();
+ g_main_menu.SetNextViewIndex(g_config.general.last_viewed_menu_index);
+ first_boot_window.is_open = g_config.general.show_welcome;
+}
+
+void xemu_hud_cleanup(void)
+{
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplSDL2_Shutdown();
+ ImGui::DestroyContext();
+}
+
+void xemu_hud_process_sdl_events(SDL_Event *event)
+{
+ ImGui_ImplSDL2_ProcessEvent(event);
+}
+
+void xemu_hud_should_capture_kbd_mouse(int *kbd, int *mouse)
+{
+ ImGuiIO& io = ImGui::GetIO();
+ if (kbd) *kbd = io.WantCaptureKeyboard;
+ if (mouse) *mouse = io.WantCaptureMouse;
+}
+
+void xemu_hud_set_framebuffer_texture(GLuint tex, bool flip)
+{
+ g_tex = tex;
+ g_flip_req = flip;
+}
+
+void xemu_hud_render(void)
+{
+ ImGuiIO& io = ImGui::GetIO();
+ uint32_t now = SDL_GetTicks();
+
+ g_viewport_mgr.Update();
+ g_font_mgr.Update();
+ if (g_last_scale != g_viewport_mgr.m_scale) {
+ ImGuiStyle &style = ImGui::GetStyle();
+ style = g_base_style;
+ style.ScaleAllSizes(g_viewport_mgr.m_scale);
+ g_last_scale = g_viewport_mgr.m_scale;
+ }
+
+ if (!first_boot_window.is_open) {
+ RenderFramebuffer(g_tex, io.DisplaySize.x, io.DisplaySize.y, g_flip_req);
+ }
+
+ ImGui_ImplOpenGL3_NewFrame();
+ io.ConfigFlags &= ~ImGuiConfigFlags_NavEnableGamepad;
+ ImGui_ImplSDL2_NewFrame(g_sdl_window);
+ io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad;
+ io.BackendFlags |= ImGuiBackendFlags_HasGamepad;
+ g_input_mgr.Update();
+
+ ImGui::NewFrame();
+ ProcessKeyboardShortcuts();
+
+#if defined(DEBUG_NV2A_GL) && defined(CONFIG_RENDERDOC)
+ if (capture_renderdoc_frame) {
+ nv2a_dbg_renderdoc_capture_frames(1);
+ capture_renderdoc_frame = false;
+ }
+#endif
+
+ if (g_config.display.ui.show_menubar && !first_boot_window.is_open) {
+ // Auto-hide main menu after 5s of inactivity
+ static uint32_t last_check = 0;
+ float alpha = 1.0;
+ const uint32_t timeout = 5000;
+ const float fade_duration = 1000.0;
+ bool menu_wakeup = g_input_mgr.MouseMoved();
+ if (menu_wakeup) {
+ last_check = now;
+ }
+ if ((now-last_check) > timeout) {
+ if (g_config.display.ui.use_animations) {
+ float t = fmin((float)((now-last_check)-timeout)/fade_duration, 1);
+ alpha = 1-t;
+ if (t >= 1) {
+ alpha = 0;
+ }
+ } else {
+ alpha = 0;
+ }
+ }
+ if (alpha > 0.0) {
+ ImVec4 tc = ImGui::GetStyle().Colors[ImGuiCol_Text];
+ tc.w = alpha;
+ ImGui::PushStyleColor(ImGuiCol_Text, tc);
+ ImGui::SetNextWindowBgAlpha(alpha);
+ ShowMainMenu();
+ ImGui::PopStyleColor();
+ } else {
+ g_main_menu_height = 0;
+ }
+ }
+
+ if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow) &&
+ !g_scene_mgr.IsDisplayingScene()) {
+
+ // If the guide button is pressed, wake the ui
+ bool menu_button = false;
+ uint32_t buttons = g_input_mgr.CombinedButtons();
+ if (buttons & CONTROLLER_BUTTON_GUIDE) {
+ menu_button = true;
+ }
+
+ // Allow controllers without a guide button to also work
+ if ((buttons & CONTROLLER_BUTTON_BACK) &&
+ (buttons & CONTROLLER_BUTTON_START)) {
+ menu_button = true;
+ }
+
+ if (ImGui::IsKeyPressed(ImGuiKey_F1)) {
+ g_scene_mgr.PushScene(g_main_menu);
+ } else if (ImGui::IsKeyPressed(ImGuiKey_F2)) {
+ g_scene_mgr.PushScene(g_popup_menu);
+ } else if (menu_button ||
+ (ImGui::IsMouseClicked(ImGuiMouseButton_Right) &&
+ !ImGui::IsAnyItemFocused() && !ImGui::IsAnyItemHovered())) {
+ g_scene_mgr.PushScene(g_popup_menu);
+ }
+ }
+
+ first_boot_window.Draw();
+ monitor_window.Draw();
+ apu_window.Draw();
+ video_window.Draw();
+ compatibility_reporter_window.Draw();
+#if defined(_WIN32)
+ update_window.Draw();
+#endif
+ g_scene_mgr.Draw();
+ if (!first_boot_window.is_open) notification_manager.Draw();
+
+ // static bool show_demo = true;
+ // if (show_demo) ImGui::ShowDemoWindow(&show_demo);
+
+ ImGui::Render();
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+
+ if (g_vsync != g_config.display.window.vsync) {
+ g_vsync = g_config.display.window.vsync;
+ SDL_GL_SetSwapInterval(g_vsync ? 1 : 0);
+ }
+
+ if (g_screenshot_pending) {
+ SaveScreenshot(g_tex, g_flip_req);
+ g_screenshot_pending = false;
+ }
+}
diff --git a/ui/xui/menubar.cc b/ui/xui/menubar.cc
new file mode 100644
index 0000000000..8b1564a44f
--- /dev/null
+++ b/ui/xui/menubar.cc
@@ -0,0 +1,192 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "common.hh"
+#include "main-menu.hh"
+#include "menubar.hh"
+#include "misc.hh"
+#include "widgets.hh"
+#include "monitor.hh"
+#include "debug.hh"
+#include "actions.hh"
+#include "compat.hh"
+#include "update.hh"
+#include "../xemu-os-utils.h"
+
+extern float g_main_menu_height; // FIXME
+
+#ifdef CONFIG_RENDERDOC
+static bool capture_renderdoc_frame = false;
+#endif
+
+#if defined(__APPLE__)
+#define SHORTCUT_MENU_TEXT(c) "Cmd+" #c
+#else
+#define SHORTCUT_MENU_TEXT(c) "Ctrl+" #c
+#endif
+
+void ProcessKeyboardShortcuts(void)
+{
+ if (IsShortcutKeyPressed(ImGuiKey_E)) {
+ ActionEjectDisc();
+ }
+
+ if (IsShortcutKeyPressed(ImGuiKey_O)) {
+ ActionLoadDisc();
+ }
+
+ if (IsShortcutKeyPressed(ImGuiKey_P)) {
+ ActionTogglePause();
+ }
+
+ if (IsShortcutKeyPressed(ImGuiKey_R)) {
+ ActionReset();
+ }
+
+ if (IsShortcutKeyPressed(ImGuiKey_Q)) {
+ ActionShutdown();
+ }
+
+ if (ImGui::IsKeyPressed(ImGuiKey_GraveAccent)) {
+ monitor_window.ToggleOpen();
+ }
+
+#if defined(DEBUG_NV2A_GL) && defined(CONFIG_RENDERDOC)
+ if (ImGui::IsKeyPressed(ImGuiKey_F10)) {
+ nv2a_dbg_renderdoc_capture_frames(1);
+ }
+#endif
+}
+
+void ShowMainMenu()
+{
+ bool running = runstate_is_running();
+
+ if (ImGui::BeginMainMenuBar())
+ {
+ if (ImGui::BeginMenu("Machine"))
+ {
+ if (ImGui::MenuItem(running ? "Pause" : "Resume", SHORTCUT_MENU_TEXT(P))) ActionTogglePause();
+ if (ImGui::MenuItem("Screenshot")) ActionScreenshot();
+
+ ImGui::Separator();
+
+ if (ImGui::MenuItem("Eject Disc", SHORTCUT_MENU_TEXT(E))) ActionEjectDisc();
+ if (ImGui::MenuItem("Load Disc...", SHORTCUT_MENU_TEXT(O))) ActionLoadDisc();
+
+ ImGui::Separator();
+
+ ImGui::MenuItem("Settings", NULL, false, false);
+ if (ImGui::MenuItem(" General")) g_main_menu.ShowGeneral();
+ if (ImGui::MenuItem(" Input")) g_main_menu.ShowInput();
+ if (ImGui::MenuItem(" Display")) g_main_menu.ShowDisplay();
+ if (ImGui::MenuItem(" Audio")) g_main_menu.ShowAudio();
+ if (ImGui::MenuItem(" Network")) g_main_menu.ShowNetwork();
+ if (ImGui::MenuItem(" System")) g_main_menu.ShowSystem();
+
+ ImGui::Separator();
+
+ if (ImGui::MenuItem("Reset", SHORTCUT_MENU_TEXT(R))) ActionReset();
+ if (ImGui::MenuItem("Exit", SHORTCUT_MENU_TEXT(Q))) ActionShutdown();
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("View"))
+ {
+ int ui_scale_idx;
+ if (g_config.display.ui.auto_scale) {
+ ui_scale_idx = 0;
+ } else {
+ ui_scale_idx = g_config.display.ui.scale;
+ if (ui_scale_idx < 0) ui_scale_idx = 0;
+ else if (ui_scale_idx > 2) ui_scale_idx = 2;
+ }
+ if (ImGui::Combo("UI Scale", &ui_scale_idx,
+ "Auto\0"
+ "1x\0"
+ "2x\0")) {
+ if (ui_scale_idx == 0) {
+ g_config.display.ui.auto_scale = true;
+ } else {
+ g_config.display.ui.auto_scale = false;
+ g_config.display.ui.scale = ui_scale_idx;
+ }
+ }
+ int rendering_scale = nv2a_get_surface_scale_factor() - 1;
+ if (ImGui::Combo("Int. Resolution Scale", &rendering_scale,
+ "1x\0"
+ "2x\0"
+ "3x\0"
+ "4x\0"
+ "5x\0"
+ "6x\0"
+ "7x\0"
+ "8x\0"
+ "9x\0"
+ "10x\0")) {
+ nv2a_set_surface_scale_factor(rendering_scale + 1);
+ }
+
+ ImGui::Combo("Display Mode", &g_config.display.ui.fit,
+ "Center\0Scale\0Scale (Widescreen 16:9)\0Scale "
+ "(4:3)\0Stretch\0");
+ ImGui::SameLine();
+ HelpMarker("Controls how the rendered content should be scaled "
+ "into the window");
+ if (ImGui::MenuItem("Fullscreen", SHORTCUT_MENU_TEXT(Alt + F),
+ xemu_is_fullscreen(), true)) {
+ xemu_toggle_fullscreen();
+ }
+
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Debug"))
+ {
+ ImGui::MenuItem("Monitor", "~", &monitor_window.is_open);
+ ImGui::MenuItem("Audio", NULL, &apu_window.m_is_open);
+ ImGui::MenuItem("Video", NULL, &video_window.m_is_open);
+#if defined(DEBUG_NV2A_GL) && defined(CONFIG_RENDERDOC)
+ if (nv2a_dbg_renderdoc_available()) {
+ ImGui::MenuItem("RenderDoc: Capture", NULL, &capture_renderdoc_frame);
+ }
+#endif
+ ImGui::EndMenu();
+ }
+
+ if (ImGui::BeginMenu("Help"))
+ {
+ if (ImGui::MenuItem("Help", NULL)) {
+ xemu_open_web_browser("https://xemu.app/docs/getting-started/");
+ }
+
+ ImGui::MenuItem("Report Compatibility...", NULL,
+ &compatibility_reporter_window.is_open);
+#if defined(_WIN32)
+ ImGui::MenuItem("Check for Updates...", NULL, &update_window.is_open);
+#endif
+
+ ImGui::Separator();
+ if (ImGui::MenuItem("About")) g_main_menu.ShowAbout();
+ ImGui::EndMenu();
+ }
+
+ g_main_menu_height = ImGui::GetWindowHeight();
+ ImGui::EndMainMenuBar();
+ }
+}
diff --git a/ui/xui/menubar.hh b/ui/xui/menubar.hh
new file mode 100644
index 0000000000..00774afc48
--- /dev/null
+++ b/ui/xui/menubar.hh
@@ -0,0 +1,22 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+
+void ProcessKeyboardShortcuts(void);
+void ShowMainMenu();
diff --git a/ui/xui/meson.build b/ui/xui/meson.build
new file mode 100644
index 0000000000..088e68c764
--- /dev/null
+++ b/ui/xui/meson.build
@@ -0,0 +1,24 @@
+xemu_ss.add(files(
+ 'actions.cc',
+ 'animation.cc',
+ 'compat.cc',
+ 'debug.cc',
+ 'font-manager.cc',
+ 'gl-helpers.cc',
+ 'input-manager.cc',
+ 'main-menu.cc',
+ 'main.cc',
+ 'menubar.cc',
+ 'monitor.cc',
+ 'notifications.cc',
+ 'popup-menu.cc',
+ 'reporting.cc',
+ 'scene-components.cc',
+ 'scene-manager.cc',
+ 'scene.cc',
+ 'viewport-manager.cc',
+ 'welcome.cc',
+ 'widgets.cc',
+))
+
+xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('update.cc'))
diff --git a/ui/xui/misc.hh b/ui/xui/misc.hh
new file mode 100644
index 0000000000..b2c8a9e99d
--- /dev/null
+++ b/ui/xui/misc.hh
@@ -0,0 +1,106 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include
+#include
+#include
+#include
+#include "common.hh"
+#include "xemu-hud.h"
+
+extern "C" {
+#include
+}
+
+static inline
+bool IsNavInputPressed(ImGuiNavInput i) {
+ ImGuiIO &io = ImGui::GetIO();
+ return io.NavInputs[i] > 0.0f && io.NavInputsDownDuration[i] == 0.0f;
+}
+
+
+static inline const char *PausedFileOpen(int flags, const char *filters,
+ const char *default_path,
+ const char *default_name)
+{
+ bool is_running = runstate_is_running();
+ if (is_running) {
+ vm_stop(RUN_STATE_PAUSED);
+ }
+ const char *r = noc_file_dialog_open(flags, filters, default_path, default_name);
+ if (is_running) {
+ vm_start();
+ }
+
+ return r;
+}
+
+template
+std::string string_format( const std::string& format, Args ... args )
+{
+ int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
+ if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
+ auto size = static_cast( size_s );
+ std::unique_ptr buf( new char[ size ] );
+ std::snprintf( buf.get(), size, format.c_str(), args ... );
+ return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
+}
+
+static inline bool IsShortcutKeyPressed(int scancode)
+{
+ ImGuiIO& io = ImGui::GetIO();
+ const bool is_osx = io.ConfigMacOSXBehaviors;
+ const bool is_shortcut_key = (is_osx ? (io.KeySuper && !io.KeyCtrl) : (io.KeyCtrl && !io.KeySuper)) && !io.KeyAlt && !io.KeyShift; // OS X style: Shortcuts using Cmd/Super instead of Ctrl
+ return is_shortcut_key && ImGui::IsKeyPressed(scancode);
+}
+
+static inline float mix(float a, float b, float t)
+{
+ return a*(1.0-t) + (b-a)*t;
+}
+
+static inline
+int PushWindowTransparencySettings(bool transparent, float alpha_transparent = 0.4, float alpha_opaque = 1.0)
+{
+ float alpha = transparent ? alpha_transparent : alpha_opaque;
+
+ ImVec4 c;
+
+ c = ImGui::GetStyle().Colors[transparent ? ImGuiCol_WindowBg : ImGuiCol_TitleBg];
+ c.w *= alpha;
+ ImGui::PushStyleColor(ImGuiCol_TitleBg, c);
+
+ c = ImGui::GetStyle().Colors[transparent ? ImGuiCol_WindowBg : ImGuiCol_TitleBgActive];
+ c.w *= alpha;
+ ImGui::PushStyleColor(ImGuiCol_TitleBgActive, c);
+
+ c = ImGui::GetStyle().Colors[ImGuiCol_WindowBg];
+ c.w *= alpha;
+ ImGui::PushStyleColor(ImGuiCol_WindowBg, c);
+
+ c = ImGui::GetStyle().Colors[ImGuiCol_Border];
+ c.w *= alpha;
+ ImGui::PushStyleColor(ImGuiCol_Border, c);
+
+ c = ImGui::GetStyle().Colors[ImGuiCol_FrameBg];
+ c.w *= alpha;
+ ImGui::PushStyleColor(ImGuiCol_FrameBg, c);
+
+ return 5;
+}
diff --git a/ui/xui/monitor.cc b/ui/xui/monitor.cc
new file mode 100644
index 0000000000..4566425330
--- /dev/null
+++ b/ui/xui/monitor.cc
@@ -0,0 +1,155 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "monitor.hh"
+#include "imgui.h"
+#include "misc.hh"
+#include "font-manager.hh"
+
+// Portable helpers
+static int Stricmp(const char* str1, const char* str2) { int d; while ((d = toupper(*str2) - toupper(*str1)) == 0 && *str1) { str1++; str2++; } return d; }
+static char* Strdup(const char *str) { size_t len = strlen(str) + 1; void* buf = malloc(len); IM_ASSERT(buf); return (char*)memcpy(buf, (const void*)str, len); }
+static void Strtrim(char* str) { char* str_end = str + strlen(str); while (str_end > str && str_end[-1] == ' ') str_end--; *str_end = 0; }
+
+MonitorWindow::MonitorWindow()
+{
+ is_open = false;
+ memset(InputBuf, 0, sizeof(InputBuf));
+ HistoryPos = -1;
+ AutoScroll = true;
+ ScrollToBottom = false;
+}
+MonitorWindow::~MonitorWindow()
+{
+}
+
+void MonitorWindow::Draw()
+{
+ if (!is_open) return;
+ int style_pop_cnt = PushWindowTransparencySettings(true) + 1;
+ ImGui::PushStyleColor(ImGuiCol_ChildBg, ImU32(ImColor(0, 0, 0, 80)));
+ ImGuiIO& io = ImGui::GetIO();
+ ImVec2 window_pos = ImVec2(0,io.DisplaySize.y/2);
+ ImGui::SetNextWindowPos(window_pos, ImGuiCond_Appearing);
+ ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y/2), ImGuiCond_Appearing);
+ if (ImGui::Begin("Monitor", &is_open, ImGuiWindowFlags_NoCollapse)) {
+ const float footer_height_to_reserve = ImGui::GetStyle().ItemSpacing.y + ImGui::GetFrameHeightWithSpacing(); // 1 separator, 1 input text
+ ImGui::BeginChild("ScrollingRegion", ImVec2(0, -footer_height_to_reserve), false, ImGuiWindowFlags_HorizontalScrollbar); // Leave room for 1 separator + 1 InputText
+
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(4,1)); // Tighten spacing
+ ImGui::PushFont(g_font_mgr.m_fixed_width_font);
+ ImGui::TextUnformatted(xemu_get_monitor_buffer());
+ ImGui::PopFont();
+
+ if (ScrollToBottom || (AutoScroll && ImGui::GetScrollY() >= ImGui::GetScrollMaxY())) {
+ ImGui::SetScrollHereY(1.0f);
+ }
+ ScrollToBottom = false;
+
+ ImGui::PopStyleVar();
+ ImGui::EndChild();
+ ImGui::Separator();
+ // Command-line
+ bool reclaim_focus = ImGui::IsWindowAppearing();
+
+ ImGui::SetNextItemWidth(-1);
+ ImGui::PushFont(g_font_mgr.m_fixed_width_font);
+ if (ImGui::InputText("#commandline", InputBuf, IM_ARRAYSIZE(InputBuf), ImGuiInputTextFlags_EnterReturnsTrue|ImGuiInputTextFlags_CallbackCompletion|ImGuiInputTextFlags_CallbackHistory, &TextEditCallbackStub, (void*)this)) {
+ char* s = InputBuf;
+ Strtrim(s);
+ if (s[0])
+ ExecCommand(s);
+ strcpy(s, "");
+ reclaim_focus = true;
+ }
+ ImGui::PopFont();
+ // Auto-focus on window apparition
+ ImGui::SetItemDefaultFocus();
+ if (reclaim_focus) {
+ ImGui::SetKeyboardFocusHere(-1); // Auto focus previous widget
+ }
+ }
+ ImGui::End();
+ ImGui::PopStyleColor(style_pop_cnt);
+}
+
+void MonitorWindow::ToggleOpen(void)
+{
+ is_open = !is_open;
+}
+
+void MonitorWindow::ExecCommand(const char* command_line)
+{
+ xemu_run_monitor_command(command_line);
+
+ // Insert into history. First find match and delete it so it can be pushed to the back. This isn't trying to be smart or optimal.
+ HistoryPos = -1;
+ for (int i = History.Size-1; i >= 0; i--)
+ if (Stricmp(History[i], command_line) == 0)
+ {
+ free(History[i]);
+ History.erase(History.begin() + i);
+ break;
+ }
+ History.push_back(Strdup(command_line));
+
+ // On commad input, we scroll to bottom even if AutoScroll==false
+ ScrollToBottom = true;
+}
+
+int MonitorWindow::TextEditCallbackStub(ImGuiInputTextCallbackData* data) // In C++11 you are better off using lambdas for this sort of forwarding callbacks
+{
+ MonitorWindow* console = (MonitorWindow*)data->UserData;
+ return console->TextEditCallback(data);
+}
+
+int MonitorWindow::TextEditCallback(ImGuiInputTextCallbackData* data)
+{
+ switch (data->EventFlag)
+ {
+ case ImGuiInputTextFlags_CallbackHistory:
+ {
+ // Example of HISTORY
+ const int prev_history_pos = HistoryPos;
+ if (data->EventKey == ImGuiKey_UpArrow)
+ {
+ if (HistoryPos == -1)
+ HistoryPos = History.Size - 1;
+ else if (HistoryPos > 0)
+ HistoryPos--;
+ }
+ else if (data->EventKey == ImGuiKey_DownArrow)
+ {
+ if (HistoryPos != -1)
+ if (++HistoryPos >= History.Size)
+ HistoryPos = -1;
+ }
+
+ // A better implementation would preserve the data on the current input line along with cursor position.
+ if (prev_history_pos != HistoryPos)
+ {
+ const char* history_str = (HistoryPos >= 0) ? History[HistoryPos] : "";
+ data->DeleteChars(0, data->BufTextLen);
+ data->InsertChars(0, history_str);
+ }
+ }
+ }
+ return 0;
+}
+
+MonitorWindow monitor_window;
diff --git a/ui/xui/monitor.hh b/ui/xui/monitor.hh
new file mode 100644
index 0000000000..e37388a033
--- /dev/null
+++ b/ui/xui/monitor.hh
@@ -0,0 +1,50 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include "../xemu-monitor.h"
+#include "common.hh"
+
+class MonitorWindow
+{
+public:
+ bool is_open;
+
+private:
+ char InputBuf[256];
+ ImVector Items;
+ ImVector Commands;
+ ImVector History;
+ int HistoryPos; // -1: new line, 0..History.Size-1 browsing history.
+ ImGuiTextFilter Filter;
+ bool AutoScroll;
+ bool ScrollToBottom;
+
+public:
+ MonitorWindow();
+ ~MonitorWindow();
+ void Draw();
+ void ToggleOpen(void);
+
+private:
+ void ExecCommand(const char* command_line);
+ static int TextEditCallbackStub(ImGuiInputTextCallbackData* data);
+ int TextEditCallback(ImGuiInputTextCallbackData* data);
+};
+
+extern MonitorWindow monitor_window;
diff --git a/ui/xui/notifications.cc b/ui/xui/notifications.cc
new file mode 100644
index 0000000000..42fbbf9657
--- /dev/null
+++ b/ui/xui/notifications.cc
@@ -0,0 +1,155 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "notifications.hh"
+#include "common.hh"
+
+#include "../xemu-notifications.h"
+
+NotificationManager notification_manager;
+
+NotificationManager::NotificationManager()
+{
+ m_active = false;
+}
+
+void NotificationManager::QueueNotification(const char *msg)
+{
+ m_notification_queue.push_back(strdup(msg));
+}
+
+void NotificationManager::QueueError(const char *msg)
+{
+ m_error_queue.push_back(strdup(msg));
+}
+
+void NotificationManager::Draw()
+{
+ uint32_t now = SDL_GetTicks();
+
+ if (m_active) {
+ // Currently displaying a notification
+ float t =
+ (m_notification_end_time - now) / (float)kNotificationDuration;
+ if (t > 1.0) {
+ // Notification delivered, free it
+ free((void *)m_msg);
+ m_active = false;
+ } else {
+ // Notification should be displayed
+ DrawNotification(t, m_msg);
+ }
+ } else {
+ // Check to see if a notification is pending
+ if (m_notification_queue.size() > 0) {
+ m_msg = m_notification_queue[0];
+ m_active = true;
+ m_notification_end_time = now + kNotificationDuration;
+ m_notification_queue.pop_front();
+ }
+ }
+
+ ImGuiIO& io = ImGui::GetIO();
+
+ if (m_error_queue.size() > 0) {
+ ImGui::OpenPopup("Error");
+ ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x/2, io.DisplaySize.y/2),
+ ImGuiCond_Always, ImVec2(0.5, 0.5));
+ }
+ if (ImGui::BeginPopupModal("Error", NULL, ImGuiWindowFlags_AlwaysAutoResize))
+ {
+ ImGui::Text("%s", m_error_queue[0]);
+ ImGui::Dummy(ImVec2(0,16));
+ ImGui::SetItemDefaultFocus();
+ ImGuiStyle &style = ImGui::GetStyle();
+ ImGui::SetCursorPosX(ImGui::GetWindowWidth()-(120+2*style.FramePadding.x));
+ if (ImGui::Button("Ok", ImVec2(120, 0))) {
+ ImGui::CloseCurrentPopup();
+ free((void*)m_error_queue[0]);
+ m_error_queue.pop_front();
+ }
+ ImGui::EndPopup();
+ }
+}
+
+void NotificationManager::DrawNotification(float t, const char *msg)
+{
+ const float DISTANCE = 10.0f;
+ static int corner = 1;
+ ImGuiIO& io = ImGui::GetIO();
+ if (corner != -1)
+ {
+ ImVec2 window_pos = ImVec2((corner & 1) ? io.DisplaySize.x - DISTANCE : DISTANCE, (corner & 2) ? io.DisplaySize.y - DISTANCE : DISTANCE);
+ window_pos.y = g_main_menu_height + DISTANCE;
+ ImVec2 window_pos_pivot = ImVec2((corner & 1) ? 1.0f : 0.0f, (corner & 2) ? 1.0f : 0.0f);
+ ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, window_pos_pivot);
+ }
+
+ const float fade_in = 0.1;
+ const float fade_out = 0.9;
+ float fade = 0;
+
+ if (t < fade_in) {
+ // Linear fade in
+ fade = t/fade_in;
+ } else if (t >= fade_out) {
+ // Linear fade out
+ fade = 1-(t-fade_out)/(1-fade_out);
+ } else {
+ // Constant
+ fade = 1.0;
+ }
+
+ ImVec4 color = ImGui::GetStyle().Colors[ImGuiCol_ButtonActive];
+ color.w *= fade;
+ ImGui::PushStyleVar(ImGuiStyleVar_PopupBorderSize, 1);
+ ImGui::PushStyleColor(ImGuiCol_PopupBg, ImVec4(0,0,0,fade*0.9f));
+ ImGui::PushStyleColor(ImGuiCol_Border, color);
+ ImGui::PushStyleColor(ImGuiCol_Text, color);
+ ImGui::SetNextWindowBgAlpha(0.90f * fade);
+ if (ImGui::Begin("Notification", NULL,
+ ImGuiWindowFlags_Tooltip |
+ ImGuiWindowFlags_NoMove |
+ ImGuiWindowFlags_NoDecoration |
+ ImGuiWindowFlags_AlwaysAutoResize |
+ ImGuiWindowFlags_NoSavedSettings |
+ ImGuiWindowFlags_NoFocusOnAppearing |
+ ImGuiWindowFlags_NoNav |
+ ImGuiWindowFlags_NoInputs
+ ))
+ {
+ ImGui::Text("%s", msg);
+ }
+ ImGui::PopStyleColor();
+ ImGui::PopStyleColor();
+ ImGui::PopStyleColor();
+ ImGui::PopStyleVar();
+ ImGui::End();
+}
+
+/* External interface, exposed via xemu-notifications.h */
+
+void xemu_queue_notification(const char *msg)
+{
+ notification_manager.QueueNotification(msg);
+}
+
+void xemu_queue_error_message(const char *msg)
+{
+ notification_manager.QueueError(msg);
+}
diff --git a/ui/xui/notifications.hh b/ui/xui/notifications.hh
new file mode 100644
index 0000000000..77a97d5915
--- /dev/null
+++ b/ui/xui/notifications.hh
@@ -0,0 +1,46 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include
+#include
+
+#include "../xemu-notifications.h"
+
+class NotificationManager
+{
+private:
+ std::deque m_notification_queue;
+ std::deque m_error_queue;
+
+ const int kNotificationDuration = 4000;
+ uint32_t m_notification_end_time;
+ const char *m_msg;
+ bool m_active;
+
+public:
+ NotificationManager();
+ void QueueNotification(const char *msg);
+ void QueueError(const char *msg);
+ void Draw();
+
+private:
+ void DrawNotification(float t, const char *msg);
+};
+
+extern NotificationManager notification_manager;
diff --git a/ui/xui/popup-menu.cc b/ui/xui/popup-menu.cc
new file mode 100644
index 0000000000..6cad92e2a1
--- /dev/null
+++ b/ui/xui/popup-menu.cc
@@ -0,0 +1,511 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include
+#include
+#include "misc.hh"
+#include "actions.hh"
+#include "font-manager.hh"
+#include "viewport-manager.hh"
+#include "scene-manager.hh"
+#include "popup-menu.hh"
+#include "input-manager.hh"
+#include "xemu-hud.h"
+#include "IconsFontAwesome6.h"
+
+PopupMenuItemDelegate::~PopupMenuItemDelegate() {}
+void PopupMenuItemDelegate::PushMenu(PopupMenu &menu) {}
+void PopupMenuItemDelegate::PopMenu() {}
+void PopupMenuItemDelegate::ClearMenuStack() {}
+void PopupMenuItemDelegate::LostFocus() {}
+void PopupMenuItemDelegate::PushFocus() {}
+void PopupMenuItemDelegate::PopFocus() {}
+bool PopupMenuItemDelegate::DidPop() { return false; }
+
+bool PopupMenuButton(std::string text, std::string icon = "")
+{
+ ImGui::PushFont(g_font_mgr.m_menu_font);
+ auto button_text = string_format("%s %s", icon.c_str(), text.c_str());
+ bool status = ImGui::Button(button_text.c_str(), ImVec2(-FLT_MIN, 0));
+ ImGui::PopFont();
+ return status;
+}
+
+bool PopupMenuCheck(std::string text, std::string icon = "", bool v = false)
+{
+ bool status = PopupMenuButton(text, icon);
+ if (v) {
+ ImGui::PushFont(g_font_mgr.m_menu_font);
+ const ImVec2 p0 = ImGui::GetItemRectMin();
+ const ImVec2 p1 = ImGui::GetItemRectMax();
+ const char *icon = ICON_FA_CHECK;
+ ImVec2 ts_icon = ImGui::CalcTextSize(icon);
+ ImDrawList *draw_list = ImGui::GetWindowDrawList();
+ ImGuiStyle &style = ImGui::GetStyle();
+ draw_list->AddText(ImVec2(p1.x - style.FramePadding.x - ts_icon.x,
+ p0.y + (p1.y - p0.y - ts_icon.y) / 2),
+ ImGui::GetColorU32(ImGuiCol_Text), icon);
+ ImGui::PopFont();
+ }
+ return status;
+}
+
+bool PopupMenuSubmenuButton(std::string text, std::string icon = "")
+{
+ bool status = PopupMenuButton(text, icon);
+
+ ImGui::PushFont(g_font_mgr.m_menu_font);
+ const ImVec2 p0 = ImGui::GetItemRectMin();
+ const ImVec2 p1 = ImGui::GetItemRectMax();
+ const char *right_icon = ICON_FA_CHEVRON_RIGHT;
+ ImVec2 ts_icon = ImGui::CalcTextSize(right_icon);
+ ImDrawList *draw_list = ImGui::GetWindowDrawList();
+ ImGuiStyle &style = ImGui::GetStyle();
+ draw_list->AddText(ImVec2(p1.x - style.FramePadding.x - ts_icon.x,
+ p0.y + (p1.y - p0.y - ts_icon.y) / 2),
+ ImGui::GetColorU32(ImGuiCol_Text), right_icon);
+ ImGui::PopFont();
+ return status;
+}
+
+bool PopupMenuToggle(std::string text, std::string icon = "", bool *v = nullptr)
+{
+ bool l_v = false;
+ if (v == NULL) v = &l_v;
+
+ ImGuiStyle &style = ImGui::GetStyle();
+ bool status = PopupMenuButton(text, icon);
+ ImVec2 p_min = ImGui::GetItemRectMin();
+ ImVec2 p_max = ImGui::GetItemRectMax();
+ if (status) *v = !*v;
+
+ ImGui::PushFont(g_font_mgr.m_menu_font);
+ float title_height = ImGui::GetTextLineHeight();
+ ImGui::PopFont();
+
+ float toggle_height = title_height * 0.75;
+ ImVec2 toggle_size(toggle_height * 1.75, toggle_height);
+ ImVec2 toggle_pos(p_max.x - toggle_size.x - style.FramePadding.x,
+ p_min.y + (title_height - toggle_size.y)/2 + style.FramePadding.y);
+ DrawToggle(*v, ImGui::IsItemHovered(), toggle_pos, toggle_size);
+
+ return status;
+}
+
+bool PopupMenuSlider(std::string text, std::string icon = "", float *v = NULL)
+{
+ bool status = PopupMenuButton(text, icon);
+ ImVec2 p_min = ImGui::GetItemRectMin();
+ ImVec2 p_max = ImGui::GetItemRectMax();
+
+ ImGuiStyle &style = ImGui::GetStyle();
+
+ float new_v = *v;
+
+ if (ImGui::IsItemHovered()) {
+ if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadLStickLeft) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadRStickLeft)) new_v -= 0.05;
+ if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadDpadRight) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadLStickRight) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadRStickRight)) new_v += 0.05;
+ }
+
+ ImGui::PushFont(g_font_mgr.m_menu_font);
+ float title_height = ImGui::GetTextLineHeight();
+ ImGui::PopFont();
+
+ float toggle_height = title_height * 0.75;
+ ImVec2 slider_size(toggle_height * 3.75, toggle_height);
+ ImVec2 slider_pos(p_max.x - slider_size.x - style.FramePadding.x,
+ p_min.y + (title_height - slider_size.y)/2 + style.FramePadding.y);
+
+ if (ImGui::IsItemActive()) {
+ ImVec2 mouse = ImGui::GetMousePos();
+ new_v = GetSliderValueForMousePos(mouse, slider_pos, slider_size);
+ }
+
+ DrawSlider(*v, ImGui::IsItemActive() || ImGui::IsItemHovered(), slider_pos,
+ slider_size);
+
+ *v = fmin(fmax(0, new_v), 1.0);
+
+ return status;
+}
+
+PopupMenu::PopupMenu() : m_animation(0.12, 0.12), m_ease_direction(0, 0)
+{
+ m_focus = false;
+ m_pop_focus = false;
+}
+
+void PopupMenu::InitFocus()
+{
+ m_pop_focus = true;
+}
+
+PopupMenu::~PopupMenu()
+{
+
+}
+
+void PopupMenu::Show(const ImVec2 &direction)
+{
+ m_animation.EaseIn();
+ m_ease_direction = direction;
+ m_focus = true;
+}
+
+void PopupMenu::Hide(const ImVec2 &direction)
+{
+ m_animation.EaseOut();
+ m_ease_direction = direction;
+}
+
+bool PopupMenu::IsAnimating()
+{
+ return m_animation.IsAnimating();
+}
+
+void PopupMenu::Draw(PopupMenuItemDelegate &nav)
+{
+ m_animation.Step();
+
+ ImGuiIO &io = ImGui::GetIO();
+ float t = m_animation.GetSinInterpolatedValue();
+ float window_alpha = t;
+ ImVec2 window_pos = ImVec2(io.DisplaySize.x / 2 + (1-t) * m_ease_direction.x,
+ io.DisplaySize.y / 2 + (1-t) * m_ease_direction.y);
+
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, window_alpha);
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding,
+ g_viewport_mgr.Scale(ImVec2(10, 5)));
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 0);
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(0, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0.5));
+ ImGui::PushStyleColor(ImGuiCol_Button, ImGui::GetColorU32(ImGuiCol_WindowBg));
+ ImGui::PushStyleColor(ImGuiCol_NavHighlight, IM_COL32_BLACK_TRANS);
+
+ if (m_focus) ImGui::SetNextWindowFocus();
+ ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always, ImVec2(0.5, 0.5));
+ ImGui::SetNextWindowSize(ImVec2(400*g_viewport_mgr.m_scale, 0), ImGuiCond_Always);
+ ImGui::SetNextWindowBgAlpha(0);
+
+ ImGui::Begin("###PopupMenu", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings);
+ if (DrawItems(nav)) nav.PopMenu();
+ if (!ImGui::IsWindowFocused(ImGuiFocusedFlags_AnyWindow)) nav.LostFocus();
+ ImVec2 pos = ImGui::GetWindowPos();
+ ImVec2 sz = ImGui::GetWindowSize();
+ ImGui::End();
+
+ if (!g_input_mgr.IsNavigatingWithController()) {
+ ImGui::PushFont(g_font_mgr.m_menu_font);
+ pos.y -= ImGui::GetFrameHeight();
+ ImGui::SetNextWindowPos(pos);
+ ImGui::SetNextWindowSize(ImVec2(sz.x, ImGui::GetFrameHeight()));
+ ImGui::SetNextWindowBgAlpha(0);
+ ImGui::Begin("###PopupMenuNav", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing);
+ ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(255, 255, 255, 200));
+ ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
+ if (ImGui::Button(ICON_FA_ARROW_LEFT)) {
+ nav.PopMenu();
+ }
+ ImGui::SameLine();
+ ImGui::SetCursorPosX(ImGui::GetContentRegionMax().x - ImGui::GetStyle().FramePadding.x * 2.0f - ImGui::GetTextLineHeight());
+ if (ImGui::Button(ICON_FA_XMARK)) {
+ nav.ClearMenuStack();
+ }
+ ImGui::PopStyleColor(2);
+ ImGui::End();
+ ImGui::PopFont();
+ }
+
+ ImGui::PopStyleColor(2);
+ ImGui::PopStyleVar(7);
+ m_pop_focus = false;
+ m_focus = false;
+}
+
+bool PopupMenu::DrawItems(PopupMenuItemDelegate &nav)
+{
+ return false;
+}
+
+class DisplayModePopupMenu : public virtual PopupMenu {
+public:
+ bool DrawItems(PopupMenuItemDelegate &nav) override
+ {
+ const char *values[] = {
+ "Center", "Scale", "Scale (Widescreen 16:9)", "Scale (4:3)", "Stretch"
+ };
+
+ for (int i = 0; i < CONFIG_DISPLAY_UI_FIT__COUNT; i++) {
+ bool selected = g_config.display.ui.fit == i;
+ if (m_focus && selected) ImGui::SetKeyboardFocusHere();
+ if (PopupMenuCheck(values[i], "", selected))
+ g_config.display.ui.fit = i;
+ }
+
+ return false;
+ }
+};
+
+extern Scene g_main_menu;
+
+class SettingsPopupMenu : public virtual PopupMenu {
+protected:
+ DisplayModePopupMenu display_mode;
+
+public:
+ bool DrawItems(PopupMenuItemDelegate &nav) override
+ {
+ bool pop = false;
+
+ if (m_focus && !m_pop_focus) {
+ ImGui::SetKeyboardFocusHere();
+ }
+ PopupMenuSlider("Volume", ICON_FA_VOLUME_HIGH, &g_config.audio.volume_limit);
+ bool fs = xemu_is_fullscreen();
+ if (PopupMenuToggle("Fullscreen", ICON_FA_WINDOW_MAXIMIZE, &fs)) {
+ xemu_toggle_fullscreen();
+ }
+ if (PopupMenuSubmenuButton("Display Mode", ICON_FA_EXPAND)) {
+ nav.PushFocus();
+ nav.PushMenu(display_mode);
+ }
+ if (PopupMenuButton("All settings...", ICON_FA_SLIDERS)) {
+ nav.ClearMenuStack();
+ g_scene_mgr.PushScene(g_main_menu);
+ }
+ if (m_pop_focus) {
+ nav.PopFocus();
+ }
+ return pop;
+ }
+};
+
+class RootPopupMenu : public virtual PopupMenu {
+protected:
+ SettingsPopupMenu settings;
+ bool refocus_first_item;
+
+public:
+ RootPopupMenu() {
+ refocus_first_item = false;
+ }
+
+ bool DrawItems(PopupMenuItemDelegate &nav) override
+ {
+ bool pop = false;
+
+ if (refocus_first_item || (m_focus && !m_pop_focus)) {
+ ImGui::SetKeyboardFocusHere();
+ refocus_first_item = false;
+ }
+
+ bool running = runstate_is_running();
+ if (running) {
+ if (PopupMenuButton("Pause", ICON_FA_CIRCLE_PAUSE)) {
+ ActionTogglePause();
+ refocus_first_item = true;
+ }
+ } else {
+ if (PopupMenuButton("Resume", ICON_FA_CIRCLE_PLAY)) {
+ ActionTogglePause();
+ refocus_first_item = true;
+ }
+ }
+ if (PopupMenuButton("Screenshot", ICON_FA_CAMERA)) {
+ ActionScreenshot();
+ pop = true;
+ }
+ if (PopupMenuButton("Eject Disc", ICON_FA_EJECT)) {
+ ActionEjectDisc();
+ pop = true;
+ }
+ if (PopupMenuButton("Load Disc...", ICON_FA_COMPACT_DISC)) {
+ ActionLoadDisc();
+ pop = true;
+ }
+ if (PopupMenuSubmenuButton("Settings", ICON_FA_GEARS)) {
+ nav.PushFocus();
+ nav.PushMenu(settings);
+ }
+ if (PopupMenuButton("Restart", ICON_FA_ARROWS_ROTATE)) {
+ ActionReset();
+ pop = true;
+ }
+ if (PopupMenuButton("Exit", ICON_FA_POWER_OFF)) {
+ ActionShutdown();
+ pop = true;
+ }
+
+ if (m_pop_focus) {
+ nav.PopFocus();
+ }
+
+ return pop;
+ }
+};
+
+RootPopupMenu root_menu;
+
+void PopupMenuScene::PushMenu(PopupMenu &menu)
+{
+ menu.Show(m_view_stack.size() ? EASE_VECTOR_LEFT : EASE_VECTOR_DOWN);
+ m_menus_in_transition.push_back(&menu);
+
+ if (m_view_stack.size()) {
+ auto current = m_view_stack.back();
+ m_menus_in_transition.push_back(current);
+ current->Hide(EASE_VECTOR_RIGHT);
+ }
+
+ m_view_stack.push_back(&menu);
+}
+
+void PopupMenuScene::PopMenu()
+{
+ if (!m_view_stack.size()) {
+ return;
+ }
+
+ if (m_view_stack.size() > 1) {
+ auto previous = m_view_stack[m_view_stack.size() - 2];
+ previous->Show(EASE_VECTOR_RIGHT);
+ previous->InitFocus();
+ m_menus_in_transition.push_back(previous);
+ }
+
+ auto current = m_view_stack.back();
+ m_view_stack.pop_back();
+ current->Hide(m_view_stack.size() ? EASE_VECTOR_LEFT : EASE_VECTOR_DOWN);
+ m_menus_in_transition.push_back(current);
+
+ if (!m_view_stack.size()) {
+ Hide();
+ }
+}
+
+void PopupMenuScene::PushFocus()
+{
+ ImGuiContext *g = ImGui::GetCurrentContext();
+ m_focus_stack.push_back(std::pair(g->LastItemData.ID,
+ g->LastItemData.Rect));
+}
+
+void PopupMenuScene::PopFocus()
+{
+ auto next_focus = m_focus_stack.back();
+ m_focus_stack.pop_back();
+ ImGuiContext *g = ImGui::GetCurrentContext();
+ g->NavInitRequest = false;
+ g->NavInitResultId = next_focus.first;
+ g->NavInitResultRectRel = ImGui::WindowRectAbsToRel(g->CurrentWindow,
+ next_focus.second);
+ // ImGui::NavUpdateAnyRequestFlag();
+ g->NavAnyRequest = g->NavMoveScoringItems || g->NavInitRequest;// || (IMGUI_DEBUG_NAV_SCORING && g->NavWindow != NULL);
+}
+
+void PopupMenuScene::ClearMenuStack()
+{
+ if (m_view_stack.size()) {
+ auto current = m_view_stack.back();
+ current->Hide(EASE_VECTOR_DOWN);
+ m_menus_in_transition.push_back(current);
+ }
+ m_view_stack.clear();
+ m_focus_stack.clear();
+ Hide();
+}
+
+void PopupMenuScene::HandleInput()
+{
+ if (IsNavInputPressed(ImGuiNavInput_Cancel)) {
+ PopMenu();
+ }
+}
+
+void PopupMenuScene::Show()
+{
+ m_background.Show();
+ m_nav_control_view.Show();
+ // m_big_state_icon.Show();
+ // m_title_info.Show();
+
+ if (m_view_stack.size() == 0) {
+ PushMenu(root_menu);
+ }
+}
+
+void PopupMenuScene::Hide()
+{
+ m_background.Hide();
+ m_nav_control_view.Hide();
+ // m_big_state_icon.Hide();
+ // m_title_info.Hide();
+}
+
+bool PopupMenuScene::IsAnimating()
+{
+ return m_menus_in_transition.size() > 0 ||
+ m_background.IsAnimating() ||
+ m_nav_control_view.IsAnimating();
+ // m_big_state_icon.IsAnimating() ||
+ // m_title_info.IsAnimating();
+}
+
+bool PopupMenuScene::Draw()
+{
+ m_background.Draw();
+ // m_big_state_icon.Draw();
+ // m_title_info.Draw();
+
+ bool displayed = false;
+ while (m_menus_in_transition.size()) {
+ auto current = m_menus_in_transition.back();
+ if (current->IsAnimating()) {
+ current->Draw(*this);
+ displayed = true;
+ break;
+ }
+ m_menus_in_transition.pop_back();
+ }
+
+ if (!displayed) {
+ if (m_view_stack.size()) {
+ m_view_stack.back()->Draw(*this);
+ HandleInput();
+ displayed = true;
+ }
+ }
+
+ m_nav_control_view.Draw();
+ return displayed || IsAnimating();
+}
+
+void PopupMenuScene::LostFocus()
+{
+ ClearMenuStack();
+}
+
+PopupMenuScene g_popup_menu;
diff --git a/ui/xui/popup-menu.hh b/ui/xui/popup-menu.hh
new file mode 100644
index 0000000000..f5556ca5e5
--- /dev/null
+++ b/ui/xui/popup-menu.hh
@@ -0,0 +1,85 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include "common.hh"
+#include "scene.hh"
+#include "scene-components.hh"
+#include "animation.hh"
+#include "widgets.hh"
+
+class PopupMenu;
+
+class PopupMenuItemDelegate
+{
+public:
+ PopupMenuItemDelegate() = default;
+ virtual ~PopupMenuItemDelegate();
+ virtual void PushMenu(PopupMenu &menu);
+ virtual void PopMenu();
+ virtual void ClearMenuStack();
+ virtual void LostFocus();
+ virtual void PushFocus();
+ virtual void PopFocus();
+ virtual bool DidPop();
+};
+
+class PopupMenu
+{
+protected:
+ EasingAnimation m_animation;
+ ImVec2 m_ease_direction;
+ bool m_focus;
+ bool m_pop_focus;
+
+public:
+ PopupMenu();
+ void InitFocus();
+ virtual ~PopupMenu();
+ void Show(const ImVec2 &direction);
+ void Hide(const ImVec2 &direction);
+ bool IsAnimating();
+ void Draw(PopupMenuItemDelegate &nav);
+ virtual bool DrawItems(PopupMenuItemDelegate &nav);
+};
+
+class PopupMenuScene : virtual public PopupMenuItemDelegate, public Scene {
+protected:
+ std::vector m_view_stack;
+ std::vector m_menus_in_transition;
+ std::vector> m_focus_stack;
+ BackgroundGradient m_background;
+ NavControlAnnotation m_nav_control_view;
+ // BigStateIcon m_big_state_icon;
+ // TitleInfo m_title_info;
+
+public:
+ void PushMenu(PopupMenu &menu) override;
+ void PopMenu() override;
+ void PushFocus() override;
+ void PopFocus() override;
+ void ClearMenuStack() override;
+ void HandleInput();
+ void Show() override;
+ void Hide() override;
+ bool IsAnimating() override;
+ bool Draw() override;
+ void LostFocus() override;
+};
+
+extern PopupMenuScene g_popup_menu;
diff --git a/ui/xemu-reporting.cc b/ui/xui/reporting.cc
similarity index 75%
rename from ui/xemu-reporting.cc
rename to ui/xui/reporting.cc
index 5b946d2f64..dc14647572 100644
--- a/ui/xemu-reporting.cc
+++ b/ui/xui/reporting.cc
@@ -1,31 +1,30 @@
-/*
- * xemu Reporting
- *
- * Title compatibility and bug report submission.
- *
- * Copyright (C) 2020-2021 Matt Borgerson
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
+//
+// xemu Reporting
+//
+// Title compatibility and bug report submission.
+//
+// Copyright (C) 2020-2021 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
#include
#include
#include
-#include "xemu-reporting.h"
+#include "reporting.hh"
#define CPPHTTPLIB_OPENSSL_SUPPORT 1
-#include "httplib.h"
-#include "json.hpp"
+#include
+#include
using json = nlohmann::json;
#define DEBUG_COMPAT_SERVICE 0
diff --git a/ui/xemu-reporting.h b/ui/xui/reporting.hh
similarity index 52%
rename from ui/xemu-reporting.h
rename to ui/xui/reporting.hh
index 383bb2a615..f823bc4c3c 100644
--- a/ui/xemu-reporting.h
+++ b/ui/xui/reporting.hh
@@ -1,26 +1,23 @@
-/*
- * xemu Reporting
- *
- * Title compatibility and bug report submission.
- *
- * Copyright (C) 2020-2021 Matt Borgerson
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-#ifndef XEMU_REPORTING_H
-#define XEMU_REPORTING_H
+//
+// xemu Reporting
+//
+// Title compatibility and bug report submission.
+//
+// Copyright (C) 2020-2021 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+#pragma once
#include
#include
@@ -59,5 +56,3 @@ public:
const std::string &GetSerializedReport();
void SetXbeData(struct xbe *xbe);
};
-
-#endif
diff --git a/ui/xui/scene-components.cc b/ui/xui/scene-components.cc
new file mode 100644
index 0000000000..35e2434430
--- /dev/null
+++ b/ui/xui/scene-components.cc
@@ -0,0 +1,278 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "scene-components.hh"
+#include "common.hh"
+#include "misc.hh"
+#include "font-manager.hh"
+#include "input-manager.hh"
+#include "viewport-manager.hh"
+
+BackgroundGradient::BackgroundGradient()
+: m_animation(0.2, 0.2) {}
+
+void BackgroundGradient::Show()
+{
+ m_animation.EaseIn();
+}
+
+void BackgroundGradient::Hide()
+{
+ m_animation.EaseOut();
+}
+
+bool BackgroundGradient::IsAnimating()
+{
+ return m_animation.IsAnimating();
+}
+
+void BackgroundGradient::Draw()
+{
+ m_animation.Step();
+
+ float a = m_animation.GetSinInterpolatedValue();
+ ImU32 top_color = ImGui::GetColorU32(ImVec4(0,0,0,a));
+ ImU32 bottom_color = ImGui::GetColorU32(ImVec4(0,0,0,fmax(0, fmin(a-0.125, 0.125))));
+
+ ImGuiIO &io = ImGui::GetIO();
+ auto dl = ImGui::GetBackgroundDrawList();
+ dl->AddRectFilledMultiColor(ImVec2(0, 0), io.DisplaySize, top_color, top_color, bottom_color, bottom_color);
+}
+
+NavControlItem::NavControlItem(std::string icon, std::string text)
+: m_icon(icon), m_text(text) {}
+
+void NavControlItem::Draw()
+{
+ ImGui::PushFont(g_font_mgr.m_menu_font_small);
+ auto text = string_format("%s %s", m_icon.c_str(), m_text.c_str());
+ ImGui::Text("%s", text.c_str());
+ ImGui::PopFont();
+}
+
+NavControlAnnotation::NavControlAnnotation()
+: m_animation(0.12,0.12)
+{
+ m_show = false;
+ m_visible = false;
+
+ // FIXME: Based on controller input type, display different icons. Currently
+ // only showing Xbox scheme
+ // FIXME: Support configuration of displayed items
+ m_items.push_back(NavControlItem(ICON_BUTTON_A, "SELECT"));
+ m_items.push_back(NavControlItem(ICON_BUTTON_B, "BACK"));
+}
+
+void NavControlAnnotation::Show()
+{
+ m_show = true;
+}
+
+void NavControlAnnotation::Hide()
+{
+ m_show = false;
+}
+
+bool NavControlAnnotation::IsAnimating()
+{
+ return m_animation.IsAnimating();
+}
+
+void NavControlAnnotation::Draw()
+{
+ if (g_input_mgr.IsNavigatingWithController() && m_show && !m_visible) {
+ m_animation.EaseIn();
+ m_visible = true;
+ } else if ((!g_input_mgr.IsNavigatingWithController() || !m_show) &&
+ m_visible) {
+ m_animation.EaseOut();
+ m_visible = false;
+ }
+
+ m_animation.Step();
+ ImGuiIO &io = ImGui::GetIO();
+ ImGui::SetNextWindowBgAlpha(0);
+ ImGui::SetNextWindowPos(
+ ImVec2(io.DisplaySize.x - g_viewport_mgr.GetExtents().z,
+ io.DisplaySize.y - g_viewport_mgr.GetExtents().w),
+ ImGuiCond_Always, ImVec2(1, 1));
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha,
+ m_animation.GetSinInterpolatedValue());
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
+ ImGui::PushStyleVar(ImGuiStyleVar_ItemSpacing, ImVec2(30, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0.5));
+ if (ImGui::Begin("###NavControlAnnotation", NULL,
+ ImGuiWindowFlags_NoDecoration |
+ ImGuiWindowFlags_AlwaysAutoResize |
+ ImGuiWindowFlags_NoSavedSettings |
+ ImGuiWindowFlags_NoFocusOnAppearing |
+ ImGuiWindowFlags_NoInputs)) {
+ int i = 0;
+ for (auto &button : m_items) {
+ if (i++) ImGui::SameLine();
+ button.Draw();
+ }
+ }
+ ImGui::End();
+ ImGui::PopStyleVar(6);
+}
+
+#if 0
+class BigStateIcon {
+protected:
+ EasingAnimation m_animation;
+
+public:
+ BigStateIcon()
+ : m_animation(0.5, 0.15)
+ {
+ }
+
+ void Show() {
+ m_animation.easeIn();
+ }
+
+ void Hide() {
+ m_animation.easeOut();
+ }
+
+ bool IsAnimating()
+ {
+ return m_animation.IsAnimating();
+ }
+
+ void Draw()
+ {
+ m_animation.step();
+ ImGuiIO &io = ImGui::GetIO();
+ ImGui::SetNextWindowBgAlpha(0);
+ ImGui::SetNextWindowPos(ImVec2(io.DisplaySize.x - g_viewport_mgr.getExtents().z, g_viewport_mgr.getExtents().y),
+ ImGuiCond_Always, ImVec2(1, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_animation.getSinInterpolatedValue());
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10*g_viewport_mgr.m_scale, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
+ if (ImGui::Begin("###BigStateIcon", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) {
+ ImGui::PushFont(g_font_mgr.m_bigStateIconFont);
+ ImGui::Text("%s", ICON_FA_PAUSE);
+ ImGui::PopFont();
+ }
+ ImGui::End();
+ ImGui::PopStyleVar(4);
+ }
+};
+
+class TitleInfo
+{
+protected:
+ GLuint screenshot;
+ ImVec2 size;
+ EasingAnimation m_animation;
+
+public:
+ TitleInfo()
+ : m_animation(0.2, 0.2)
+ {
+ screenshot = 0;
+ }
+
+ void Show()
+ {
+ m_animation.easeIn();
+ }
+
+ void Hide()
+ {
+ m_animation.easeOut();
+ }
+
+ bool IsAnimating()
+ {
+ return m_animation.IsAnimating();
+ }
+
+ void initScreenshot()
+ {
+ if (screenshot == 0) {
+ glGenTextures(1, &screenshot);
+ int w, h, n;
+ stbi_set_flip_vertically_on_load(0);
+ unsigned char *data = stbi_load("./data/cover_front.jpg", &w, &h, &n, 4);
+ assert(data);
+ assert(n == 4 || n == 3);
+ glActiveTexture(GL_TEXTURE0);
+ glBindTexture(GL_TEXTURE_2D, screenshot);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_BORDER);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
+ stbi_image_free(data);
+
+ // Fix width
+ float width = 100;
+ float height = width*h/w;
+ size = ImVec2(width, height);
+ }
+ }
+
+ void Draw()
+ {
+ initScreenshot();
+ m_animation.step();
+
+ ImGui::SetNextWindowSize(g_viewport_mgr.scale(ImVec2(600, 600)));
+ ImGui::SetNextWindowBgAlpha(0);
+ ImGui::SetNextWindowPos(ImVec2(g_viewport_mgr.getExtents().x,
+ g_viewport_mgr.getExtents().y),
+ ImGuiCond_Always);
+ ImGui::PushStyleVar(ImGuiStyleVar_Alpha, m_animation.getSinInterpolatedValue());
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(10, 0));
+ ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0);
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0.5));
+ ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, g_viewport_mgr.m_scale*6);
+ if (ImGui::Begin("###TitleInfo", NULL, ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings)) {
+ ImGui::Columns(2, NULL, false);
+ ImGuiStyle &style = ImGui::GetStyle();
+ ImVec2 scaled_size = g_viewport_mgr.scale(size);
+ ImGui::SetColumnWidth(0, scaled_size.x + style.ItemSpacing.x);
+ ImGui::Dummy(scaled_size);
+ ImVec2 p0 = ImGui::GetItemRectMin();
+ ImVec2 p1 = ImGui::GetItemRectMax();
+ ImDrawList *draw_list = ImGui::GetWindowDrawList();
+ draw_list->AddImageRounded((ImTextureID)screenshot, p0, p1, ImVec2(0, 0), ImVec2(1, 1), ImGui::GetColorU32(ImVec4(1,1,1,m_animation.getSinInterpolatedValue())), 3*g_viewport_mgr.m_scale);
+
+ ImGui::NextColumn();
+
+ ImGui::PushFont(g_font_mgr.m_menuFont);
+ ImGui::Text("Halo: Combat Evolved");
+ ImGui::PopFont();
+ ImGui::PushFont(g_font_mgr.m_menuFontSmall);
+ ImGui::Text("NTSC MS-004");
+ ImGui::PopFont();
+ ImGui::Columns(1);
+ }
+ ImGui::End();
+ ImGui::PopStyleVar(6);
+ }
+};
+#endif
diff --git a/ui/xui/scene-components.hh b/ui/xui/scene-components.hh
new file mode 100644
index 0000000000..d5b86e7c54
--- /dev/null
+++ b/ui/xui/scene-components.hh
@@ -0,0 +1,91 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include
+#include
+#include "animation.hh"
+
+class BackgroundGradient
+{
+protected:
+ EasingAnimation m_animation;
+
+public:
+ BackgroundGradient();
+ void Show();
+ void Hide();
+ bool IsAnimating();
+ void Draw();
+};
+
+class NavControlItem
+{
+protected:
+ std::string m_icon;
+ std::string m_text;
+
+public:
+ NavControlItem(std::string icon, std::string text);
+ void Draw();
+};
+
+class NavControlAnnotation
+{
+protected:
+ EasingAnimation m_animation;
+ std::vector m_items;
+ bool m_show, m_visible;
+
+public:
+ NavControlAnnotation();
+ void Show();
+ void Hide();
+ bool IsAnimating();
+ void Draw();
+};
+
+#if 0
+class BigStateIcon {
+protected:
+ EasingAnimation m_animation;
+
+public:
+ BigStateIcon();
+ void Show();
+ void Hide();
+ bool IsAnimating();
+ void Draw();
+};
+
+class TitleInfo
+{
+protected:
+ GLuint screenshot;
+ ImVec2 size;
+ EasingAnimation m_animation;
+
+public:
+ TitleInfo();
+ void Show();
+ void Hide();
+ bool IsAnimating();
+ void initScreenshot();
+ void Draw();
+};
+#endif
diff --git a/ui/xui/scene-manager.cc b/ui/xui/scene-manager.cc
new file mode 100644
index 0000000000..ef938a0f8f
--- /dev/null
+++ b/ui/xui/scene-manager.cc
@@ -0,0 +1,52 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "scene-manager.hh"
+
+SceneManager g_scene_mgr;
+
+SceneManager::SceneManager()
+{
+ m_active_scene = nullptr;
+}
+
+void SceneManager::PushScene(Scene &scene)
+{
+ m_scenes.insert(m_scenes.begin(), &scene);
+}
+
+bool SceneManager::IsDisplayingScene()
+{
+ return m_active_scene != nullptr || m_scenes.size() > 0;
+}
+
+bool SceneManager::Draw()
+{
+ if (m_active_scene) {
+ bool finished = !m_active_scene->Draw();
+ if (finished) {
+ m_active_scene = nullptr;
+ }
+ return true;
+ } else if (m_scenes.size()) {
+ m_active_scene = m_scenes.back();
+ m_scenes.pop_back();
+ m_active_scene->Show();
+ }
+ return false;
+}
diff --git a/ui/xui/scene-manager.hh b/ui/xui/scene-manager.hh
new file mode 100644
index 0000000000..776c896caa
--- /dev/null
+++ b/ui/xui/scene-manager.hh
@@ -0,0 +1,36 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include
+#include "scene.hh"
+
+class SceneManager
+{
+protected:
+ Scene *m_active_scene;
+ std::vector m_scenes;
+
+public:
+ SceneManager();
+ void PushScene(Scene &scene);
+ bool IsDisplayingScene();
+ bool Draw();
+};
+
+extern SceneManager g_scene_mgr;
diff --git a/ui/xui/scene.cc b/ui/xui/scene.cc
new file mode 100644
index 0000000000..357a54db2b
--- /dev/null
+++ b/ui/xui/scene.cc
@@ -0,0 +1,25 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "scene.hh"
+
+Scene::~Scene() {}
+void Scene::Show() {}
+void Scene::Hide() {}
+bool Scene::IsAnimating() { return false; }
+bool Scene::Draw() { return false; }
diff --git a/ui/xui/scene.hh b/ui/xui/scene.hh
new file mode 100644
index 0000000000..665c76d321
--- /dev/null
+++ b/ui/xui/scene.hh
@@ -0,0 +1,30 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+
+class Scene
+{
+public:
+ Scene() = default;
+ virtual ~Scene();
+ virtual void Show();
+ virtual void Hide();
+ virtual bool IsAnimating();
+ virtual bool Draw();
+};
diff --git a/ui/xui/update.cc b/ui/xui/update.cc
new file mode 100644
index 0000000000..306ec7dfeb
--- /dev/null
+++ b/ui/xui/update.cc
@@ -0,0 +1,276 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "common.hh"
+#include "update.hh"
+#include "viewport-manager.hh"
+#include
+#include
+#include
+#include "util/miniz/miniz.h"
+#include "xemu-version.h"
+
+#if defined(_WIN32)
+const char *version_host = "raw.githubusercontent.com";
+const char *version_uri = "/mborgerson/xemu/ppa-snapshot/XEMU_VERSION";
+const char *download_host = "github.com";
+const char *download_uri = "/mborgerson/xemu/releases/latest/download/xemu-win-release.zip";
+#else
+FIXME
+#endif
+
+#define CPPHTTPLIB_OPENSSL_SUPPORT 1
+#include
+
+#define DPRINTF(fmt, ...) fprintf(stderr, fmt, ##__VA_ARGS__);
+
+AutoUpdateWindow update_window;
+
+AutoUpdateWindow::AutoUpdateWindow()
+{
+ is_open = false;
+}
+
+void AutoUpdateWindow::CheckForUpdates()
+{
+ updater.check_for_update([this](){
+ is_open |= updater.is_update_available();
+ });
+}
+
+void AutoUpdateWindow::Draw()
+{
+ if (!is_open) return;
+ ImGui::SetNextWindowContentSize(ImVec2(550.0f*g_viewport_mgr.m_scale, 0.0f));
+ if (!ImGui::Begin("Update", &is_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize)) {
+ ImGui::End();
+ return;
+ }
+
+ if (ImGui::IsWindowAppearing() && !updater.is_update_available()) {
+ updater.check_for_update();
+ }
+
+ const char *status_msg[] = {
+ "",
+ "An error has occured. Try again.",
+ "Checking for update...",
+ "Downloading update...",
+ "Update successful! Restart to launch updated version of xemu."
+ };
+ const char *available_msg[] = {
+ "Update availability unknown.",
+ "This version of xemu is up to date.",
+ "An updated version of xemu is available!",
+ };
+
+ if (updater.get_status() == UPDATER_IDLE) {
+ ImGui::Text(available_msg[updater.get_update_availability()]);
+ } else {
+ ImGui::Text(status_msg[updater.get_status()]);
+ }
+
+ if (updater.is_updating()) {
+ ImGui::ProgressBar(updater.get_update_progress_percentage()/100.0f,
+ ImVec2(-1.0f, 0.0f));
+ }
+
+ ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
+ ImGui::Separator();
+ ImGui::Dummy(ImVec2(0.0f, ImGui::GetStyle().WindowPadding.y));
+
+ float w = (130)*g_viewport_mgr.m_scale;
+ float bw = w + (10)*g_viewport_mgr.m_scale;
+ ImGui::SetCursorPosX(ImGui::GetWindowWidth()-bw);
+
+ if (updater.is_checking_for_update() || updater.is_updating()) {
+ if (ImGui::Button("Cancel", ImVec2(w, 0))) {
+ updater.cancel();
+ }
+ } else {
+ if (updater.is_pending_restart()) {
+ if (ImGui::Button("Restart", ImVec2(w, 0))) {
+ updater.restart_to_updated();
+ }
+ } else if (updater.is_update_available()) {
+ if (ImGui::Button("Update", ImVec2(w, 0))) {
+ updater.update();
+ }
+ } else {
+ if (ImGui::Button("Check for Update", ImVec2(w, 0))) {
+ updater.check_for_update();
+ }
+ }
+ }
+
+ ImGui::End();
+}
+
+Updater::Updater()
+{
+ m_status = UPDATER_IDLE;
+ m_update_availability = UPDATE_AVAILABILITY_UNKNOWN;
+ m_update_percentage = 0;
+ m_latest_version = "Unknown";
+ m_should_cancel = false;
+}
+
+void Updater::check_for_update(UpdaterCallback on_complete)
+{
+ if (m_status == UPDATER_IDLE || m_status == UPDATER_ERROR) {
+ m_on_complete = on_complete;
+ qemu_thread_create(&m_thread, "update_worker",
+ &Updater::checker_thread_worker_func,
+ this, QEMU_THREAD_JOINABLE);
+ }
+}
+
+void *Updater::checker_thread_worker_func(void *updater)
+{
+ ((Updater *)updater)->check_for_update_internal();
+ return NULL;
+}
+
+void Updater::check_for_update_internal()
+{
+ httplib::SSLClient cli(version_host, 443);
+ cli.set_follow_location(true);
+ cli.set_timeout_sec(5);
+ auto res = cli.Get(version_uri, [this](uint64_t len, uint64_t total) {
+ m_update_percentage = len*100/total;
+ return !m_should_cancel;
+ });
+ if (m_should_cancel) {
+ m_should_cancel = false;
+ m_status = UPDATER_IDLE;
+ goto finished;
+ } else if (!res || res->status != 200) {
+ m_status = UPDATER_ERROR;
+ goto finished;
+ }
+
+ if (strcmp(xemu_version, res->body.c_str())) {
+ m_update_availability = UPDATE_AVAILABLE;
+ } else {
+ m_update_availability = UPDATE_NOT_AVAILABLE;
+ }
+
+ m_latest_version = res->body;
+ m_status = UPDATER_IDLE;
+finished:
+ if (m_on_complete) {
+ m_on_complete();
+ }
+}
+
+void Updater::update()
+{
+ if (m_status == UPDATER_IDLE || m_status == UPDATER_ERROR) {
+ m_status = UPDATER_UPDATING;
+ qemu_thread_create(&m_thread, "update_worker",
+ &Updater::update_thread_worker_func,
+ this, QEMU_THREAD_JOINABLE);
+ }
+}
+
+void *Updater::update_thread_worker_func(void *updater)
+{
+ ((Updater *)updater)->update_internal();
+ return NULL;
+}
+
+void Updater::update_internal()
+{
+ httplib::SSLClient cli(download_host, 443);
+ cli.set_follow_location(true);
+ cli.set_timeout_sec(5);
+ auto res = cli.Get(download_uri, [this](uint64_t len, uint64_t total) {
+ m_update_percentage = len*100/total;
+ return !m_should_cancel;
+ });
+
+ if (m_should_cancel) {
+ m_should_cancel = false;
+ m_status = UPDATER_IDLE;
+ return;
+ } else if (!res || res->status != 200) {
+ m_status = UPDATER_ERROR;
+ return;
+ }
+
+ mz_zip_archive zip;
+ mz_zip_zero_struct(&zip);
+ if (!mz_zip_reader_init_mem(&zip, res->body.data(), res->body.size(), 0)) {
+ DPRINTF("mz_zip_reader_init_mem failed\n");
+ m_status = UPDATER_ERROR;
+ return;
+ }
+
+ mz_uint num_files = mz_zip_reader_get_num_files(&zip);
+ for (mz_uint file_idx = 0; file_idx < num_files; file_idx++) {
+ mz_zip_archive_file_stat fstat;
+ if (!mz_zip_reader_file_stat(&zip, file_idx, &fstat)) {
+ DPRINTF("mz_zip_reader_file_stat failed for file #%d\n", file_idx);
+ goto errored;
+ }
+
+ if (fstat.m_filename[strlen(fstat.m_filename)-1] == '/') {
+ /* FIXME: mkdirs */
+ DPRINTF("FIXME: subdirs not handled yet\n");
+ goto errored;
+ }
+
+ char *dst_path = g_strdup_printf("%s%s", SDL_GetBasePath(), fstat.m_filename);
+ DPRINTF("extracting %s to %s\n", fstat.m_filename, dst_path);
+
+ if (!strcmp(fstat.m_filename, "xemu.exe")) {
+ // We cannot overwrite current executable, but we can move it
+ char *renamed_path = g_strdup_printf("%s%s", SDL_GetBasePath(), "xemu-previous.exe");
+ MoveFileExA(dst_path, renamed_path, MOVEFILE_REPLACE_EXISTING);
+ g_free(renamed_path);
+ }
+
+ if (!mz_zip_reader_extract_to_file(&zip, file_idx, dst_path, 0)) {
+ DPRINTF("mz_zip_reader_extract_to_file failed to create %s\n", dst_path);
+ g_free(dst_path);
+ goto errored;
+ }
+
+ g_free(dst_path);
+ }
+
+ m_status = UPDATER_UPDATE_SUCCESSFUL;
+ goto cleanup_zip;
+errored:
+ m_status = UPDATER_ERROR;
+cleanup_zip:
+ mz_zip_reader_end(&zip);
+}
+
+extern "C" {
+extern char **gArgv;
+}
+
+void Updater::restart_to_updated()
+{
+ char *target_exec = g_strdup_printf("%s%s", SDL_GetBasePath(), "xemu.exe");
+ DPRINTF("Restarting to updated executable %s\n", target_exec);
+ _execv(target_exec, gArgv);
+ DPRINTF("Launching updated executable failed\n");
+ exit(1);
+}
diff --git a/ui/xui/update.hh b/ui/xui/update.hh
new file mode 100644
index 0000000000..f77336b04d
--- /dev/null
+++ b/ui/xui/update.hh
@@ -0,0 +1,92 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#if defined(_WIN32)
+#include
+#include
+#include
+
+extern "C" {
+#include "qemu/osdep.h"
+#include "qemu-common.h"
+#include "qemu/thread.h"
+}
+
+typedef enum {
+ UPDATE_AVAILABILITY_UNKNOWN,
+ UPDATE_NOT_AVAILABLE,
+ UPDATE_AVAILABLE
+} UpdateAvailability;
+
+typedef enum {
+ UPDATER_IDLE,
+ UPDATER_ERROR,
+ UPDATER_CHECKING_FOR_UPDATE,
+ UPDATER_UPDATING,
+ UPDATER_UPDATE_SUCCESSFUL
+} UpdateStatus;
+
+using UpdaterCallback = std::function;
+
+class Updater {
+private:
+ UpdateAvailability m_update_availability;
+ int m_update_percentage;
+ QemuThread m_thread;
+ std::string m_latest_version;
+ bool m_should_cancel;
+ UpdateStatus m_status;
+ UpdaterCallback m_on_complete;
+
+public:
+ Updater();
+ UpdateStatus get_status() { return m_status; }
+ UpdateAvailability get_update_availability() { return m_update_availability; }
+ bool is_errored() { return m_status == UPDATER_ERROR; }
+ bool is_pending_restart() { return m_status == UPDATER_UPDATE_SUCCESSFUL; }
+ bool is_update_available() { return m_update_availability == UPDATE_AVAILABLE; }
+ bool is_checking_for_update() { return m_status == UPDATER_CHECKING_FOR_UPDATE; }
+ bool is_updating() { return m_status == UPDATER_UPDATING; }
+ std::string get_update_version() { return m_latest_version; }
+ void cancel() { m_should_cancel = true; }
+ void update();
+ void update_internal();
+ void check_for_update(UpdaterCallback on_complete = nullptr);
+ void check_for_update_internal();
+ int get_update_progress_percentage() { return m_update_percentage; }
+ static void *update_thread_worker_func(void *updater);
+ static void *checker_thread_worker_func(void *updater);
+ void restart_to_updated(void);
+};
+
+class AutoUpdateWindow
+{
+protected:
+ Updater updater;
+
+public:
+ bool is_open;
+
+ AutoUpdateWindow();
+ void CheckForUpdates();
+ void Draw();
+};
+
+extern AutoUpdateWindow update_window;
+#endif //_ WIN32
diff --git a/ui/xui/viewport-manager.cc b/ui/xui/viewport-manager.cc
new file mode 100644
index 0000000000..c45d7a8be4
--- /dev/null
+++ b/ui/xui/viewport-manager.cc
@@ -0,0 +1,89 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "viewport-manager.hh"
+
+ViewportManager g_viewport_mgr;
+
+ViewportManager::ViewportManager() {
+ m_scale = 1;
+ m_extents.x = 25 * m_scale; // Distance from Left
+ m_extents.y = 25 * m_scale; // '' Top
+ m_extents.z = 25 * m_scale; // '' Right
+ m_extents.w = 25 * m_scale; // '' Bottom
+}
+
+ImVec4 ViewportManager::GetExtents()
+{
+ return m_extents;
+}
+
+#if 0
+void ViewportManager::DrawExtents()
+{
+ ImGuiIO &io = ImGui::GetIO();
+ ImVec2 tl(m_extents.x, m_extents.y);
+ ImVec2 tr(io.DisplaySize.x - m_extents.z, m_extents.y);
+ ImVec2 br(io.DisplaySize.x - m_extents.z, io.DisplaySize.y - m_extents.w);
+ ImVec2 bl(m_extents.x, io.DisplaySize.y - m_extents.w);
+
+ auto dl = ImGui::GetForegroundDrawList();
+ ImU32 color = 0xffff00ff;
+ dl->AddLine(tl, tr, color, 2.0);
+ dl->AddLine(tr, br, color, 2.0);
+ dl->AddLine(br, bl, color, 2.0);
+ dl->AddLine(bl, tl, color, 2.0);
+ dl->AddLine(tl, br, color, 2.0);
+ dl->AddLine(bl, tr, color, 2.0);
+}
+#endif
+
+ImVec2 ViewportManager::Scale(const ImVec2 vec2)
+{
+ return ImVec2(vec2.x * m_scale, vec2.y * m_scale);
+}
+
+void ViewportManager::Update()
+{
+ ImGuiIO &io = ImGui::GetIO();
+
+ if (g_config.display.ui.auto_scale) {
+ if (io.DisplaySize.x > 1920) {
+ g_config.display.ui.scale = 2;
+ } else {
+ g_config.display.ui.scale = 1;
+ }
+ }
+
+ m_scale = g_config.display.ui.scale;
+
+ if (m_scale < 1) {
+ m_scale = 1;
+ } else if (m_scale > 2) {
+ m_scale = 2;
+ }
+
+ if (io.DisplaySize.x > 640*m_scale) {
+ m_extents.x = 25 * m_scale; // Distance from Left
+ m_extents.y = 25 * m_scale; // '' Top
+ m_extents.z = 25 * m_scale; // '' Right
+ m_extents.w = 25 * m_scale; // '' Bottom
+ } else {
+ m_extents = ImVec4(0,0,0,0);
+ }
+}
diff --git a/ui/xui/viewport-manager.hh b/ui/xui/viewport-manager.hh
new file mode 100644
index 0000000000..ca98e6163f
--- /dev/null
+++ b/ui/xui/viewport-manager.hh
@@ -0,0 +1,36 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include "common.hh"
+
+class ViewportManager
+{
+protected:
+ ImVec4 m_extents;
+
+public:
+ float m_scale;
+ ViewportManager();
+ ImVec4 GetExtents();
+ void DrawExtents();
+ ImVec2 Scale(const ImVec2 vec2);
+ void Update();
+};
+
+extern ViewportManager g_viewport_mgr;
diff --git a/ui/xui/welcome.cc b/ui/xui/welcome.cc
new file mode 100644
index 0000000000..837fe04af9
--- /dev/null
+++ b/ui/xui/welcome.cc
@@ -0,0 +1,99 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "ui/xui/viewport-manager.hh"
+#include "common.hh"
+#include "imgui.h"
+#include "viewport-manager.hh"
+#include "welcome.hh"
+#include "widgets.hh"
+#include "misc.hh"
+#include "gl-helpers.hh"
+#include "xemu-version.h"
+#include "main-menu.hh"
+
+FirstBootWindow::FirstBootWindow()
+{
+ is_open = false;
+}
+
+void FirstBootWindow::Draw()
+{
+ if (!is_open) return;
+
+ ImVec2 size(400*g_viewport_mgr.m_scale, 300*g_viewport_mgr.m_scale);
+ ImGuiIO& io = ImGui::GetIO();
+
+ ImVec2 window_pos = ImVec2((io.DisplaySize.x - size.x)/2, (io.DisplaySize.y - size.y)/2);
+ ImGui::SetNextWindowPos(window_pos, ImGuiCond_Always);
+
+ ImGui::SetNextWindowSize(size, ImGuiCond_Appearing);
+ if (!ImGui::Begin("First Boot", &is_open, ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoDecoration)) {
+ ImGui::End();
+ return;
+ }
+
+ static uint32_t time_start = 0;
+ if (ImGui::IsWindowAppearing()) {
+ time_start = SDL_GetTicks();
+ }
+ uint32_t now = SDL_GetTicks() - time_start;
+
+ ImGui::SetCursorPosY(ImGui::GetCursorPosY()-50*g_viewport_mgr.m_scale);
+ ImGui::SetCursorPosX((ImGui::GetWindowWidth()-256*g_viewport_mgr.m_scale)/2);
+
+ logo_fbo->Target();
+ ImTextureID id = (ImTextureID)(intptr_t)logo_fbo->Texture();
+ float t_w = 256.0;
+ float t_h = 256.0;
+ float x_off = 0;
+ ImGui::Image(id,
+ ImVec2((t_w-x_off)*g_viewport_mgr.m_scale, t_h*g_viewport_mgr.m_scale),
+ ImVec2(x_off/t_w, t_h/t_h),
+ ImVec2(t_w/t_w, 0));
+ if (ImGui::IsItemClicked()) {
+ time_start = SDL_GetTicks();
+ }
+ RenderLogo(now, 0x42e335ff, 0x42e335ff, 0x00000000);
+ logo_fbo->Restore();
+
+ ImGui::SetCursorPosY(ImGui::GetCursorPosY()-100*g_viewport_mgr.m_scale);
+ ImGui::SetCursorPosX(10*g_viewport_mgr.m_scale);
+ ImGui::Dummy(ImVec2(0,20*g_viewport_mgr.m_scale));
+
+ const char *msg = "Configure machine settings to get started";
+ ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2);
+ ImGui::Text("%s", msg);
+
+ ImGui::Dummy(ImVec2(0,20*g_viewport_mgr.m_scale));
+ ImGui::SetCursorPosX((ImGui::GetWindowWidth()-120*g_viewport_mgr.m_scale)/2);
+ ImGui::SetItemDefaultFocus();
+ if (ImGui::Button("Settings", ImVec2(120*g_viewport_mgr.m_scale, 0))) {
+ g_main_menu.ShowSystem();
+ g_config.general.show_welcome = false;
+ }
+ ImGui::Dummy(ImVec2(0,20*g_viewport_mgr.m_scale));
+
+ msg = "Visit https://xemu.app for more information";
+ ImGui::SetCursorPosX((ImGui::GetWindowWidth()-ImGui::CalcTextSize(msg).x)/2);
+ Hyperlink(msg, "https://xemu.app");
+
+ ImGui::End();
+}
+
+FirstBootWindow first_boot_window;
diff --git a/ui/xui/welcome.hh b/ui/xui/welcome.hh
new file mode 100644
index 0000000000..9c99613c86
--- /dev/null
+++ b/ui/xui/welcome.hh
@@ -0,0 +1,29 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+
+class FirstBootWindow
+{
+public:
+ bool is_open;
+ FirstBootWindow();
+ void Draw();
+};
+
+extern FirstBootWindow first_boot_window;
diff --git a/ui/xui/widgets.cc b/ui/xui/widgets.cc
new file mode 100644
index 0000000000..15d6565111
--- /dev/null
+++ b/ui/xui/widgets.cc
@@ -0,0 +1,506 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#include "widgets.hh"
+#include "misc.hh"
+#include "font-manager.hh"
+#include "viewport-manager.hh"
+#include "ui/xemu-os-utils.h"
+
+void Separator()
+{
+ // XXX: IDK. Maybe there's a better way to draw a separator ( ImGui::Separator() ) that cuts through window
+ // padding... Just grab the draw list and draw the line with outer clip rect
+
+ float thickness = 1 * g_viewport_mgr.m_scale;
+
+ ImGuiWindow *window = ImGui::GetCurrentWindow();
+ ImDrawList *draw_list = ImGui::GetWindowDrawList();
+ ImRect window_rect = window->Rect();
+ ImVec2 size = ImVec2(window_rect.GetWidth(), thickness);
+
+ ImVec2 p0(window_rect.Min.x, ImGui::GetCursorScreenPos().y);
+ ImVec2 p1(p0.x + size.x, p0.y);
+ ImGui::PushClipRect(window_rect.Min, window_rect.Max, false);
+ draw_list->AddLine(p0, p1, ImGui::GetColorU32(ImGuiCol_Separator), thickness);
+ ImGui::PopClipRect();
+ ImGui::Dummy(size);
+}
+
+void SectionTitle(const char *title)
+{
+ ImGui::Spacing();
+ ImGui::PushFont(g_font_mgr.m_menu_font_medium);
+ ImGui::Text("%s", title);
+ ImGui::PopFont();
+ Separator();
+}
+
+float GetWidgetTitleDescriptionHeight(const char *title,
+ const char *description)
+{
+ ImGui::PushFont(g_font_mgr.m_menu_font_medium);
+ float h = ImGui::GetFrameHeight();
+ ImGui::PopFont();
+
+ if (description) {
+ ImGuiStyle &style = ImGui::GetStyle();
+ h += style.ItemInnerSpacing.y;
+ ImGui::PushFont(g_font_mgr.m_default_font);
+ h += ImGui::GetTextLineHeight();
+ ImGui::PopFont();
+ }
+
+ return h;
+}
+
+void WidgetTitleDescription(const char *title, const char *description,
+ ImVec2 pos)
+{
+ ImDrawList *draw_list = ImGui::GetWindowDrawList();
+ ImGuiStyle &style = ImGui::GetStyle();
+
+ ImVec2 text_pos = pos;
+ text_pos.x += style.FramePadding.x;
+ text_pos.y += style.FramePadding.y;
+
+ ImGui::PushFont(g_font_mgr.m_menu_font_medium);
+ float title_height = ImGui::GetTextLineHeight();
+ draw_list->AddText(text_pos, ImGui::GetColorU32(ImGuiCol_Text), title);
+ ImGui::PopFont();
+
+ if (description) {
+ text_pos.y += title_height + style.ItemInnerSpacing.y;
+
+ ImGui::PushFont(g_font_mgr.m_default_font);
+ draw_list->AddText(text_pos, ImGui::GetColorU32(ImVec4(0.94f, 0.94f, 0.94f, 0.70f)), description);
+ ImGui::PopFont();
+ }
+}
+
+void WidgetTitleDescriptionItem(const char *str_id, const char *description)
+{
+ ImVec2 p = ImGui::GetCursorScreenPos();
+ ImVec2 size(ImGui::GetColumnWidth(),
+ GetWidgetTitleDescriptionHeight(str_id, description));
+ WidgetTitleDescription(str_id, description, p);
+
+ // XXX: Internal API
+ ImRect bb(p, ImVec2(p.x + size.x, p.y + size.y));
+ ImGui::ItemSize(size, 0.0f);
+ ImGui::ItemAdd(bb, 0);
+}
+
+float GetSliderRadius(ImVec2 size)
+{
+ return size.y * 0.5;
+}
+
+float GetSliderTrackXOffset(ImVec2 size)
+{
+ return GetSliderRadius(size);
+}
+
+float GetSliderTrackWidth(ImVec2 size)
+{
+ return size.x - GetSliderRadius(size) * 2;
+}
+
+float GetSliderValueForMousePos(ImVec2 mouse, ImVec2 pos, ImVec2 size)
+{
+ return (mouse.x - pos.x - GetSliderTrackXOffset(size)) /
+ GetSliderTrackWidth(size);
+}
+
+void DrawSlider(float v, bool hovered, ImVec2 pos, ImVec2 size)
+{
+ ImDrawList *draw_list = ImGui::GetWindowDrawList();
+
+ float radius = GetSliderRadius(size);
+ float rounding = size.y * 0.25;
+ float slot_half_height = size.y * 0.125;
+ const bool circular_grab = false;
+
+ ImU32 bg = hovered ? ImGui::GetColorU32(ImGuiCol_FrameBgActive)
+ : ImGui::GetColorU32(ImGuiCol_CheckMark);
+
+ ImVec2 pmid(pos.x + radius + v*(size.x - radius*2), pos.y + size.y / 2);
+ ImVec2 smin(pos.x + rounding, pmid.y - slot_half_height);
+ ImVec2 smax(pmid.x, pmid.y + slot_half_height);
+ draw_list->AddRectFilled(smin, smax, bg, rounding);
+
+ bg = hovered ? ImGui::GetColorU32(ImGuiCol_FrameBgHovered)
+ : ImGui::GetColorU32(ImGuiCol_FrameBg);
+
+ smin.x = pmid.x;
+ smax.x = pos.x + size.x - rounding;
+ draw_list->AddRectFilled(smin, smax, bg, rounding);
+
+ if (circular_grab) {
+ draw_list->AddCircleFilled(pmid, radius * 0.8, ImGui::GetColorU32(ImGuiCol_SliderGrab));
+ } else {
+ ImVec2 offs(radius*0.8, radius*0.8);
+ draw_list->AddRectFilled(pmid - offs, pmid + offs, ImGui::GetColorU32(ImGuiCol_SliderGrab), rounding);
+ }
+}
+
+void DrawToggle(bool enabled, bool hovered, ImVec2 pos, ImVec2 size)
+{
+ ImDrawList *draw_list = ImGui::GetWindowDrawList();
+
+ float radius = size.y * 0.5;
+ float rounding = size.y * 0.25;
+ float slot_half_height = size.y * 0.5;
+ const bool circular_grab = false;
+
+ ImU32 bg = hovered ? ImGui::GetColorU32(enabled ? ImGuiCol_FrameBgActive : ImGuiCol_FrameBgHovered)
+ : ImGui::GetColorU32(enabled ? ImGuiCol_CheckMark : ImGuiCol_FrameBg);
+
+ ImVec2 pmid(pos.x + radius + (int)enabled * (size.x - radius * 2), pos.y + size.y / 2);
+ ImVec2 smin(pos.x, pmid.y - slot_half_height);
+ ImVec2 smax(pos.x + size.x, pmid.y + slot_half_height);
+ draw_list->AddRectFilled(smin, smax, bg, rounding);
+
+ if (circular_grab) {
+ draw_list->AddCircleFilled(pmid, radius * 0.8, ImGui::GetColorU32(ImGuiCol_SliderGrab));
+ } else {
+ ImVec2 offs(radius*0.8, radius*0.8);
+ draw_list->AddRectFilled(pmid - offs, pmid + offs, ImGui::GetColorU32(ImGuiCol_SliderGrab), rounding);
+ }
+}
+
+bool Toggle(const char *str_id, bool *v, const char *description)
+{
+ ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
+
+ ImGuiStyle &style = ImGui::GetStyle();
+
+ ImGui::PushFont(g_font_mgr.m_menu_font_medium);
+ float title_height = ImGui::GetTextLineHeight();
+ ImGui::PopFont();
+
+ ImVec2 p = ImGui::GetCursorScreenPos();
+ ImVec2 bb(ImGui::GetColumnWidth(),
+ GetWidgetTitleDescriptionHeight(str_id, description));
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
+ ImGui::PushID(str_id);
+ bool status = ImGui::Button("###toggle_button", bb);
+ if (status) {
+ *v = !*v;
+ }
+ ImGui::PopID();
+ ImGui::PopStyleVar();
+ const ImVec2 p_min = ImGui::GetItemRectMin();
+ const ImVec2 p_max = ImGui::GetItemRectMax();
+
+ WidgetTitleDescription(str_id, description, p);
+
+ float toggle_height = title_height * 0.9;
+ ImVec2 toggle_size(toggle_height * 1.75, toggle_height);
+ ImVec2 toggle_pos(p_max.x - toggle_size.x - style.FramePadding.x,
+ p_min.y + (title_height - toggle_size.y)/2 + style.FramePadding.y);
+ DrawToggle(*v, ImGui::IsItemHovered(), toggle_pos, toggle_size);
+
+ ImGui::PopStyleColor();
+
+ return status;
+}
+
+void Slider(const char *str_id, float *v, const char *description)
+{
+ ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
+
+ ImGuiStyle &style = ImGui::GetStyle();
+ ImGuiWindow *window = ImGui::GetCurrentWindow();
+
+ ImGui::PushFont(g_font_mgr.m_menu_font_medium);
+ float title_height = ImGui::GetTextLineHeight();
+ ImGui::PopFont();
+
+ ImVec2 p = ImGui::GetCursorScreenPos();
+ ImVec2 size(ImGui::GetColumnWidth(),
+ GetWidgetTitleDescriptionHeight(str_id, description));
+ WidgetTitleDescription(str_id, description, p);
+
+ // XXX: Internal API
+ ImVec2 wpos = ImGui::GetCursorPos();
+ ImRect bb(p, ImVec2(p.x + size.x, p.y + size.y));
+ ImGui::ItemSize(size, 0.0f);
+ ImGui::ItemAdd(bb, 0);
+ ImGui::SetItemAllowOverlap();
+ ImGui::SameLine(0, 0);
+
+ ImVec2 slider_size(size.x * 0.4, title_height * 0.9);
+ ImVec2 slider_pos(bb.Max.x - slider_size.x - style.FramePadding.x,
+ p.y + (title_height - slider_size.y)/2 + style.FramePadding.y);
+
+ ImGui::SetCursorPos(ImVec2(wpos.x + size.x - slider_size.x - style.FramePadding.x,
+ wpos.y));
+
+ ImGui::InvisibleButton("###slider", slider_size, 0);
+
+
+ if (ImGui::IsItemHovered()) {
+ if (ImGui::IsKeyPressed(ImGuiKey_LeftArrow) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadLStickLeft) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadRStickLeft)) {
+ *v -= 0.05;
+ }
+ if (ImGui::IsKeyPressed(ImGuiKey_RightArrow) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadDpadRight) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadLStickRight) ||
+ ImGui::IsKeyPressed(ImGuiKey_GamepadRStickRight)) {
+ *v += 0.05;
+ }
+
+ if (
+ ImGui::IsKeyDown(ImGuiKey_LeftArrow) ||
+ ImGui::IsKeyDown(ImGuiKey_GamepadDpadLeft) ||
+ ImGui::IsKeyDown(ImGuiKey_GamepadLStickLeft) ||
+ ImGui::IsKeyDown(ImGuiKey_GamepadRStickLeft) ||
+ ImGui::IsKeyDown(ImGuiKey_RightArrow) ||
+ ImGui::IsKeyDown(ImGuiKey_GamepadDpadRight) ||
+ ImGui::IsKeyDown(ImGuiKey_GamepadLStickRight) ||
+ ImGui::IsKeyDown(ImGuiKey_GamepadRStickRight)
+ ) {
+ ImGui::NavMoveRequestCancel();
+ }
+ }
+
+ if (ImGui::IsItemActive()) {
+ ImVec2 mouse = ImGui::GetMousePos();
+ *v = GetSliderValueForMousePos(mouse, slider_pos, slider_size);
+ }
+ *v = fmax(0, fmin(*v, 1));
+ DrawSlider(*v, ImGui::IsItemHovered() || ImGui::IsItemActive(), slider_pos,
+ slider_size);
+
+ ImVec2 slider_max = ImVec2(slider_pos.x + slider_size.x, slider_pos.y + slider_size.y);
+ ImGui::RenderNavHighlight(ImRect(slider_pos, slider_max), window->GetID("###slider"));
+
+ ImGui::PopStyleColor();
+}
+
+bool FilePicker(const char *str_id, const char **buf, const char *filters,
+ bool dir)
+{
+ bool changed = false;
+
+ ImGui::PushStyleColor(ImGuiCol_Button, IM_COL32_BLACK_TRANS);
+ ImGuiStyle &style = ImGui::GetStyle();
+ ImVec2 p = ImGui::GetCursorScreenPos();
+ const char *desc = strlen(*buf) ? *buf : "(None Selected)";
+ ImVec2 bb(ImGui::GetColumnWidth(),
+ GetWidgetTitleDescriptionHeight(str_id, desc));
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0, 0));
+ ImGui::PushID(str_id);
+ bool status = ImGui::Button("###file_button", bb);
+ if (status) {
+ const char *new_path =
+ PausedFileOpen(dir ? NOC_FILE_DIALOG_DIR : NOC_FILE_DIALOG_OPEN,
+ filters, *buf, NULL);
+ if (new_path) {
+ free((void*)*buf);
+ *buf = strdup(new_path);
+ changed = true;
+ }
+ }
+ ImGui::PopID();
+ ImGui::PopStyleVar();
+
+ WidgetTitleDescription(str_id, desc, p);
+
+ const ImVec2 p0 = ImGui::GetItemRectMin();
+ const ImVec2 p1 = ImGui::GetItemRectMax();
+
+ ImDrawList *draw_list = ImGui::GetWindowDrawList();
+
+ ImGui::PushFont(g_font_mgr.m_menu_font);
+ const char *icon = dir ? ICON_FA_FOLDER : ICON_FA_FILE;
+ ImVec2 ts_icon = ImGui::CalcTextSize(icon);
+ draw_list->AddText(ImVec2(p1.x - style.FramePadding.x - ts_icon.x,
+ p0.y + (p1.y - p0.y - ts_icon.y) / 2),
+ ImGui::GetColorU32(ImGuiCol_Text), icon);
+ ImGui::PopFont();
+
+ ImGui::PopStyleColor();
+
+ return changed;
+}
+
+void DrawComboChevron()
+{
+ ImGui::PushFont(g_font_mgr.m_menu_font);
+ const ImVec2 p0 = ImGui::GetItemRectMin();
+ const ImVec2 p1 = ImGui::GetItemRectMax();
+ const char *icon = ICON_FA_CHEVRON_DOWN;
+ ImVec2 ts_icon = ImGui::CalcTextSize(icon);
+ ImGuiStyle &style = ImGui::GetStyle();
+ ImDrawList *draw_list = ImGui::GetWindowDrawList();
+ draw_list->AddText(ImVec2(p1.x - style.FramePadding.x - ts_icon.x,
+ p0.y + (p1.y - p0.y - ts_icon.y) / 2),
+ ImGui::GetColorU32(ImGuiCol_Text), icon);
+ ImGui::PopFont();
+}
+
+void PrepareComboTitleDescription(const char *label, const char *description,
+ float combo_size_ratio)
+{
+ float width = ImGui::GetColumnWidth();
+ ImVec2 pos = ImGui::GetCursorScreenPos();
+ ImVec2 size(width, GetWidgetTitleDescriptionHeight(label, description));
+ WidgetTitleDescription(label, description, pos);
+
+ ImVec2 wpos = ImGui::GetCursorPos();
+ ImRect bb(pos, ImVec2(pos.x + size.x, pos.y + size.y));
+ ImGui::ItemSize(size, 0.0f);
+ ImGui::ItemAdd(bb, 0);
+ ImGui::SetItemAllowOverlap();
+ ImGui::SameLine(0, 0);
+ float combo_width = width * combo_size_ratio;
+ ImGui::SetCursorPos(ImVec2(wpos.x + width - combo_width, wpos.y));
+}
+
+bool ChevronCombo(const char *label, int *current_item,
+ bool (*items_getter)(void *, int, const char **), void *data,
+ int items_count, const char *description)
+{
+ bool value_changed = false;
+ float combo_width = ImGui::GetColumnWidth();
+ if (*label != '#') {
+ float combo_size_ratio = 0.4;
+ PrepareComboTitleDescription(label, description, combo_size_ratio);
+ combo_width *= combo_size_ratio;
+ }
+
+ ImGuiContext& g = *GImGui;
+ ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(1, 0));
+
+ // Call the getter to obtain the preview string which is a parameter to BeginCombo()
+ const char* preview_value = NULL;
+ if (*current_item >= 0 && *current_item < items_count)
+ items_getter(data, *current_item, &preview_value);
+
+ ImGui::SetNextItemWidth(combo_width);
+ ImGui::PushFont(g_font_mgr.m_menu_font_small);
+ ImGui::PushID(label);
+ if (ImGui::BeginCombo("###chevron_combo", preview_value, ImGuiComboFlags_NoArrowButton)) {
+ // Display items
+ // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
+ for (int i = 0; i < items_count; i++)
+ {
+ ImGui::PushID(i);
+ const bool item_selected = (i == *current_item);
+ const char* item_text;
+ if (!items_getter(data, i, &item_text))
+ item_text = "*Unknown item*";
+ if (ImGui::Selectable(item_text, item_selected))
+ {
+ value_changed = true;
+ *current_item = i;
+ }
+ if (item_selected)
+ ImGui::SetItemDefaultFocus();
+ ImGui::PopID();
+ }
+
+ ImGui::EndCombo();
+
+ if (value_changed)
+ ImGui::MarkItemEdited(g.LastItemData.ID);
+ }
+ ImGui::PopID();
+ ImGui::PopFont();
+ DrawComboChevron();
+ ImGui::PopStyleVar();
+ return value_changed;
+}
+
+// Getter for the old Combo() API: "item1\0item2\0item3\0"
+static bool Items_SingleStringGetter(void* data, int idx, const char** out_text)
+{
+ // FIXME-OPT: we could pre-compute the indices to fasten this. But only 1 active combo means the waste is limited.
+ const char* items_separated_by_zeros = (const char*)data;
+ int items_count = 0;
+ const char* p = items_separated_by_zeros;
+ while (*p)
+ {
+ if (idx == items_count)
+ break;
+ p += strlen(p) + 1;
+ items_count++;
+ }
+ if (!*p)
+ return false;
+ if (out_text)
+ *out_text = p;
+ return true;
+}
+
+// Combo box helper allowing to pass all items in a single string literal holding multiple zero-terminated items "item1\0item2\0"
+bool ChevronCombo(const char* label, int* current_item, const char* items_separated_by_zeros, const char *description)
+{
+ int items_count = 0;
+ const char* p = items_separated_by_zeros; // FIXME-OPT: Avoid computing this, or at least only when combo is open
+ while (*p)
+ {
+ p += strlen(p) + 1;
+ items_count++;
+ }
+ bool value_changed = ChevronCombo(
+ label, current_item, Items_SingleStringGetter,
+ (void *)items_separated_by_zeros, items_count, description);
+ return value_changed;
+}
+
+void Hyperlink(const char *text, const char *url)
+{
+ ImColor col;
+ ImGui::Text("%s", text);
+ if (ImGui::IsItemHovered()) {
+ col = IM_COL32_WHITE;
+ ImGui::SetMouseCursor(ImGuiMouseCursor_Hand);
+ } else {
+ col = ImColor(127, 127, 127, 255);
+ }
+
+ ImVec2 max = ImGui::GetItemRectMax();
+ ImVec2 min = ImGui::GetItemRectMin();
+ min.x -= 1 * g_viewport_mgr.m_scale;
+ min.y = max.y;
+ max.x -= 1 * g_viewport_mgr.m_scale;
+ ImGui::GetWindowDrawList()->AddLine(min, max, col, 1.0 * g_viewport_mgr.m_scale);
+
+ if (ImGui::IsItemClicked()) {
+ xemu_open_web_browser(url);
+ }
+}
+
+void HelpMarker(const char* desc)
+{
+ ImGui::TextDisabled("(?)");
+ if (ImGui::IsItemHovered())
+ {
+ ImGui::BeginTooltip();
+ ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f);
+ ImGui::TextUnformatted(desc);
+ ImGui::PopTextWrapPos();
+ ImGui::EndTooltip();
+ }
+}
diff --git a/ui/xui/widgets.hh b/ui/xui/widgets.hh
new file mode 100644
index 0000000000..5ee99be815
--- /dev/null
+++ b/ui/xui/widgets.hh
@@ -0,0 +1,48 @@
+//
+// xemu User Interface
+//
+// Copyright (C) 2020-2022 Matt Borgerson
+//
+// This program is free software; you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation; either version 2 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+#pragma once
+#include "common.hh"
+
+void Separator();
+void SectionTitle(const char *title);
+float GetWidgetTitleDescriptionHeight(const char *title,
+ const char *description);
+void WidgetTitleDescription(const char *title, const char *description,
+ ImVec2 pos);
+void WidgetTitleDescriptionItem(const char *str_id,
+ const char *description = nullptr);
+float GetSliderRadius(ImVec2 size);
+float GetSliderTrackXOffset(ImVec2 size);
+float GetSliderTrackWidth(ImVec2 size);
+float GetSliderValueForMousePos(ImVec2 mouse, ImVec2 pos, ImVec2 size);
+void DrawSlider(float v, bool hovered, ImVec2 pos, ImVec2 size);
+void DrawToggle(bool enabled, bool hovered, ImVec2 pos, ImVec2 size);
+bool Toggle(const char *str_id, bool *v, const char *description = nullptr);
+void Slider(const char *str_id, float *v, const char *description = nullptr);
+bool FilePicker(const char *str_id, const char **buf, const char *filters,
+ bool dir = false);
+void DrawComboChevron();
+void PrepareComboTitleDescription(const char *label, const char *description,
+ float combo_size_ratio);
+bool ChevronCombo(const char *label, int *current_item,
+ bool (*items_getter)(void *, int, const char **), void *data,
+ int items_count, const char *description = NULL);
+bool ChevronCombo(const char* label, int* current_item, const char* items_separated_by_zeros, const char *description = NULL);
+void Hyperlink(const char *text, const char *url);
+void HelpMarker(const char* desc);
diff --git a/ui/xemu-hud.h b/ui/xui/xemu-hud.h
similarity index 95%
rename from ui/xemu-hud.h
rename to ui/xui/xemu-hud.h
index e9a50bfbb6..a510c85bf6 100644
--- a/ui/xemu-hud.h
+++ b/ui/xui/xemu-hud.h
@@ -30,7 +30,6 @@ extern "C" {
#endif
// Implemented in xemu.c
-extern int scaling_mode;
int xemu_is_fullscreen(void);
void xemu_monitor_init(void);
void xemu_toggle_fullscreen(void);
@@ -43,6 +42,7 @@ void xemu_hud_cleanup(void);
void xemu_hud_render(void);
void xemu_hud_process_sdl_events(SDL_Event *event);
void xemu_hud_should_capture_kbd_mouse(int *kbd, int *mouse);
+void xemu_hud_set_framebuffer_texture(GLuint tex, bool flip);
#ifdef __cplusplus
}