From 9c06980275b6b31fc9f1b7f7df9ac692dad508d8 Mon Sep 17 00:00:00 2001 From: Matt Borgerson Date: Tue, 3 May 2022 03:07:22 -0700 Subject: [PATCH] ui: Redesign user interface Introduces a new user interface that looks much nicer, is easier to navigate with controllers, provides more context to users, and is scalable. Some additional features are included. * Adds 'popup menu' with actions that can be used easily from controller * Adds 'main menu', unifying other configuration dialogs * Adds port-forwarding user interface * Adds screenshot feature * Adds volume control feature * Adds gamepad auto-bind option * Adds vsync configuration option * Adds auto UI scaling * Adds preferred window size selection * Adds AV pack selection * Exposes some existing config items in GUI --- .clang-format | 1 + .gitmodules | 8 +- build.sh | 5 - config_spec.yml | 56 +- configure | 2 +- data/{roboto_medium.ttf => Roboto-Medium.ttf} | Bin 171656 -> 168644 bytes data/RobotoCondensed-Regular.ttf | Bin 0 -> 166836 bytes data/abxy.svg | 156 + data/abxy.ttf | Bin 0 -> 1776 bytes data/abxy_font.svg | 161 + data/font_awesome_6_1_1_solid.otf | Bin 0 -> 975352 bytes data/meson.build | 5 +- genconfig | 2 +- hw/xbox/mcpx/apu.c | 42 +- licenses/fontawesome.license.txt | 165 + licenses/fpng.license.txt | 24 + licenses/tomlplusplus.license.txt | 16 + net/slirp.c | 9 + scripts/archive-source.sh | 2 +- scripts/gen-license.py | 23 +- softmmu/vl.c | 17 +- ui/imgui | 1 - ui/implot | 1 - ui/meson.build | 48 +- ui/thirdparty/fa/IconsFontAwesome6.h | 1393 +++++++ ui/thirdparty/fpng/HEAD | 1 + ui/thirdparty/fpng/fpng.cpp | 3222 +++++++++++++++++ ui/thirdparty/fpng/fpng.h | 122 + ui/{ => thirdparty/httplib}/httplib.h | 0 ui/thirdparty/imgui | 1 + .../imgui_impl_opengl3_loader_override.h | 1 + ui/thirdparty/implot | 1 + ui/{ => thirdparty/json}/json.hpp | 0 ui/thirdparty/meson.build | 63 + .../noc_file_dialog}/noc_file_dialog.h | 3 + .../noc_file_dialog}/noc_file_dialog_gtk.c | 0 .../noc_file_dialog}/noc_file_dialog_macos.m | 0 .../noc_file_dialog}/noc_file_dialog_win32.c | 0 ui/{ => thirdparty/stb_image}/stb_image.h | 475 ++- ui/thirdparty/stb_image/stb_image_impl.c | 2 + ui/xemu-custom-widgets.c | 311 -- ui/xemu-custom-widgets.h | 45 - ui/xemu-hud.cc | 2412 ------------ ui/xemu-input.c | 37 +- ui/xemu-net.c | 50 +- ui/xemu-os-utils.h | 39 +- ui/xemu-settings.cc | 28 + ui/xemu-settings.h | 3 + ui/xemu-shaders.c | 371 -- ui/xemu-shaders.h | 103 - ui/xemu-update.cc | 195 - ui/xemu-update.h | 80 - ui/xemu.c | 132 +- ui/xui/actions.cc | 65 + ui/xui/actions.hh | 26 + ui/xui/animation.cc | 161 + ui/xui/animation.hh | 73 + ui/xui/common.hh | 55 + ui/xui/compat.cc | 220 ++ ui/xui/compat.hh | 41 + ui/xui/debug.cc | 323 ++ ui/xui/debug.hh | 40 + ui/xui/font-manager.cc | 118 + ui/xui/font-manager.hh | 45 + ui/xui/gl-helpers.cc | 777 ++++ ui/xui/gl-helpers.hh | 50 + ui/xui/input-manager.cc | 116 + ui/xui/input-manager.hh | 20 + ui/xui/main-menu.cc | 1142 ++++++ ui/xui/main-menu.hh | 187 + ui/xui/main.cc | 306 ++ ui/xui/menubar.cc | 192 + ui/xui/menubar.hh | 22 + ui/xui/meson.build | 24 + ui/xui/misc.hh | 106 + ui/xui/monitor.cc | 155 + ui/xui/monitor.hh | 50 + ui/xui/notifications.cc | 155 + ui/xui/notifications.hh | 46 + ui/xui/popup-menu.cc | 511 +++ ui/xui/popup-menu.hh | 85 + ui/{xemu-reporting.cc => xui/reporting.cc} | 47 +- ui/{xemu-reporting.h => xui/reporting.hh} | 45 +- ui/xui/scene-components.cc | 278 ++ ui/xui/scene-components.hh | 91 + ui/xui/scene-manager.cc | 52 + ui/xui/scene-manager.hh | 36 + ui/xui/scene.cc | 25 + ui/xui/scene.hh | 30 + ui/xui/update.cc | 276 ++ ui/xui/update.hh | 92 + ui/xui/viewport-manager.cc | 89 + ui/xui/viewport-manager.hh | 36 + ui/xui/welcome.cc | 99 + ui/xui/welcome.hh | 29 + ui/xui/widgets.cc | 506 +++ ui/xui/widgets.hh | 48 + ui/{ => xui}/xemu-hud.h | 2 +- 98 files changed, 12880 insertions(+), 3846 deletions(-) rename data/{roboto_medium.ttf => Roboto-Medium.ttf} (97%) create mode 100644 data/RobotoCondensed-Regular.ttf create mode 100644 data/abxy.svg create mode 100644 data/abxy.ttf create mode 100644 data/abxy_font.svg create mode 100644 data/font_awesome_6_1_1_solid.otf create mode 100644 licenses/fontawesome.license.txt create mode 100644 licenses/fpng.license.txt create mode 100644 licenses/tomlplusplus.license.txt delete mode 160000 ui/imgui delete mode 160000 ui/implot create mode 100644 ui/thirdparty/fa/IconsFontAwesome6.h create mode 100644 ui/thirdparty/fpng/HEAD create mode 100644 ui/thirdparty/fpng/fpng.cpp create mode 100644 ui/thirdparty/fpng/fpng.h rename ui/{ => thirdparty/httplib}/httplib.h (100%) create mode 160000 ui/thirdparty/imgui create mode 100644 ui/thirdparty/imgui_impl_opengl3_loader_override.h create mode 160000 ui/thirdparty/implot rename ui/{ => thirdparty/json}/json.hpp (100%) create mode 100644 ui/thirdparty/meson.build rename ui/{ => thirdparty/noc_file_dialog}/noc_file_dialog.h (99%) rename ui/{ => thirdparty/noc_file_dialog}/noc_file_dialog_gtk.c (100%) rename ui/{ => thirdparty/noc_file_dialog}/noc_file_dialog_macos.m (100%) rename ui/{ => thirdparty/noc_file_dialog}/noc_file_dialog_win32.c (100%) rename ui/{ => thirdparty/stb_image}/stb_image.h (93%) create mode 100644 ui/thirdparty/stb_image/stb_image_impl.c delete mode 100644 ui/xemu-custom-widgets.c delete mode 100644 ui/xemu-custom-widgets.h delete mode 100644 ui/xemu-hud.cc delete mode 100644 ui/xemu-shaders.c delete mode 100644 ui/xemu-shaders.h delete mode 100644 ui/xemu-update.cc delete mode 100644 ui/xemu-update.h create mode 100644 ui/xui/actions.cc create mode 100644 ui/xui/actions.hh create mode 100644 ui/xui/animation.cc create mode 100644 ui/xui/animation.hh create mode 100644 ui/xui/common.hh create mode 100644 ui/xui/compat.cc create mode 100644 ui/xui/compat.hh create mode 100644 ui/xui/debug.cc create mode 100644 ui/xui/debug.hh create mode 100644 ui/xui/font-manager.cc create mode 100644 ui/xui/font-manager.hh create mode 100644 ui/xui/gl-helpers.cc create mode 100644 ui/xui/gl-helpers.hh create mode 100644 ui/xui/input-manager.cc create mode 100644 ui/xui/input-manager.hh create mode 100644 ui/xui/main-menu.cc create mode 100644 ui/xui/main-menu.hh create mode 100644 ui/xui/main.cc create mode 100644 ui/xui/menubar.cc create mode 100644 ui/xui/menubar.hh create mode 100644 ui/xui/meson.build create mode 100644 ui/xui/misc.hh create mode 100644 ui/xui/monitor.cc create mode 100644 ui/xui/monitor.hh create mode 100644 ui/xui/notifications.cc create mode 100644 ui/xui/notifications.hh create mode 100644 ui/xui/popup-menu.cc create mode 100644 ui/xui/popup-menu.hh rename ui/{xemu-reporting.cc => xui/reporting.cc} (75%) rename ui/{xemu-reporting.h => xui/reporting.hh} (52%) create mode 100644 ui/xui/scene-components.cc create mode 100644 ui/xui/scene-components.hh create mode 100644 ui/xui/scene-manager.cc create mode 100644 ui/xui/scene-manager.hh create mode 100644 ui/xui/scene.cc create mode 100644 ui/xui/scene.hh create mode 100644 ui/xui/update.cc create mode 100644 ui/xui/update.hh create mode 100644 ui/xui/viewport-manager.cc create mode 100644 ui/xui/viewport-manager.hh create mode 100644 ui/xui/welcome.cc create mode 100644 ui/xui/welcome.hh create mode 100644 ui/xui/widgets.cc create mode 100644 ui/xui/widgets.hh rename ui/{ => xui}/xemu-hud.h (95%) 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 f714a514d94e495095e2f1e525a341eade187c17..e89b0b79a2910f0ac309a1845a9f733bcb568792 100644 GIT binary patch delta 224 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%0DobldLI6;RHmt=q(=g# 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Ð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 literal 0 HcmV?d00001 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 @@ + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + diff --git a/data/abxy.ttf b/data/abxy.ttf new file mode 100644 index 0000000000000000000000000000000000000000..d7b4b2f1d19b4b1fb809e2c6f87c835c22b94e53 GIT binary patch literal 1776 zcmds1&u<%55dPk-?RDGKaZ=k&)Fj@-O+u;T*t>}XQAnI9F{Dyx6=>=Rq~f@a9mzj} z?L_5P1QG`yT7d-O58zaUdf>nXP6+kTQH?I{7t4*CKA+-~wvjc1|2b`ge5dg??%``YYwMPBHv zYT9IddoeS%JpTBb;Jl{Lm-s$fc9PT(1ITAlrPKNvZ-5z&uFdTWX80a|D zkzEc3F35%}kI&MMb23|xOqRizs>)O+OpWO>Td}{^Y<}77icY@J=#O3;n0js?8jVaV zd(E|6vzZ%OoQ!sDXEL?=>T3O%Uq8D4ouAwC9E^+~P2|zv{iCD$Y|rtd*Xwt#UZsaN zyzksq!CQaQ;T34Cp?!x{L{N2j7rgk$;oUfeuN>Y({0I3LxlR!A(yz@c@S)w|Rmd4> z<1YB|p2NEl!Y2;zA^s4vm_r7u!0cRx-(}=cz!pl#qDC=a*eYeoq(?}>n6)xe{wkjLx zXX9~)CcJZ7jYhR{J=0=XF}Z^iOR>7sMk&N4CBfMy`z$5 + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + 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 0000000000000000000000000000000000000000..2452af583243d2040545fe78e60b689db0ce9bc4 GIT binary patch literal 975352 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±JO#wsmB{B5~(n!gMgji6NsWFXQu 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

5bX7LHfMO<9R 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 z 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 bem{!)3Kqfw(u?G}+uqO^`=Et=ljQ#cCg(Y# literal 0 HcmV?d00001 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 @@ zlib = 'zlib' lgplv2_1 = 'lgplv2_1' apache2 = 'apache2' +unlicense = 'unlicense' multi = 'multi' @@ -179,13 +180,13 @@ def head(self): 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 @@ def head(self): 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 @@ def head(self): 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 @@ def head(self): 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 }