diff --git a/.clang-format b/.clang-format index efa5f1c3c4..56804459cb 100644 --- a/.clang-format +++ b/.clang-format @@ -68,6 +68,7 @@ IncludeCategories: IncludeIsMainRegex: '$' IndentCaseLabels: false IndentWidth: 4 +AccessModifierOffset: -4 IndentWrappedFunctionNames: false KeepEmptyLinesAtTheStartOfBlocks: false MacroBlockBegin: '.*_BEGIN$' # only PREC_BEGIN ? diff --git a/.gitmodules b/.gitmodules index 7b390efe57..699d5526f7 100644 --- a/.gitmodules +++ b/.gitmodules @@ -64,12 +64,12 @@ [submodule "roms/vbootrom"] path = roms/vbootrom url = https://gitlab.com/qemu-project/vbootrom.git -[submodule "ui/imgui"] - path = ui/imgui +[submodule "ui/thirdparty/imgui"] + path = ui/thirdparty/imgui url = https://github.com/ocornut/imgui.git ignore = untracked -[submodule "ui/implot"] - path = ui/implot +[submodule "ui/thirdparty/implot"] + path = ui/thirdparty/implot url = https://github.com/epezent/implot.git ignore = untracked [submodule "hw/xbox/nv2a/xxHash"] diff --git a/build.sh b/build.sh index 0163be239d..10d40de3bc 100755 --- a/build.sh +++ b/build.sh @@ -272,11 +272,6 @@ set -x # Print commands from now on ${opts} \ "$@" -# Force imgui update now to work around annoying make issue -if ! test -f "${project_source_dir}/ui/imgui/imgui.cpp"; then - ./scripts/git-submodule.sh update ui/imgui -fi - time make -j"${job_count}" ${target} 2>&1 | tee build.log "${postbuild}" # call post build functions diff --git a/config_spec.yml b/config_spec.yml index d58b01e1f3..b1f7e9d538 100644 --- a/config_spec.yml +++ b/config_spec.yml @@ -6,8 +6,10 @@ general: check: type: bool default: true - misc: - skip_boot_anim: bool + screenshot_dir: string + skip_boot_anim: bool + # throttle_io: bool + last_viewed_menu_index: integer user_token: string input: @@ -16,6 +18,11 @@ input: port2: string port3: string port4: string + gamecontrollerdb_path: string + auto_bind: + type: bool + default: true + background_input_capture: bool display: quality: @@ -23,9 +30,27 @@ display: type: integer default: 1 window: - last_width: integer - last_height: integer + fullscreen_on_startup: bool + startup_size: + type: enum + values: [last_used, 640x480, 1280x720, 1280x800, 1280x960, 1920x1080, 2560x1440, 2560x1600, 2560x1920, 3840x2160] + default: 1280x960 + last_width: + type: integer + default: 640 + last_height: + type: integer + default: 480 + vsync: + type: bool + default: true ui: + show_menubar: + type: bool + default: true + use_animations: + type: bool + default: true fit: type: enum values: [center, scale, scale_16_9, scale_4_3, stretch] @@ -33,9 +58,15 @@ display: scale: type: integer default: 1 + auto_scale: + type: bool + default: true audio: use_dsp: bool + volume_limit: + type: number + default: 1 net: enable: bool @@ -52,12 +83,26 @@ net: remote_addr: type: string default: 1.2.3.4:9368 + nat: + forward_ports: + type: array + items: + host: integer + guest: integer + protocol: + type: enum + values: [tcp, udp] + default: tcp sys: mem_limit: type: enum values: ['64', '128'] default: '64' + avpack: + type: enum + values: [scart, hdtv, vga, rfu, svideo, composite, none] + default: hdtv files: bootrom_path: string flashrom_path: string @@ -69,3 +114,6 @@ perf: hard_fpu: type: bool default: true + # cache_shaders: + # type: bool + # default: true diff --git a/configure b/configure index f71e805409..34fea06dba 100755 --- a/configure +++ b/configure @@ -261,7 +261,7 @@ else git_submodules_action="ignore" fi -git_submodules="ui/keycodemapdb ui/imgui ui/implot util/xxHash" +git_submodules="ui/keycodemapdb ui/thirdparty/imgui ui/thirdparty/implot util/xxHash tomlplusplus genconfig" git="git" # Don't accept a target_list environment variable. diff --git a/data/roboto_medium.ttf b/data/Roboto-Medium.ttf similarity index 97% rename from data/roboto_medium.ttf rename to data/Roboto-Medium.ttf index f714a514d9..e89b0b79a2 100644 Binary files a/data/roboto_medium.ttf and b/data/Roboto-Medium.ttf differ diff --git a/data/RobotoCondensed-Regular.ttf b/data/RobotoCondensed-Regular.ttf new file mode 100644 index 0000000000..17e8ea57b1 Binary files /dev/null and b/data/RobotoCondensed-Regular.ttf differ diff --git a/data/abxy.svg b/data/abxy.svg new file mode 100644 index 0000000000..9adde380e1 --- /dev/null +++ b/data/abxy.svg @@ -0,0 +1,156 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + width="76.111221mm" + height="9.6190205mm" + viewBox="0 0 76.111221 9.6190205" + version="1.1" + id="svg986" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)" + sodipodi:docname="abxy.svg"> + <defs + id="defs980" /> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1.979899" + inkscape:cx="276.28204" + inkscape:cy="77.212294" + inkscape:document-units="mm" + inkscape:current-layer="g1622" + showgrid="false" + fit-margin-top="0" + fit-margin-left="0" + fit-margin-right="0" + fit-margin-bottom="0" + inkscape:window-width="1920" + inkscape:window-height="1043" + inkscape:window-x="0" + inkscape:window-y="0" + inkscape:window-maximized="1" /> + <metadata + id="metadata983"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + inkscape:label="Layer 1" + inkscape:groupmode="layer" + id="layer1" + transform="translate(-48.12296,-137.22026)" + style="display:none"> + <circle + style="opacity:0.9;fill:#1a1a1a;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" + id="path1694" + cx="63.995201" + cy="141.96429" + r="4.6772165" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;line-height:1.25;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="62.234642" + y="144.12724" + id="text1698"><tspan + sodipodi:role="line" + id="tspan1696" + x="62.234642" + y="144.12724" + style="stroke-width:0.26458332">A</tspan></text> + <circle + r="4.6772165" + cy="141.96429" + cx="79.870201" + id="circle1580" + style="opacity:0.9;fill:#1a1a1a;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" /> + <text + id="text1584" + y="144.12724" + x="78.109634" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;line-height:1.25;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="144.12724" + x="78.109634" + id="tspan1582" + sodipodi:role="line">B</tspan></text> + <circle + style="opacity:0.9;fill:#1a1a1a;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" + id="circle1586" + cx="92.041039" + cy="141.96429" + r="4.6772165" /> + <text + xml:space="preserve" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;line-height:1.25;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + x="90.280449" + y="144.12724" + id="text1590"><tspan + sodipodi:role="line" + id="tspan1588" + x="90.280449" + y="144.12724" + style="stroke-width:0.26458332">X</tspan></text> + <circle + r="4.6772165" + cy="141.96429" + cx="104.74105" + id="circle1592" + style="opacity:0.9;fill:#1a1a1a;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" /> + <text + id="text1596" + y="144.12724" + x="102.98046" + style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;font-size:6.3499999px;line-height:1.25;font-family:'Roboto Condensed';-inkscape-font-specification:'Roboto Condensed, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332" + xml:space="preserve"><tspan + style="stroke-width:0.26458332" + y="144.12724" + x="102.98046" + id="tspan1594" + sodipodi:role="line">Y</tspan></text> + </g> + <g + transform="translate(-48.12296,-137.22026)" + id="g1622" + inkscape:groupmode="layer" + inkscape:label="Layer 1 copy"> + <path + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" + d="m 52.800194,137.28692 a 4.6772165,4.6772165 0 0 0 -4.677234,4.67724 4.6772165,4.6772165 0 0 0 4.677234,4.67723 4.6772165,4.6772165 0 0 0 4.677234,-4.67723 4.6772165,4.6772165 0 0 0 -4.677234,-4.67724 z m -0.173116,2.32596 h 0.486792 l 1.457275,4.51445 h -0.58291 l -0.356567,-1.17822 H 52.10618 l -0.350367,1.17822 h -0.58291 z m 0.241846,0.79375 -0.613916,2.05259 h 1.230932 z" + id="circle1598" + inkscape:connector-curvature="0" /> + <path + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" + d="m 66.382135,137.28692 a 4.6772165,4.6772165 0 0 0 -4.677233,4.67724 4.6772165,4.6772165 0 0 0 4.677233,4.67723 4.6772165,4.6772165 0 0 0 4.677235,-4.67723 4.6772165,4.6772165 0 0 0 -4.677235,-4.67724 z m -1.273823,2.32596 h 1.286742 c 0.411345,0 0.720371,0.10025 0.927076,0.30076 0.208773,0.2005 0.313159,0.50126 0.313159,0.90227 0,0.21084 -0.05271,0.39687 -0.158129,0.5581 -0.10542,0.16123 -0.248046,0.28629 -0.427881,0.37517 0.206706,0.062 0.370004,0.18914 0.489892,0.38137 0.121957,0.19224 0.182935,0.42375 0.182935,0.69454 0,0.40514 -0.111622,0.72347 -0.334864,0.95498 -0.221173,0.23151 -0.537435,0.34726 -0.94878,0.34726 h -1.33015 z m 0.567406,0.48989 v 1.43557 h 0.728639 c 0.196371,0 0.354499,-0.0661 0.47439,-0.19844 0.121954,-0.13229 0.182933,-0.30799 0.182933,-0.52709 0,-0.24805 -0.05478,-0.42789 -0.16433,-0.53951 -0.109553,-0.11368 -0.276987,-0.17053 -0.502296,-0.17053 z m 0,1.91306 v 1.62471 h 0.775148 c 0.214973,0 0.385505,-0.0703 0.511595,-0.21084 0.126093,-0.14263 0.189138,-0.3421 0.189138,-0.59841 0,-0.54364 -0.229444,-0.81546 -0.68833,-0.81546 z" + id="circle1604" + inkscape:connector-curvature="0" /> + <path + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" + d="m 79.964081,137.28692 a 4.6772165,4.6772165 0 0 0 -4.677233,4.67724 4.6772165,4.6772165 0 0 0 4.677233,4.67723 4.6772165,4.6772165 0 0 0 4.677235,-4.67723 4.6772165,4.6772165 0 0 0 -4.677235,-4.67724 z m -1.524971,2.32596 h 0.663527 l 0.85576,1.73013 0.849562,-1.73013 h 0.666625 l -1.175123,2.23862 1.199928,2.27583 h -0.672828 l -0.868164,-1.76113 -0.871265,1.76113 h -0.672827 l 1.20613,-2.27583 z" + id="circle1610" + inkscape:connector-curvature="0" /> + <path + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:0.26458332;stroke-opacity:1" + d="m 93.546026,137.28692 a 4.6772165,4.6772165 0 0 0 -4.677233,4.67724 4.6772165,4.6772165 0 0 0 4.677233,4.67723 4.6772165,4.6772165 0 0 0 4.677235,-4.67723 4.6772165,4.6772165 0 0 0 -4.677235,-4.67724 z m -1.667598,2.32596 h 0.644922 l 0.923976,2.26653 0.923975,-2.26653 h 0.641821 l -1.283645,2.83083 v 1.68362 H 93.16207 v -1.68362 z" + id="circle1616" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/data/abxy.ttf b/data/abxy.ttf new file mode 100644 index 0000000000..d7b4b2f1d1 Binary files /dev/null and b/data/abxy.ttf differ diff --git a/data/abxy_font.svg b/data/abxy_font.svg new file mode 100644 index 0000000000..caaeb154ce --- /dev/null +++ b/data/abxy_font.svg @@ -0,0 +1,161 @@ +<?xml version="1.0" encoding="UTF-8" standalone="no"?> +<!-- Created with Inkscape (http://www.inkscape.org/) --> + +<svg + xmlns:dc="http://purl.org/dc/elements/1.1/" + xmlns:cc="http://creativecommons.org/ns#" + xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" + xmlns:svg="http://www.w3.org/2000/svg" + xmlns="http://www.w3.org/2000/svg" + xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" + xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" + height="1000" + width="1000" + sodipodi:docname="abxy_font.svg" + version="1.1" + id="svg1688" + inkscape:version="0.92.5 (2060ec1f9f, 2020-04-08)"> + <sodipodi:namedview + id="base" + pagecolor="#ffffff" + bordercolor="#666666" + borderopacity="1.0" + inkscape:pageopacity="0.0" + inkscape:pageshadow="2" + inkscape:zoom="1" + inkscape:cx="580.56811" + inkscape:cy="584.04581" + inkscape:document-units="px" + inkscape:current-layer="layer2" + showgrid="false" + showguides="true" + inkscape:window-width="1727" + inkscape:window-height="1380" + inkscape:window-x="3633" + inkscape:window-y="576" + inkscape:window-maximized="0" + showborder="true" + inkscape:showpageshadow="false"> + <sodipodi:guide + id="guide_baseline" + inkscape:label="baseline" + position="0,253" + orientation="0,1" + inkscape:locked="false" /> + <sodipodi:guide + id="guide_ascender" + inkscape:label="ascender" + position="0,945" + orientation="0,1" + inkscape:locked="false" /> + <sodipodi:guide + id="guide_caps" + inkscape:label="caps" + position="0,896" + orientation="0,1" + inkscape:locked="false" /> + <sodipodi:guide + id="guide_xheight" + inkscape:label="xheight" + position="0,729" + orientation="0,1" + inkscape:locked="false" /> + <sodipodi:guide + id="guide_descender" + inkscape:label="descender" + position="0,28" + orientation="0,1" + inkscape:locked="false" /> + </sodipodi:namedview> + <defs + id="defs4"> + <font + horiz-adv-x="1024" + id="font2233" + inkscape:label="font 1"> + <font-face + units-per-em="1024" + id="font-face2235" + font-family="ABXY" /> + <missing-glyph + d="M0,0h1000v1024h-1000z" + id="missing-glyph2237" /> + <glyph + glyph-name="Button_A" + id="glyph2313" + unicode="A" + d="M 500.00007,752.85352 A 237.17679,237.17679 0 0 1 262.82246,515.67551 237.17679,237.17679 0 0 1 500.00007,278.49804 237.17679,237.17679 0 0 1 737.17754,515.67551 237.17679,237.17679 0 0 1 500.00007,752.85352 Z M 491.2215,634.90651 h 24.68493 L 589.80332,405.98342 H 560.24448 L 542.1634,465.7298 h -77.35585 l -17.76686,-59.74638 h -29.5587 z m 12.26384,-40.25024 -31.13114,-104.0847 h 62.41953 z" /> + <glyph + glyph-name="Button_B" + id="glyph2315" + d="M 500,752.85352 A 237.17679,237.17679 0 0 1 262.82239,515.67551 237.17679,237.17679 0 0 1 500,278.49804 237.17679,237.17679 0 0 1 737.17761,515.67551 237.17679,237.17679 0 0 1 500,752.85352 Z M 435.40562,634.90651 h 65.24952 c 20.85888,0 36.5291,-5.08361 47.01093,-15.25121 10.58662,-10.16722 15.88007,-25.41843 15.88007,-45.75326 0,-10.6914 -2.67302,-20.12485 -8.01866,-28.30062 -5.34563,-8.17576 -12.57819,-14.51744 -21.69729,-19.02454 10.48183,-3.14394 18.76237,-9.59108 24.84178,-19.33875 6.18444,-9.74834 9.2766,-21.48799 9.2766,-35.2195 0,-20.54426 -5.66026,-36.68635 -16.98063,-48.426 -11.21546,-11.73965 -27.25277,-17.60921 -48.11165,-17.60921 h -67.45054 z m 28.77261,-24.84178 v -72.79631 h 36.94851 c 9.95777,0 17.97629,3.3519 24.05582,10.0627 6.18432,6.70824 9.27633,15.6179 9.27633,26.72816 0,12.57833 -2.7778,21.69783 -8.33287,27.35795 -5.55546,5.76464 -14.04572,8.6475 -25.47088,8.6475 z m 0,-97.00937 v -82.38727 h 39.30691 c 10.9011,0 19.5486,3.56483 25.94248,10.6914 6.39402,7.23269 9.59109,17.34758 9.59109,30.34479 0,27.56738 -11.635,41.35108 -34.90459,41.35108 z" + unicode="B" /> + <glyph + glyph-name="Button_X" + id="glyph2317" + d="M 500,752.85352 A 237.17679,237.17679 0 0 1 262.82226,515.67551 237.17679,237.17679 0 0 1 500,278.49804 237.17679,237.17679 0 0 1 737.17774,515.67551 237.17679,237.17679 0 0 1 500,752.85352 Z M 422.67044,634.90651 h 33.64651 l 43.39486,-87.73318 43.0805,87.73318 h 33.80376 L 517.0068,521.38836 577.85388,405.98342 h -34.11837 l -44.0237,89.30508 -44.18095,-89.30508 h -34.11837 l 61.16157,115.40494 z" + unicode="X" /> + <glyph + glyph-name="Button_Y" + id="glyph2319" + d="M 500,752.85352 A 237.17679,237.17679 0 0 1 262.82239,515.67551 237.17679,237.17679 0 0 1 500,278.49804 237.17679,237.17679 0 0 1 737.17761,515.67551 237.17679,237.17679 0 0 1 500,752.85352 Z M 415.43788,634.90651 h 32.70331 l 46.85369,-114.93335 46.85382,114.93335 h 32.54622 L 509.3025,491.35806 v -85.37464 h -28.77261 v 85.37464 z" + unicode="Y" /> + </font> + </defs> + <metadata + id="metadata7"> + <rdf:RDF> + <cc:Work + rdf:about=""> + <dc:format>image/svg+xml</dc:format> + <dc:type + rdf:resource="http://purl.org/dc/dcmitype/StillImage" /> + <dc:title></dc:title> + </cc:Work> + </rdf:RDF> + </metadata> + <g + id="layer1" + inkscape:groupmode="layer" + inkscape:label="A" + style="display:none"> + <path + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:13.41674423;stroke-opacity:1" + d="M 500.00007,271.14648 A 237.17679,237.17679 0 0 0 262.82246,508.32449 237.17679,237.17679 0 0 0 500.00007,745.50196 237.17679,237.17679 0 0 0 737.17754,508.32449 237.17679,237.17679 0 0 0 500.00007,271.14648 Z m -8.77857,117.94701 h 24.68493 l 73.89689,228.92309 H 560.24448 L 542.1634,558.2702 h -77.35585 l -17.76686,59.74638 h -29.5587 z m 12.26384,40.25024 -31.13114,104.0847 h 62.41953 z" + id="circle1598" + inkscape:connector-curvature="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer4" + inkscape:label="B" + style="display:none"> + <path + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:13.41674423;stroke-opacity:1" + d="M 500,271.14648 A 237.17679,237.17679 0 0 0 262.82239,508.32449 237.17679,237.17679 0 0 0 500,745.50196 237.17679,237.17679 0 0 0 737.17761,508.32449 237.17679,237.17679 0 0 0 500,271.14648 Z m -64.59438,117.94701 h 65.24952 c 20.85888,0 36.5291,5.08361 47.01093,15.25121 10.58662,10.16722 15.88007,25.41843 15.88007,45.75326 0,10.6914 -2.67302,20.12485 -8.01866,28.30062 -5.34563,8.17576 -12.57819,14.51744 -21.69729,19.02454 10.48183,3.14394 18.76237,9.59108 24.84178,19.33875 6.18444,9.74834 9.2766,21.48799 9.2766,35.2195 0,20.54426 -5.66026,36.68635 -16.98063,48.426 -11.21546,11.73965 -27.25277,17.60921 -48.11165,17.60921 h -67.45054 z m 28.77261,24.84178 v 72.79631 h 36.94851 c 9.95777,0 17.97629,-3.3519 24.05582,-10.0627 6.18432,-6.70824 9.27633,-15.6179 9.27633,-26.72816 0,-12.57833 -2.7778,-21.69783 -8.33287,-27.35795 -5.55546,-5.76464 -14.04572,-8.6475 -25.47088,-8.6475 z m 0,97.00937 v 82.38727 h 39.30691 c 10.9011,0 19.5486,-3.56483 25.94248,-10.6914 6.39402,-7.23269 9.59109,-17.34758 9.59109,-30.34479 0,-27.56738 -11.635,-41.35108 -34.90459,-41.35108 z" + id="circle1604" + inkscape:connector-curvature="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer3" + inkscape:label="X" + style="display:none"> + <path + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:13.41674423;stroke-opacity:1" + d="M 500,271.14648 A 237.17679,237.17679 0 0 0 262.82226,508.32449 237.17679,237.17679 0 0 0 500,745.50196 237.17679,237.17679 0 0 0 737.17774,508.32449 237.17679,237.17679 0 0 0 500,271.14648 Z m -77.32956,117.94701 h 33.64651 l 43.39486,87.73318 43.0805,-87.73318 h 33.80376 l -59.58927,113.51815 60.84708,115.40494 h -34.11837 l -44.0237,-89.30508 -44.18095,89.30508 h -34.11837 l 61.16157,-115.40494 z" + id="circle1610" + inkscape:connector-curvature="0" /> + </g> + <g + inkscape:groupmode="layer" + id="layer2" + inkscape:label="Y" + style="display:inline"> + <path + style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:13.41674423;stroke-opacity:1" + d="M 500,271.14648 A 237.17679,237.17679 0 0 0 262.82239,508.32449 237.17679,237.17679 0 0 0 500,745.50196 237.17679,237.17679 0 0 0 737.17761,508.32449 237.17679,237.17679 0 0 0 500,271.14648 Z m -84.56212,117.94701 h 32.70331 l 46.85369,114.93335 46.85382,-114.93335 h 32.54622 L 509.3025,532.64194 v 85.37464 h -28.77261 v -85.37464 z" + id="circle1616" + inkscape:connector-curvature="0" /> + </g> +</svg> diff --git a/data/font_awesome_6_1_1_solid.otf b/data/font_awesome_6_1_1_solid.otf new file mode 100644 index 0000000000..2452af5832 Binary files /dev/null and b/data/font_awesome_6_1_1_solid.otf differ diff --git a/data/meson.build b/data/meson.build index 785e335a40..89e6123d90 100644 --- a/data/meson.build +++ b/data/meson.build @@ -2,7 +2,10 @@ pfiles = [ 'controller_mask.png', 'logo_sdf.png', 'xemu_64x64.png', - 'roboto_medium.ttf', + 'abxy.ttf', + 'Roboto-Medium.ttf', + 'RobotoCondensed-Regular.ttf', + 'font_awesome_6_1_1_solid.otf', ] libpfile_targets = [] diff --git a/genconfig b/genconfig index 8220e87489..5da3fd2463 160000 --- a/genconfig +++ b/genconfig @@ -1 +1 @@ -Subproject commit 8220e8748922ddd9bba4cbacd608601586c9a4bb +Subproject commit 5da3fd2463288d9e048dbf3ea41f2bad0a4287a8 diff --git a/hw/xbox/mcpx/apu.c b/hw/xbox/mcpx/apu.c index 1344793337..cf34f14197 100644 --- a/hw/xbox/mcpx/apu.c +++ b/hw/xbox/mcpx/apu.c @@ -297,6 +297,27 @@ void mcpx_apu_debug_toggle_mute(uint16_t v) g_dbg_muted_voices[v / 64] ^= (1LL << (v % 64)); } +static void mcpx_apu_update_dsp_preference(MCPXAPUState *d) +{ + static int last_known_preference = -1; + + if (last_known_preference == (int)g_config.audio.use_dsp) { + return; + } + + if (g_config.audio.use_dsp) { + d->mon = MCPX_APU_DEBUG_MON_GP_OR_EP; + d->gp.realtime = true; + d->ep.realtime = true; + } else { + d->mon = MCPX_APU_DEBUG_MON_VP; + d->gp.realtime = false; + d->ep.realtime = false; + } + + last_known_preference = g_config.audio.use_dsp; +} + static float clampf(float v, float min, float max) { if (v < min) { @@ -2050,6 +2071,7 @@ static int voice_get_samples(MCPXAPUState *d, uint32_t v, float samples[][2], static void se_frame(MCPXAPUState *d) { + mcpx_apu_update_dsp_preference(d); mcpx_debug_begin_frame(); g_dbg.gp_realtime = d->gp.realtime; g_dbg.ep_realtime = d->ep.realtime; @@ -2137,6 +2159,7 @@ static void se_frame(MCPXAPUState *d) d->apu_fifo_output[off + i][0] += isamp[2*i]; d->apu_fifo_output[off + i][1] += isamp[2*i+1]; } + memset(d->vp.sample_buf, 0, sizeof(d->vp.sample_buf)); memset(mixbins, 0, sizeof(mixbins)); } @@ -2198,6 +2221,15 @@ static void se_frame(MCPXAPUState *d) fwrite(d->apu_fifo_output, sizeof(d->apu_fifo_output), 1, fd); fclose(fd); #endif + + if (0 <= g_config.audio.volume_limit && g_config.audio.volume_limit < 1) { + float f = pow(g_config.audio.volume_limit, M_E); + for (int i = 0; i < 256; i++) { + d->apu_fifo_output[i][0] *= f; + d->apu_fifo_output[i][1] *= f; + } + } + qemu_spin_lock(&d->vp.out_buf_lock); int num_bytes_free = fifo8_num_free(&d->vp.out_buf); assert(num_bytes_free >= sizeof(d->apu_fifo_output)); @@ -2624,15 +2656,7 @@ void mcpx_apu_init(PCIBus *bus, int devfn, MemoryRegion *ram) /* Until DSP is more performant, a switch to decide whether or not we should * use the full audio pipeline or not. */ - if (g_config.audio.use_dsp) { - d->mon = MCPX_APU_DEBUG_MON_GP_OR_EP; - d->gp.realtime = true; - d->ep.realtime = true; - } else { - d->mon = MCPX_APU_DEBUG_MON_VP; - d->gp.realtime = false; - d->ep.realtime = false; - } + mcpx_apu_update_dsp_preference(d); qemu_thread_create(&d->apu_thread, "mcpx.apu_thread", mcpx_apu_frame_thread, d, QEMU_THREAD_JOINABLE); diff --git a/licenses/fontawesome.license.txt b/licenses/fontawesome.license.txt new file mode 100644 index 0000000000..cc557ece45 --- /dev/null +++ b/licenses/fontawesome.license.txt @@ -0,0 +1,165 @@ +Fonticons, Inc. (https://fontawesome.com) + +-------------------------------------------------------------------------------- + +Font Awesome Free License + +Font Awesome Free is free, open source, and GPL friendly. You can use it for +commercial projects, open source projects, or really almost whatever you want. +Full Font Awesome Free license: https://fontawesome.com/license/free. + +-------------------------------------------------------------------------------- + +# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/) + +The Font Awesome Free download is licensed under a Creative Commons +Attribution 4.0 International License and applies to all icons packaged +as SVG and JS file types. + +-------------------------------------------------------------------------------- + +# Fonts: SIL OFL 1.1 License + +In the Font Awesome Free download, the SIL OFL license applies to all icons +packaged as web and desktop font files. + +Copyright (c) 2022 Fonticons, Inc. (https://fontawesome.com) +with Reserved Font Name: "Font Awesome". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + +SIL OPEN FONT LICENSE +Version 1.1 - 26 February 2007 + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting — in part or in whole — any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. + +-------------------------------------------------------------------------------- + +# Code: MIT License (https://opensource.org/licenses/MIT) + +In the Font Awesome Free download, the MIT license applies to all non-font and +non-icon files. + +Copyright 2022 Fonticons, Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in the +Software without restriction, including without limitation the rights to use, copy, +modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, +and to permit persons to whom the Software is furnished to do so, subject to the +following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, +INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A +PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE +SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +-------------------------------------------------------------------------------- + +# Attribution + +Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font +Awesome Free files already contain embedded comments with sufficient +attribution, so you shouldn't need to do anything additional when using these +files normally. + +We've kept attribution comments terse, so we ask that you do not actively work +to remove them from files, especially code. They're a great way for folks to +learn about Font Awesome. + +-------------------------------------------------------------------------------- + +# Brand Icons + +All brand icons are trademarks of their respective owners. The use of these +trademarks does not indicate endorsement of the trademark holder by Font +Awesome, nor vice versa. **Please do not use brand logos for any purpose except +to represent the company, product, or service to which they refer.** diff --git a/licenses/fpng.license.txt b/licenses/fpng.license.txt new file mode 100644 index 0000000000..68a49daad8 --- /dev/null +++ b/licenses/fpng.license.txt @@ -0,0 +1,24 @@ +This is free and unencumbered software released into the public domain. + +Anyone is free to copy, modify, publish, use, compile, sell, or +distribute this software, either in source code form or as a compiled +binary, for any purpose, commercial or non-commercial, and by any +means. + +In jurisdictions that recognize copyright laws, the author or authors +of this software dedicate any and all copyright interest in the +software to the public domain. We make this dedication for the benefit +of the public at large and to the detriment of our heirs and +successors. We intend this dedication to be an overt act of +relinquishment in perpetuity of all present and future rights to this +software under copyright law. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. +IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR +OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, +ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +OTHER DEALINGS IN THE SOFTWARE. + +For more information, please refer to <http://unlicense.org/> 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 <mark.gillard@outlook.com.au> + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated +documentation files (the "Software"), to deal in the Software without restriction, including without limitation the +rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the +Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE +WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR +COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/net/slirp.c b/net/slirp.c index ad3a838e0b..d5f6d621c1 100644 --- a/net/slirp.c +++ b/net/slirp.c @@ -641,6 +641,15 @@ static SlirpState *slirp_lookup(Monitor *mon, const char *id) } } +#ifdef XBOX +void *slirp_get_state_from_netdev(const char *id) +{ + SlirpState *s = slirp_lookup(NULL, id); + if (!s) return NULL; + return s->slirp; +} +#endif + void hmp_hostfwd_remove(Monitor *mon, const QDict *qdict) { struct in_addr host_addr = { .s_addr = INADDR_ANY }; diff --git a/scripts/archive-source.sh b/scripts/archive-source.sh index a2044943d2..debb1d344d 100755 --- a/scripts/archive-source.sh +++ b/scripts/archive-source.sh @@ -28,7 +28,7 @@ sub_file="${sub_tdir}/submodule.tar" # different to the host OS. submodules="dtc slirp meson ui/keycodemapdb" submodules="$submodules tests/fp/berkeley-softfloat-3 tests/fp/berkeley-testfloat-3" -submodules="$submodules ui/imgui ui/implot util/xxHash tomlplusplus genconfig" # xemu extras +submodules="$submodules ui/thirdparty/imgui ui/thirdparty/implot util/xxHash tomlplusplus genconfig" # xemu extras sub_deinit="" function cleanup() { diff --git a/scripts/gen-license.py b/scripts/gen-license.py index 705f8f96a1..66ec79cb98 100755 --- a/scripts/gen-license.py +++ b/scripts/gen-license.py @@ -20,6 +20,7 @@ bsd_3clause = 'bsd-3clause' zlib = 'zlib' lgplv2_1 = 'lgplv2_1' apache2 = 'apache2' +unlicense = 'unlicense' multi = 'multi' @@ -179,13 +180,13 @@ Lib('slirp', 'https://gitlab.freedesktop.org/slirp', Lib('imgui', 'https://github.com/ocornut/imgui', mit, 'https://raw.githubusercontent.com/ocornut/imgui/master/LICENSE.txt', ships_static=all_platforms, - submodule=Submodule('ui/imgui') + submodule=Submodule('ui/thirdparty/imgui') ), Lib('implot', 'https://github.com/epezent/implot', mit, 'https://raw.githubusercontent.com/epezent/implot/master/LICENSE', ships_static=all_platforms, - submodule=Submodule('ui/implot') + submodule=Submodule('ui/thirdparty/implot') ), Lib('httplib', 'https://github.com/yhirose/cpp-httplib', @@ -206,10 +207,10 @@ Lib('stb_image', 'https://github.com/nothings/stb', version='2.25' ), -Lib('inih', 'https://github.com/benhoyt/inih', - bsd, 'https://raw.githubusercontent.com/mborgerson/xemu/master/ui/inih/LICENSE.txt', +Lib('tomlplusplus', 'https://github.com/marzer/tomlplusplus', + mit, 'https://raw.githubusercontent.com/marzer/tomlplusplus/master/LICENSE', ships_static=all_platforms, - version='351217124ddb3e3fe2b982248a04c672350bb0af' + submodule=Submodule('tomlplusplus') ), Lib('xxHash', 'https://github.com/Cyan4973/xxHash.git', @@ -218,6 +219,12 @@ Lib('xxHash', 'https://github.com/Cyan4973/xxHash.git', submodule=Submodule('util/xxHash') ), +Lib('fpng', 'https://github.com/richgel999/fpng', + unlicense, 'https://github.com/richgel999/fpng/blob/main/README.md', + ships_static=all_platforms, + version='6926f5a0a78f22d42b074a0ab8032e07736babd4' + ), + # # Data files included with xemu # @@ -228,6 +235,12 @@ Lib('roboto', 'https://github.com/googlefonts/roboto', version='2.138' ), +Lib('fontawesome', 'https://fontawesome.com', + multi, '', + ships_static=all_platforms, + version='6.1.1' + ), + # # Libraries either linked statically, dynamically linked & shipped, or dynamically linked with system-installed libraries only # diff --git a/softmmu/vl.c b/softmmu/vl.c index 0a76d0335c..2eecce1cd7 100644 --- a/softmmu/vl.c +++ b/softmmu/vl.c @@ -2866,10 +2866,21 @@ void qemu_init(int argc, char **argv, char **envp) } } - fake_argv[fake_argc++] = g_strdup_printf("xbox%s%s%s", + const char *avpack_str = (const char *[]){ + "scart", + "hdtv", + "vga", + "rfu", + "svideo", + "composite", + "none", + }[g_config.sys.avpack]; + + fake_argv[fake_argc++] = g_strdup_printf("xbox%s%s%s,avpack=%s", (bootrom_arg != NULL) ? bootrom_arg : "", - g_config.general.misc.skip_boot_anim ? ",short-animation=on" : "", - ",kernel-irqchip=off" + g_config.general.skip_boot_anim ? ",short-animation=on" : "", + ",kernel-irqchip=off", + avpack_str ); if (bootrom_arg != NULL) { diff --git a/ui/imgui b/ui/imgui deleted file mode 160000 index e18abe3619..0000000000 --- a/ui/imgui +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e18abe3619cfa0eced163c027d0349506814816c diff --git a/ui/implot b/ui/implot deleted file mode 160000 index a6bab98517..0000000000 --- a/ui/implot +++ /dev/null @@ -1 +0,0 @@ -Subproject commit a6bab98517b1baa3116db52518dda1eb2d7eaab7 diff --git a/ui/meson.build b/ui/meson.build index d73512360f..02064602e7 100644 --- a/ui/meson.build +++ b/ui/meson.build @@ -15,57 +15,35 @@ softmmu_ss.add(files( 'udmabuf.c', )) -imgui_files = files( - 'imgui/imgui.cpp', - 'imgui/imgui_draw.cpp', - 'imgui/imgui_tables.cpp', - #'imgui/imgui_demo.cpp', - 'imgui/imgui_widgets.cpp', - 'imgui/backends/imgui_impl_opengl3.cpp', - 'imgui/backends/imgui_impl_sdl.cpp', - 'implot/implot.cpp', - #'implot/implot_demo.cpp', - 'implot/implot_items.cpp' -) - -imgui_cppargs = ['-DIMGUI_IMPL_OPENGL_LOADER_CUSTOM="epoxy/gl.h"'] - -libimgui = static_library('imgui', - sources: imgui_files, - cpp_args: imgui_cppargs, - include_directories: 'imgui', - dependencies: [sdl, opengl]) -imgui = declare_dependency(link_with: libimgui, - compile_args: imgui_cppargs, - include_directories: 'imgui') +subdir('thirdparty') xemu_ss = ss.source_set() xemu_ss.add(files( - 'xemu.c', - 'xemu-custom-widgets.c', - 'xemu-data.c', 'xemu-input.c', 'xemu-monitor.c', 'xemu-net.c', 'xemu-settings.cc', - 'xemu-shaders.c', - 'xemu-hud.cc', - 'xemu-reporting.cc', + + 'xemu.c', + 'xemu-data.c', )) -xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-update.cc')) +subdir('xui') if 'CONFIG_DARWIN' in config_host xemu_cocoa = dependency('appleframeworks', modules: 'Cocoa') -xemu_ss.add(xemu_cocoa) # FIXME: Use existing cocoa name +xemu_ss.add(xemu_cocoa) endif -xemu_ss.add(imgui, sdl, opengl, openssl) -xemu_ss.add(when: 'CONFIG_LINUX', if_true: [xemu_gtk, files('xemu-os-utils-linux.c', 'noc_file_dialog_gtk.c')]) -xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-os-utils-windows.c', 'noc_file_dialog_win32.c')) -xemu_ss.add(when: 'CONFIG_DARWIN', if_true: files('xemu-os-utils-macos.m', 'noc_file_dialog_macos.m')) +if 'CONFIG_LINUX' in config_host +xemu_ss.add(xemu_gtk) +endif +xemu_ss.add(when: 'CONFIG_LINUX', if_true: [xemu_gtk, files('xemu-os-utils-linux.c')]) +xemu_ss.add(when: 'CONFIG_WIN32', if_true: files('xemu-os-utils-windows.c')) +xemu_ss.add(when: 'CONFIG_DARWIN', if_true: files('xemu-os-utils-macos.m')) xemu_ss.add(when: 'CONFIG_RENDERDOC', if_true: [libdl]) +xemu_ss.add(imgui, implot, stb_image, noc, sdl, opengl, openssl, fa, fpng, json, httplib) softmmu_ss.add_all(xemu_ss) diff --git a/ui/thirdparty/fa/IconsFontAwesome6.h b/ui/thirdparty/fa/IconsFontAwesome6.h new file mode 100644 index 0000000000..050956448c --- /dev/null +++ b/ui/thirdparty/fa/IconsFontAwesome6.h @@ -0,0 +1,1393 @@ +// Generated by https://github.com/juliettef/IconFontCppHeaders script GenerateIconFontCppHeaders.py for languages C and C++ +// from https://github.com/FortAwesome/Font-Awesome/raw/6.x/metadata/icons.yml +// for use with https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-regular-400.ttf, https://github.com/FortAwesome/Font-Awesome/blob/6.x/webfonts/fa-solid-900.ttf +#pragma once + +#define FONT_ICON_FILE_NAME_FAR "fa-regular-400.ttf" +#define FONT_ICON_FILE_NAME_FAS "fa-solid-900.ttf" + +#define ICON_MIN_FA 0x21 +#define ICON_MAX_FA 0xf8ff +#define ICON_FA_0 "0" // U+30 +#define ICON_FA_1 "1" // U+31 +#define ICON_FA_2 "2" // U+32 +#define ICON_FA_3 "3" // U+33 +#define ICON_FA_4 "4" // U+34 +#define ICON_FA_5 "5" // U+35 +#define ICON_FA_6 "6" // U+36 +#define ICON_FA_7 "7" // U+37 +#define ICON_FA_8 "8" // U+38 +#define ICON_FA_9 "9" // U+39 +#define ICON_FA_A "A" // U+41 +#define ICON_FA_ADDRESS_BOOK "\xef\x8a\xb9" // U+f2b9 +#define ICON_FA_ADDRESS_CARD "\xef\x8a\xbb" // U+f2bb +#define ICON_FA_ALIGN_CENTER "\xef\x80\xb7" // U+f037 +#define ICON_FA_ALIGN_JUSTIFY "\xef\x80\xb9" // U+f039 +#define ICON_FA_ALIGN_LEFT "\xef\x80\xb6" // U+f036 +#define ICON_FA_ALIGN_RIGHT "\xef\x80\xb8" // U+f038 +#define ICON_FA_ANCHOR "\xef\x84\xbd" // U+f13d +#define ICON_FA_ANCHOR_CIRCLE_CHECK "\xee\x92\xaa" // U+e4aa +#define ICON_FA_ANCHOR_CIRCLE_EXCLAMATION "\xee\x92\xab" // U+e4ab +#define ICON_FA_ANCHOR_CIRCLE_XMARK "\xee\x92\xac" // U+e4ac +#define ICON_FA_ANCHOR_LOCK "\xee\x92\xad" // U+e4ad +#define ICON_FA_ANGLE_DOWN "\xef\x84\x87" // U+f107 +#define ICON_FA_ANGLE_LEFT "\xef\x84\x84" // U+f104 +#define ICON_FA_ANGLE_RIGHT "\xef\x84\x85" // U+f105 +#define ICON_FA_ANGLE_UP "\xef\x84\x86" // U+f106 +#define ICON_FA_ANGLES_DOWN "\xef\x84\x83" // U+f103 +#define ICON_FA_ANGLES_LEFT "\xef\x84\x80" // U+f100 +#define ICON_FA_ANGLES_RIGHT "\xef\x84\x81" // U+f101 +#define ICON_FA_ANGLES_UP "\xef\x84\x82" // U+f102 +#define ICON_FA_ANKH "\xef\x99\x84" // U+f644 +#define ICON_FA_APPLE_WHOLE "\xef\x97\x91" // U+f5d1 +#define ICON_FA_ARCHWAY "\xef\x95\x97" // U+f557 +#define ICON_FA_ARROW_DOWN "\xef\x81\xa3" // U+f063 +#define ICON_FA_ARROW_DOWN_1_9 "\xef\x85\xa2" // U+f162 +#define ICON_FA_ARROW_DOWN_9_1 "\xef\xa2\x86" // U+f886 +#define ICON_FA_ARROW_DOWN_A_Z "\xef\x85\x9d" // U+f15d +#define ICON_FA_ARROW_DOWN_LONG "\xef\x85\xb5" // U+f175 +#define ICON_FA_ARROW_DOWN_SHORT_WIDE "\xef\xa2\x84" // U+f884 +#define ICON_FA_ARROW_DOWN_UP_ACROSS_LINE "\xee\x92\xaf" // U+e4af +#define ICON_FA_ARROW_DOWN_UP_LOCK "\xee\x92\xb0" // U+e4b0 +#define ICON_FA_ARROW_DOWN_WIDE_SHORT "\xef\x85\xa0" // U+f160 +#define ICON_FA_ARROW_DOWN_Z_A "\xef\xa2\x81" // U+f881 +#define ICON_FA_ARROW_LEFT "\xef\x81\xa0" // U+f060 +#define ICON_FA_ARROW_LEFT_LONG "\xef\x85\xb7" // U+f177 +#define ICON_FA_ARROW_POINTER "\xef\x89\x85" // U+f245 +#define ICON_FA_ARROW_RIGHT "\xef\x81\xa1" // U+f061 +#define ICON_FA_ARROW_RIGHT_ARROW_LEFT "\xef\x83\xac" // U+f0ec +#define ICON_FA_ARROW_RIGHT_FROM_BRACKET "\xef\x82\x8b" // U+f08b +#define ICON_FA_ARROW_RIGHT_LONG "\xef\x85\xb8" // U+f178 +#define ICON_FA_ARROW_RIGHT_TO_BRACKET "\xef\x82\x90" // U+f090 +#define ICON_FA_ARROW_RIGHT_TO_CITY "\xee\x92\xb3" // U+e4b3 +#define ICON_FA_ARROW_ROTATE_LEFT "\xef\x83\xa2" // U+f0e2 +#define ICON_FA_ARROW_ROTATE_RIGHT "\xef\x80\x9e" // U+f01e +#define ICON_FA_ARROW_TREND_DOWN "\xee\x82\x97" // U+e097 +#define ICON_FA_ARROW_TREND_UP "\xee\x82\x98" // U+e098 +#define ICON_FA_ARROW_TURN_DOWN "\xef\x85\x89" // U+f149 +#define ICON_FA_ARROW_TURN_UP "\xef\x85\x88" // U+f148 +#define ICON_FA_ARROW_UP "\xef\x81\xa2" // U+f062 +#define ICON_FA_ARROW_UP_1_9 "\xef\x85\xa3" // U+f163 +#define ICON_FA_ARROW_UP_9_1 "\xef\xa2\x87" // U+f887 +#define ICON_FA_ARROW_UP_A_Z "\xef\x85\x9e" // U+f15e +#define ICON_FA_ARROW_UP_FROM_BRACKET "\xee\x82\x9a" // U+e09a +#define ICON_FA_ARROW_UP_FROM_GROUND_WATER "\xee\x92\xb5" // U+e4b5 +#define ICON_FA_ARROW_UP_FROM_WATER_PUMP "\xee\x92\xb6" // U+e4b6 +#define ICON_FA_ARROW_UP_LONG "\xef\x85\xb6" // U+f176 +#define ICON_FA_ARROW_UP_RIGHT_DOTS "\xee\x92\xb7" // U+e4b7 +#define ICON_FA_ARROW_UP_RIGHT_FROM_SQUARE "\xef\x82\x8e" // U+f08e +#define ICON_FA_ARROW_UP_SHORT_WIDE "\xef\xa2\x85" // U+f885 +#define ICON_FA_ARROW_UP_WIDE_SHORT "\xef\x85\xa1" // U+f161 +#define ICON_FA_ARROW_UP_Z_A "\xef\xa2\x82" // U+f882 +#define ICON_FA_ARROWS_DOWN_TO_LINE "\xee\x92\xb8" // U+e4b8 +#define ICON_FA_ARROWS_DOWN_TO_PEOPLE "\xee\x92\xb9" // U+e4b9 +#define ICON_FA_ARROWS_LEFT_RIGHT "\xef\x81\xbe" // U+f07e +#define ICON_FA_ARROWS_LEFT_RIGHT_TO_LINE "\xee\x92\xba" // U+e4ba +#define ICON_FA_ARROWS_ROTATE "\xef\x80\xa1" // U+f021 +#define ICON_FA_ARROWS_SPIN "\xee\x92\xbb" // U+e4bb +#define ICON_FA_ARROWS_SPLIT_UP_AND_LEFT "\xee\x92\xbc" // U+e4bc +#define ICON_FA_ARROWS_TO_CIRCLE "\xee\x92\xbd" // U+e4bd +#define ICON_FA_ARROWS_TO_DOT "\xee\x92\xbe" // U+e4be +#define ICON_FA_ARROWS_TO_EYE "\xee\x92\xbf" // U+e4bf +#define ICON_FA_ARROWS_TURN_RIGHT "\xee\x93\x80" // U+e4c0 +#define ICON_FA_ARROWS_TURN_TO_DOTS "\xee\x93\x81" // U+e4c1 +#define ICON_FA_ARROWS_UP_DOWN "\xef\x81\xbd" // U+f07d +#define ICON_FA_ARROWS_UP_DOWN_LEFT_RIGHT "\xef\x81\x87" // U+f047 +#define ICON_FA_ARROWS_UP_TO_LINE "\xee\x93\x82" // U+e4c2 +#define ICON_FA_ASTERISK "*" // U+2a +#define ICON_FA_AT "@" // U+40 +#define ICON_FA_ATOM "\xef\x97\x92" // U+f5d2 +#define ICON_FA_AUDIO_DESCRIPTION "\xef\x8a\x9e" // U+f29e +#define ICON_FA_AUSTRAL_SIGN "\xee\x82\xa9" // U+e0a9 +#define ICON_FA_AWARD "\xef\x95\x99" // U+f559 +#define ICON_FA_B "B" // U+42 +#define ICON_FA_BABY "\xef\x9d\xbc" // U+f77c +#define ICON_FA_BABY_CARRIAGE "\xef\x9d\xbd" // U+f77d +#define ICON_FA_BACKWARD "\xef\x81\x8a" // U+f04a +#define ICON_FA_BACKWARD_FAST "\xef\x81\x89" // U+f049 +#define ICON_FA_BACKWARD_STEP "\xef\x81\x88" // U+f048 +#define ICON_FA_BACON "\xef\x9f\xa5" // U+f7e5 +#define ICON_FA_BACTERIA "\xee\x81\x99" // U+e059 +#define ICON_FA_BACTERIUM "\xee\x81\x9a" // U+e05a +#define ICON_FA_BAG_SHOPPING "\xef\x8a\x90" // U+f290 +#define ICON_FA_BAHAI "\xef\x99\xa6" // U+f666 +#define ICON_FA_BAHT_SIGN "\xee\x82\xac" // U+e0ac +#define ICON_FA_BAN "\xef\x81\x9e" // U+f05e +#define ICON_FA_BAN_SMOKING "\xef\x95\x8d" // U+f54d +#define ICON_FA_BANDAGE "\xef\x91\xa2" // U+f462 +#define ICON_FA_BARCODE "\xef\x80\xaa" // U+f02a +#define ICON_FA_BARS "\xef\x83\x89" // U+f0c9 +#define ICON_FA_BARS_PROGRESS "\xef\xa0\xa8" // U+f828 +#define ICON_FA_BARS_STAGGERED "\xef\x95\x90" // U+f550 +#define ICON_FA_BASEBALL "\xef\x90\xb3" // U+f433 +#define ICON_FA_BASEBALL_BAT_BALL "\xef\x90\xb2" // U+f432 +#define ICON_FA_BASKET_SHOPPING "\xef\x8a\x91" // U+f291 +#define ICON_FA_BASKETBALL "\xef\x90\xb4" // U+f434 +#define ICON_FA_BATH "\xef\x8b\x8d" // U+f2cd +#define ICON_FA_BATTERY_EMPTY "\xef\x89\x84" // U+f244 +#define ICON_FA_BATTERY_FULL "\xef\x89\x80" // U+f240 +#define ICON_FA_BATTERY_HALF "\xef\x89\x82" // U+f242 +#define ICON_FA_BATTERY_QUARTER "\xef\x89\x83" // U+f243 +#define ICON_FA_BATTERY_THREE_QUARTERS "\xef\x89\x81" // U+f241 +#define ICON_FA_BED "\xef\x88\xb6" // U+f236 +#define ICON_FA_BED_PULSE "\xef\x92\x87" // U+f487 +#define ICON_FA_BEER_MUG_EMPTY "\xef\x83\xbc" // U+f0fc +#define ICON_FA_BELL "\xef\x83\xb3" // U+f0f3 +#define ICON_FA_BELL_CONCIERGE "\xef\x95\xa2" // U+f562 +#define ICON_FA_BELL_SLASH "\xef\x87\xb6" // U+f1f6 +#define ICON_FA_BEZIER_CURVE "\xef\x95\x9b" // U+f55b +#define ICON_FA_BICYCLE "\xef\x88\x86" // U+f206 +#define ICON_FA_BINOCULARS "\xef\x87\xa5" // U+f1e5 +#define ICON_FA_BIOHAZARD "\xef\x9e\x80" // U+f780 +#define ICON_FA_BITCOIN_SIGN "\xee\x82\xb4" // U+e0b4 +#define ICON_FA_BLENDER "\xef\x94\x97" // U+f517 +#define ICON_FA_BLENDER_PHONE "\xef\x9a\xb6" // U+f6b6 +#define ICON_FA_BLOG "\xef\x9e\x81" // U+f781 +#define ICON_FA_BOLD "\xef\x80\xb2" // U+f032 +#define ICON_FA_BOLT "\xef\x83\xa7" // U+f0e7 +#define ICON_FA_BOLT_LIGHTNING "\xee\x82\xb7" // U+e0b7 +#define ICON_FA_BOMB "\xef\x87\xa2" // U+f1e2 +#define ICON_FA_BONE "\xef\x97\x97" // U+f5d7 +#define ICON_FA_BONG "\xef\x95\x9c" // U+f55c +#define ICON_FA_BOOK "\xef\x80\xad" // U+f02d +#define ICON_FA_BOOK_ATLAS "\xef\x95\x98" // U+f558 +#define ICON_FA_BOOK_BIBLE "\xef\x99\x87" // U+f647 +#define ICON_FA_BOOK_BOOKMARK "\xee\x82\xbb" // U+e0bb +#define ICON_FA_BOOK_JOURNAL_WHILLS "\xef\x99\xaa" // U+f66a +#define ICON_FA_BOOK_MEDICAL "\xef\x9f\xa6" // U+f7e6 +#define ICON_FA_BOOK_OPEN "\xef\x94\x98" // U+f518 +#define ICON_FA_BOOK_OPEN_READER "\xef\x97\x9a" // U+f5da +#define ICON_FA_BOOK_QURAN "\xef\x9a\x87" // U+f687 +#define ICON_FA_BOOK_SKULL "\xef\x9a\xb7" // U+f6b7 +#define ICON_FA_BOOKMARK "\xef\x80\xae" // U+f02e +#define ICON_FA_BORDER_ALL "\xef\xa1\x8c" // U+f84c +#define ICON_FA_BORDER_NONE "\xef\xa1\x90" // U+f850 +#define ICON_FA_BORDER_TOP_LEFT "\xef\xa1\x93" // U+f853 +#define ICON_FA_BORE_HOLE "\xee\x93\x83" // U+e4c3 +#define ICON_FA_BOTTLE_DROPLET "\xee\x93\x84" // U+e4c4 +#define ICON_FA_BOTTLE_WATER "\xee\x93\x85" // U+e4c5 +#define ICON_FA_BOWL_FOOD "\xee\x93\x86" // U+e4c6 +#define ICON_FA_BOWL_RICE "\xee\x8b\xab" // U+e2eb +#define ICON_FA_BOWLING_BALL "\xef\x90\xb6" // U+f436 +#define ICON_FA_BOX "\xef\x91\xa6" // U+f466 +#define ICON_FA_BOX_ARCHIVE "\xef\x86\x87" // U+f187 +#define ICON_FA_BOX_OPEN "\xef\x92\x9e" // U+f49e +#define ICON_FA_BOX_TISSUE "\xee\x81\x9b" // U+e05b +#define ICON_FA_BOXES_PACKING "\xee\x93\x87" // U+e4c7 +#define ICON_FA_BOXES_STACKED "\xef\x91\xa8" // U+f468 +#define ICON_FA_BRAILLE "\xef\x8a\xa1" // U+f2a1 +#define ICON_FA_BRAIN "\xef\x97\x9c" // U+f5dc +#define ICON_FA_BRAZILIAN_REAL_SIGN "\xee\x91\xac" // U+e46c +#define ICON_FA_BREAD_SLICE "\xef\x9f\xac" // U+f7ec +#define ICON_FA_BRIDGE "\xee\x93\x88" // U+e4c8 +#define ICON_FA_BRIDGE_CIRCLE_CHECK "\xee\x93\x89" // U+e4c9 +#define ICON_FA_BRIDGE_CIRCLE_EXCLAMATION "\xee\x93\x8a" // U+e4ca +#define ICON_FA_BRIDGE_CIRCLE_XMARK "\xee\x93\x8b" // U+e4cb +#define ICON_FA_BRIDGE_LOCK "\xee\x93\x8c" // U+e4cc +#define ICON_FA_BRIDGE_WATER "\xee\x93\x8e" // U+e4ce +#define ICON_FA_BRIEFCASE "\xef\x82\xb1" // U+f0b1 +#define ICON_FA_BRIEFCASE_MEDICAL "\xef\x91\xa9" // U+f469 +#define ICON_FA_BROOM "\xef\x94\x9a" // U+f51a +#define ICON_FA_BROOM_BALL "\xef\x91\x98" // U+f458 +#define ICON_FA_BRUSH "\xef\x95\x9d" // U+f55d +#define ICON_FA_BUCKET "\xee\x93\x8f" // U+e4cf +#define ICON_FA_BUG "\xef\x86\x88" // U+f188 +#define ICON_FA_BUG_SLASH "\xee\x92\x90" // U+e490 +#define ICON_FA_BUGS "\xee\x93\x90" // U+e4d0 +#define ICON_FA_BUILDING "\xef\x86\xad" // U+f1ad +#define ICON_FA_BUILDING_CIRCLE_ARROW_RIGHT "\xee\x93\x91" // U+e4d1 +#define ICON_FA_BUILDING_CIRCLE_CHECK "\xee\x93\x92" // U+e4d2 +#define ICON_FA_BUILDING_CIRCLE_EXCLAMATION "\xee\x93\x93" // U+e4d3 +#define ICON_FA_BUILDING_CIRCLE_XMARK "\xee\x93\x94" // U+e4d4 +#define ICON_FA_BUILDING_COLUMNS "\xef\x86\x9c" // U+f19c +#define ICON_FA_BUILDING_FLAG "\xee\x93\x95" // U+e4d5 +#define ICON_FA_BUILDING_LOCK "\xee\x93\x96" // U+e4d6 +#define ICON_FA_BUILDING_NGO "\xee\x93\x97" // U+e4d7 +#define ICON_FA_BUILDING_SHIELD "\xee\x93\x98" // U+e4d8 +#define ICON_FA_BUILDING_UN "\xee\x93\x99" // U+e4d9 +#define ICON_FA_BUILDING_USER "\xee\x93\x9a" // U+e4da +#define ICON_FA_BUILDING_WHEAT "\xee\x93\x9b" // U+e4db +#define ICON_FA_BULLHORN "\xef\x82\xa1" // U+f0a1 +#define ICON_FA_BULLSEYE "\xef\x85\x80" // U+f140 +#define ICON_FA_BURGER "\xef\xa0\x85" // U+f805 +#define ICON_FA_BURST "\xee\x93\x9c" // U+e4dc +#define ICON_FA_BUS "\xef\x88\x87" // U+f207 +#define ICON_FA_BUS_SIMPLE "\xef\x95\x9e" // U+f55e +#define ICON_FA_BUSINESS_TIME "\xef\x99\x8a" // U+f64a +#define ICON_FA_C "C" // U+43 +#define ICON_FA_CAKE_CANDLES "\xef\x87\xbd" // U+f1fd +#define ICON_FA_CALCULATOR "\xef\x87\xac" // U+f1ec +#define ICON_FA_CALENDAR "\xef\x84\xb3" // U+f133 +#define ICON_FA_CALENDAR_CHECK "\xef\x89\xb4" // U+f274 +#define ICON_FA_CALENDAR_DAY "\xef\x9e\x83" // U+f783 +#define ICON_FA_CALENDAR_DAYS "\xef\x81\xb3" // U+f073 +#define ICON_FA_CALENDAR_MINUS "\xef\x89\xb2" // U+f272 +#define ICON_FA_CALENDAR_PLUS "\xef\x89\xb1" // U+f271 +#define ICON_FA_CALENDAR_WEEK "\xef\x9e\x84" // U+f784 +#define ICON_FA_CALENDAR_XMARK "\xef\x89\xb3" // U+f273 +#define ICON_FA_CAMERA "\xef\x80\xb0" // U+f030 +#define ICON_FA_CAMERA_RETRO "\xef\x82\x83" // U+f083 +#define ICON_FA_CAMERA_ROTATE "\xee\x83\x98" // U+e0d8 +#define ICON_FA_CAMPGROUND "\xef\x9a\xbb" // U+f6bb +#define ICON_FA_CANDY_CANE "\xef\x9e\x86" // U+f786 +#define ICON_FA_CANNABIS "\xef\x95\x9f" // U+f55f +#define ICON_FA_CAPSULES "\xef\x91\xab" // U+f46b +#define ICON_FA_CAR "\xef\x86\xb9" // U+f1b9 +#define ICON_FA_CAR_BATTERY "\xef\x97\x9f" // U+f5df +#define ICON_FA_CAR_BURST "\xef\x97\xa1" // U+f5e1 +#define ICON_FA_CAR_ON "\xee\x93\x9d" // U+e4dd +#define ICON_FA_CAR_REAR "\xef\x97\x9e" // U+f5de +#define ICON_FA_CAR_SIDE "\xef\x97\xa4" // U+f5e4 +#define ICON_FA_CAR_TUNNEL "\xee\x93\x9e" // U+e4de +#define ICON_FA_CARAVAN "\xef\xa3\xbf" // U+f8ff +#define ICON_FA_CARET_DOWN "\xef\x83\x97" // U+f0d7 +#define ICON_FA_CARET_LEFT "\xef\x83\x99" // U+f0d9 +#define ICON_FA_CARET_RIGHT "\xef\x83\x9a" // U+f0da +#define ICON_FA_CARET_UP "\xef\x83\x98" // U+f0d8 +#define ICON_FA_CARROT "\xef\x9e\x87" // U+f787 +#define ICON_FA_CART_ARROW_DOWN "\xef\x88\x98" // U+f218 +#define ICON_FA_CART_FLATBED "\xef\x91\xb4" // U+f474 +#define ICON_FA_CART_FLATBED_SUITCASE "\xef\x96\x9d" // U+f59d +#define ICON_FA_CART_PLUS "\xef\x88\x97" // U+f217 +#define ICON_FA_CART_SHOPPING "\xef\x81\xba" // U+f07a +#define ICON_FA_CASH_REGISTER "\xef\x9e\x88" // U+f788 +#define ICON_FA_CAT "\xef\x9a\xbe" // U+f6be +#define ICON_FA_CEDI_SIGN "\xee\x83\x9f" // U+e0df +#define ICON_FA_CENT_SIGN "\xee\x8f\xb5" // U+e3f5 +#define ICON_FA_CERTIFICATE "\xef\x82\xa3" // U+f0a3 +#define ICON_FA_CHAIR "\xef\x9b\x80" // U+f6c0 +#define ICON_FA_CHALKBOARD "\xef\x94\x9b" // U+f51b +#define ICON_FA_CHALKBOARD_USER "\xef\x94\x9c" // U+f51c +#define ICON_FA_CHAMPAGNE_GLASSES "\xef\x9e\x9f" // U+f79f +#define ICON_FA_CHARGING_STATION "\xef\x97\xa7" // U+f5e7 +#define ICON_FA_CHART_AREA "\xef\x87\xbe" // U+f1fe +#define ICON_FA_CHART_BAR "\xef\x82\x80" // U+f080 +#define ICON_FA_CHART_COLUMN "\xee\x83\xa3" // U+e0e3 +#define ICON_FA_CHART_GANTT "\xee\x83\xa4" // U+e0e4 +#define ICON_FA_CHART_LINE "\xef\x88\x81" // U+f201 +#define ICON_FA_CHART_PIE "\xef\x88\x80" // U+f200 +#define ICON_FA_CHART_SIMPLE "\xee\x91\xb3" // U+e473 +#define ICON_FA_CHECK "\xef\x80\x8c" // U+f00c +#define ICON_FA_CHECK_DOUBLE "\xef\x95\xa0" // U+f560 +#define ICON_FA_CHECK_TO_SLOT "\xef\x9d\xb2" // U+f772 +#define ICON_FA_CHEESE "\xef\x9f\xaf" // U+f7ef +#define ICON_FA_CHESS "\xef\x90\xb9" // U+f439 +#define ICON_FA_CHESS_BISHOP "\xef\x90\xba" // U+f43a +#define ICON_FA_CHESS_BOARD "\xef\x90\xbc" // U+f43c +#define ICON_FA_CHESS_KING "\xef\x90\xbf" // U+f43f +#define ICON_FA_CHESS_KNIGHT "\xef\x91\x81" // U+f441 +#define ICON_FA_CHESS_PAWN "\xef\x91\x83" // U+f443 +#define ICON_FA_CHESS_QUEEN "\xef\x91\x85" // U+f445 +#define ICON_FA_CHESS_ROOK "\xef\x91\x87" // U+f447 +#define ICON_FA_CHEVRON_DOWN "\xef\x81\xb8" // U+f078 +#define ICON_FA_CHEVRON_LEFT "\xef\x81\x93" // U+f053 +#define ICON_FA_CHEVRON_RIGHT "\xef\x81\x94" // U+f054 +#define ICON_FA_CHEVRON_UP "\xef\x81\xb7" // U+f077 +#define ICON_FA_CHILD "\xef\x86\xae" // U+f1ae +#define ICON_FA_CHILD_RIFLE "\xee\x93\xa0" // U+e4e0 +#define ICON_FA_CHILDREN "\xee\x93\xa1" // U+e4e1 +#define ICON_FA_CHURCH "\xef\x94\x9d" // U+f51d +#define ICON_FA_CIRCLE "\xef\x84\x91" // U+f111 +#define ICON_FA_CIRCLE_ARROW_DOWN "\xef\x82\xab" // U+f0ab +#define ICON_FA_CIRCLE_ARROW_LEFT "\xef\x82\xa8" // U+f0a8 +#define ICON_FA_CIRCLE_ARROW_RIGHT "\xef\x82\xa9" // U+f0a9 +#define ICON_FA_CIRCLE_ARROW_UP "\xef\x82\xaa" // U+f0aa +#define ICON_FA_CIRCLE_CHECK "\xef\x81\x98" // U+f058 +#define ICON_FA_CIRCLE_CHEVRON_DOWN "\xef\x84\xba" // U+f13a +#define ICON_FA_CIRCLE_CHEVRON_LEFT "\xef\x84\xb7" // U+f137 +#define ICON_FA_CIRCLE_CHEVRON_RIGHT "\xef\x84\xb8" // U+f138 +#define ICON_FA_CIRCLE_CHEVRON_UP "\xef\x84\xb9" // U+f139 +#define ICON_FA_CIRCLE_DOLLAR_TO_SLOT "\xef\x92\xb9" // U+f4b9 +#define ICON_FA_CIRCLE_DOT "\xef\x86\x92" // U+f192 +#define ICON_FA_CIRCLE_DOWN "\xef\x8d\x98" // U+f358 +#define ICON_FA_CIRCLE_EXCLAMATION "\xef\x81\xaa" // U+f06a +#define ICON_FA_CIRCLE_H "\xef\x91\xbe" // U+f47e +#define ICON_FA_CIRCLE_HALF_STROKE "\xef\x81\x82" // U+f042 +#define ICON_FA_CIRCLE_INFO "\xef\x81\x9a" // U+f05a +#define ICON_FA_CIRCLE_LEFT "\xef\x8d\x99" // U+f359 +#define ICON_FA_CIRCLE_MINUS "\xef\x81\x96" // U+f056 +#define ICON_FA_CIRCLE_NODES "\xee\x93\xa2" // U+e4e2 +#define ICON_FA_CIRCLE_NOTCH "\xef\x87\x8e" // U+f1ce +#define ICON_FA_CIRCLE_PAUSE "\xef\x8a\x8b" // U+f28b +#define ICON_FA_CIRCLE_PLAY "\xef\x85\x84" // U+f144 +#define ICON_FA_CIRCLE_PLUS "\xef\x81\x95" // U+f055 +#define ICON_FA_CIRCLE_QUESTION "\xef\x81\x99" // U+f059 +#define ICON_FA_CIRCLE_RADIATION "\xef\x9e\xba" // U+f7ba +#define ICON_FA_CIRCLE_RIGHT "\xef\x8d\x9a" // U+f35a +#define ICON_FA_CIRCLE_STOP "\xef\x8a\x8d" // U+f28d +#define ICON_FA_CIRCLE_UP "\xef\x8d\x9b" // U+f35b +#define ICON_FA_CIRCLE_USER "\xef\x8a\xbd" // U+f2bd +#define ICON_FA_CIRCLE_XMARK "\xef\x81\x97" // U+f057 +#define ICON_FA_CITY "\xef\x99\x8f" // U+f64f +#define ICON_FA_CLAPPERBOARD "\xee\x84\xb1" // U+e131 +#define ICON_FA_CLIPBOARD "\xef\x8c\xa8" // U+f328 +#define ICON_FA_CLIPBOARD_CHECK "\xef\x91\xac" // U+f46c +#define ICON_FA_CLIPBOARD_LIST "\xef\x91\xad" // U+f46d +#define ICON_FA_CLIPBOARD_QUESTION "\xee\x93\xa3" // U+e4e3 +#define ICON_FA_CLIPBOARD_USER "\xef\x9f\xb3" // U+f7f3 +#define ICON_FA_CLOCK "\xef\x80\x97" // U+f017 +#define ICON_FA_CLOCK_ROTATE_LEFT "\xef\x87\x9a" // U+f1da +#define ICON_FA_CLONE "\xef\x89\x8d" // U+f24d +#define ICON_FA_CLOSED_CAPTIONING "\xef\x88\x8a" // U+f20a +#define ICON_FA_CLOUD "\xef\x83\x82" // U+f0c2 +#define ICON_FA_CLOUD_ARROW_DOWN "\xef\x83\xad" // U+f0ed +#define ICON_FA_CLOUD_ARROW_UP "\xef\x83\xae" // U+f0ee +#define ICON_FA_CLOUD_BOLT "\xef\x9d\xac" // U+f76c +#define ICON_FA_CLOUD_MEATBALL "\xef\x9c\xbb" // U+f73b +#define ICON_FA_CLOUD_MOON "\xef\x9b\x83" // U+f6c3 +#define ICON_FA_CLOUD_MOON_RAIN "\xef\x9c\xbc" // U+f73c +#define ICON_FA_CLOUD_RAIN "\xef\x9c\xbd" // U+f73d +#define ICON_FA_CLOUD_SHOWERS_HEAVY "\xef\x9d\x80" // U+f740 +#define ICON_FA_CLOUD_SHOWERS_WATER "\xee\x93\xa4" // U+e4e4 +#define ICON_FA_CLOUD_SUN "\xef\x9b\x84" // U+f6c4 +#define ICON_FA_CLOUD_SUN_RAIN "\xef\x9d\x83" // U+f743 +#define ICON_FA_CLOVER "\xee\x84\xb9" // U+e139 +#define ICON_FA_CODE "\xef\x84\xa1" // U+f121 +#define ICON_FA_CODE_BRANCH "\xef\x84\xa6" // U+f126 +#define ICON_FA_CODE_COMMIT "\xef\x8e\x86" // U+f386 +#define ICON_FA_CODE_COMPARE "\xee\x84\xba" // U+e13a +#define ICON_FA_CODE_FORK "\xee\x84\xbb" // U+e13b +#define ICON_FA_CODE_MERGE "\xef\x8e\x87" // U+f387 +#define ICON_FA_CODE_PULL_REQUEST "\xee\x84\xbc" // U+e13c +#define ICON_FA_COINS "\xef\x94\x9e" // U+f51e +#define ICON_FA_COLON_SIGN "\xee\x85\x80" // U+e140 +#define ICON_FA_COMMENT "\xef\x81\xb5" // U+f075 +#define ICON_FA_COMMENT_DOLLAR "\xef\x99\x91" // U+f651 +#define ICON_FA_COMMENT_DOTS "\xef\x92\xad" // U+f4ad +#define ICON_FA_COMMENT_MEDICAL "\xef\x9f\xb5" // U+f7f5 +#define ICON_FA_COMMENT_SLASH "\xef\x92\xb3" // U+f4b3 +#define ICON_FA_COMMENT_SMS "\xef\x9f\x8d" // U+f7cd +#define ICON_FA_COMMENTS "\xef\x82\x86" // U+f086 +#define ICON_FA_COMMENTS_DOLLAR "\xef\x99\x93" // U+f653 +#define ICON_FA_COMPACT_DISC "\xef\x94\x9f" // U+f51f +#define ICON_FA_COMPASS "\xef\x85\x8e" // U+f14e +#define ICON_FA_COMPASS_DRAFTING "\xef\x95\xa8" // U+f568 +#define ICON_FA_COMPRESS "\xef\x81\xa6" // U+f066 +#define ICON_FA_COMPUTER "\xee\x93\xa5" // U+e4e5 +#define ICON_FA_COMPUTER_MOUSE "\xef\xa3\x8c" // U+f8cc +#define ICON_FA_COOKIE "\xef\x95\xa3" // U+f563 +#define ICON_FA_COOKIE_BITE "\xef\x95\xa4" // U+f564 +#define ICON_FA_COPY "\xef\x83\x85" // U+f0c5 +#define ICON_FA_COPYRIGHT "\xef\x87\xb9" // U+f1f9 +#define ICON_FA_COUCH "\xef\x92\xb8" // U+f4b8 +#define ICON_FA_COW "\xef\x9b\x88" // U+f6c8 +#define ICON_FA_CREDIT_CARD "\xef\x82\x9d" // U+f09d +#define ICON_FA_CROP "\xef\x84\xa5" // U+f125 +#define ICON_FA_CROP_SIMPLE "\xef\x95\xa5" // U+f565 +#define ICON_FA_CROSS "\xef\x99\x94" // U+f654 +#define ICON_FA_CROSSHAIRS "\xef\x81\x9b" // U+f05b +#define ICON_FA_CROW "\xef\x94\xa0" // U+f520 +#define ICON_FA_CROWN "\xef\x94\xa1" // U+f521 +#define ICON_FA_CRUTCH "\xef\x9f\xb7" // U+f7f7 +#define ICON_FA_CRUZEIRO_SIGN "\xee\x85\x92" // U+e152 +#define ICON_FA_CUBE "\xef\x86\xb2" // U+f1b2 +#define ICON_FA_CUBES "\xef\x86\xb3" // U+f1b3 +#define ICON_FA_CUBES_STACKED "\xee\x93\xa6" // U+e4e6 +#define ICON_FA_D "D" // U+44 +#define ICON_FA_DATABASE "\xef\x87\x80" // U+f1c0 +#define ICON_FA_DELETE_LEFT "\xef\x95\x9a" // U+f55a +#define ICON_FA_DEMOCRAT "\xef\x9d\x87" // U+f747 +#define ICON_FA_DESKTOP "\xef\x8e\x90" // U+f390 +#define ICON_FA_DHARMACHAKRA "\xef\x99\x95" // U+f655 +#define ICON_FA_DIAGRAM_NEXT "\xee\x91\xb6" // U+e476 +#define ICON_FA_DIAGRAM_PREDECESSOR "\xee\x91\xb7" // U+e477 +#define ICON_FA_DIAGRAM_PROJECT "\xef\x95\x82" // U+f542 +#define ICON_FA_DIAGRAM_SUCCESSOR "\xee\x91\xba" // U+e47a +#define ICON_FA_DIAMOND "\xef\x88\x99" // U+f219 +#define ICON_FA_DIAMOND_TURN_RIGHT "\xef\x97\xab" // U+f5eb +#define ICON_FA_DICE "\xef\x94\xa2" // U+f522 +#define ICON_FA_DICE_D20 "\xef\x9b\x8f" // U+f6cf +#define ICON_FA_DICE_D6 "\xef\x9b\x91" // U+f6d1 +#define ICON_FA_DICE_FIVE "\xef\x94\xa3" // U+f523 +#define ICON_FA_DICE_FOUR "\xef\x94\xa4" // U+f524 +#define ICON_FA_DICE_ONE "\xef\x94\xa5" // U+f525 +#define ICON_FA_DICE_SIX "\xef\x94\xa6" // U+f526 +#define ICON_FA_DICE_THREE "\xef\x94\xa7" // U+f527 +#define ICON_FA_DICE_TWO "\xef\x94\xa8" // U+f528 +#define ICON_FA_DISEASE "\xef\x9f\xba" // U+f7fa +#define ICON_FA_DISPLAY "\xee\x85\xa3" // U+e163 +#define ICON_FA_DIVIDE "\xef\x94\xa9" // U+f529 +#define ICON_FA_DNA "\xef\x91\xb1" // U+f471 +#define ICON_FA_DOG "\xef\x9b\x93" // U+f6d3 +#define ICON_FA_DOLLAR_SIGN "$" // U+24 +#define ICON_FA_DOLLY "\xef\x91\xb2" // U+f472 +#define ICON_FA_DONG_SIGN "\xee\x85\xa9" // U+e169 +#define ICON_FA_DOOR_CLOSED "\xef\x94\xaa" // U+f52a +#define ICON_FA_DOOR_OPEN "\xef\x94\xab" // U+f52b +#define ICON_FA_DOVE "\xef\x92\xba" // U+f4ba +#define ICON_FA_DOWN_LEFT_AND_UP_RIGHT_TO_CENTER "\xef\x90\xa2" // U+f422 +#define ICON_FA_DOWN_LONG "\xef\x8c\x89" // U+f309 +#define ICON_FA_DOWNLOAD "\xef\x80\x99" // U+f019 +#define ICON_FA_DRAGON "\xef\x9b\x95" // U+f6d5 +#define ICON_FA_DRAW_POLYGON "\xef\x97\xae" // U+f5ee +#define ICON_FA_DROPLET "\xef\x81\x83" // U+f043 +#define ICON_FA_DROPLET_SLASH "\xef\x97\x87" // U+f5c7 +#define ICON_FA_DRUM "\xef\x95\xa9" // U+f569 +#define ICON_FA_DRUM_STEELPAN "\xef\x95\xaa" // U+f56a +#define ICON_FA_DRUMSTICK_BITE "\xef\x9b\x97" // U+f6d7 +#define ICON_FA_DUMBBELL "\xef\x91\x8b" // U+f44b +#define ICON_FA_DUMPSTER "\xef\x9e\x93" // U+f793 +#define ICON_FA_DUMPSTER_FIRE "\xef\x9e\x94" // U+f794 +#define ICON_FA_DUNGEON "\xef\x9b\x99" // U+f6d9 +#define ICON_FA_E "E" // U+45 +#define ICON_FA_EAR_DEAF "\xef\x8a\xa4" // U+f2a4 +#define ICON_FA_EAR_LISTEN "\xef\x8a\xa2" // U+f2a2 +#define ICON_FA_EARTH_AFRICA "\xef\x95\xbc" // U+f57c +#define ICON_FA_EARTH_AMERICAS "\xef\x95\xbd" // U+f57d +#define ICON_FA_EARTH_ASIA "\xef\x95\xbe" // U+f57e +#define ICON_FA_EARTH_EUROPE "\xef\x9e\xa2" // U+f7a2 +#define ICON_FA_EARTH_OCEANIA "\xee\x91\xbb" // U+e47b +#define ICON_FA_EGG "\xef\x9f\xbb" // U+f7fb +#define ICON_FA_EJECT "\xef\x81\x92" // U+f052 +#define ICON_FA_ELEVATOR "\xee\x85\xad" // U+e16d +#define ICON_FA_ELLIPSIS "\xef\x85\x81" // U+f141 +#define ICON_FA_ELLIPSIS_VERTICAL "\xef\x85\x82" // U+f142 +#define ICON_FA_ENVELOPE "\xef\x83\xa0" // U+f0e0 +#define ICON_FA_ENVELOPE_CIRCLE_CHECK "\xee\x93\xa8" // U+e4e8 +#define ICON_FA_ENVELOPE_OPEN "\xef\x8a\xb6" // U+f2b6 +#define ICON_FA_ENVELOPE_OPEN_TEXT "\xef\x99\x98" // U+f658 +#define ICON_FA_ENVELOPES_BULK "\xef\x99\xb4" // U+f674 +#define ICON_FA_EQUALS "=" // U+3d +#define ICON_FA_ERASER "\xef\x84\xad" // U+f12d +#define ICON_FA_ETHERNET "\xef\x9e\x96" // U+f796 +#define ICON_FA_EURO_SIGN "\xef\x85\x93" // U+f153 +#define ICON_FA_EXCLAMATION "!" // U+21 +#define ICON_FA_EXPAND "\xef\x81\xa5" // U+f065 +#define ICON_FA_EXPLOSION "\xee\x93\xa9" // U+e4e9 +#define ICON_FA_EYE "\xef\x81\xae" // U+f06e +#define ICON_FA_EYE_DROPPER "\xef\x87\xbb" // U+f1fb +#define ICON_FA_EYE_LOW_VISION "\xef\x8a\xa8" // U+f2a8 +#define ICON_FA_EYE_SLASH "\xef\x81\xb0" // U+f070 +#define ICON_FA_F "F" // U+46 +#define ICON_FA_FACE_ANGRY "\xef\x95\x96" // U+f556 +#define ICON_FA_FACE_DIZZY "\xef\x95\xa7" // U+f567 +#define ICON_FA_FACE_FLUSHED "\xef\x95\xb9" // U+f579 +#define ICON_FA_FACE_FROWN "\xef\x84\x99" // U+f119 +#define ICON_FA_FACE_FROWN_OPEN "\xef\x95\xba" // U+f57a +#define ICON_FA_FACE_GRIMACE "\xef\x95\xbf" // U+f57f +#define ICON_FA_FACE_GRIN "\xef\x96\x80" // U+f580 +#define ICON_FA_FACE_GRIN_BEAM "\xef\x96\x82" // U+f582 +#define ICON_FA_FACE_GRIN_BEAM_SWEAT "\xef\x96\x83" // U+f583 +#define ICON_FA_FACE_GRIN_HEARTS "\xef\x96\x84" // U+f584 +#define ICON_FA_FACE_GRIN_SQUINT "\xef\x96\x85" // U+f585 +#define ICON_FA_FACE_GRIN_SQUINT_TEARS "\xef\x96\x86" // U+f586 +#define ICON_FA_FACE_GRIN_STARS "\xef\x96\x87" // U+f587 +#define ICON_FA_FACE_GRIN_TEARS "\xef\x96\x88" // U+f588 +#define ICON_FA_FACE_GRIN_TONGUE "\xef\x96\x89" // U+f589 +#define ICON_FA_FACE_GRIN_TONGUE_SQUINT "\xef\x96\x8a" // U+f58a +#define ICON_FA_FACE_GRIN_TONGUE_WINK "\xef\x96\x8b" // U+f58b +#define ICON_FA_FACE_GRIN_WIDE "\xef\x96\x81" // U+f581 +#define ICON_FA_FACE_GRIN_WINK "\xef\x96\x8c" // U+f58c +#define ICON_FA_FACE_KISS "\xef\x96\x96" // U+f596 +#define ICON_FA_FACE_KISS_BEAM "\xef\x96\x97" // U+f597 +#define ICON_FA_FACE_KISS_WINK_HEART "\xef\x96\x98" // U+f598 +#define ICON_FA_FACE_LAUGH "\xef\x96\x99" // U+f599 +#define ICON_FA_FACE_LAUGH_BEAM "\xef\x96\x9a" // U+f59a +#define ICON_FA_FACE_LAUGH_SQUINT "\xef\x96\x9b" // U+f59b +#define ICON_FA_FACE_LAUGH_WINK "\xef\x96\x9c" // U+f59c +#define ICON_FA_FACE_MEH "\xef\x84\x9a" // U+f11a +#define ICON_FA_FACE_MEH_BLANK "\xef\x96\xa4" // U+f5a4 +#define ICON_FA_FACE_ROLLING_EYES "\xef\x96\xa5" // U+f5a5 +#define ICON_FA_FACE_SAD_CRY "\xef\x96\xb3" // U+f5b3 +#define ICON_FA_FACE_SAD_TEAR "\xef\x96\xb4" // U+f5b4 +#define ICON_FA_FACE_SMILE "\xef\x84\x98" // U+f118 +#define ICON_FA_FACE_SMILE_BEAM "\xef\x96\xb8" // U+f5b8 +#define ICON_FA_FACE_SMILE_WINK "\xef\x93\x9a" // U+f4da +#define ICON_FA_FACE_SURPRISE "\xef\x97\x82" // U+f5c2 +#define ICON_FA_FACE_TIRED "\xef\x97\x88" // U+f5c8 +#define ICON_FA_FAN "\xef\xa1\xa3" // U+f863 +#define ICON_FA_FAUCET "\xee\x80\x85" // U+e005 +#define ICON_FA_FAUCET_DRIP "\xee\x80\x86" // U+e006 +#define ICON_FA_FAX "\xef\x86\xac" // U+f1ac +#define ICON_FA_FEATHER "\xef\x94\xad" // U+f52d +#define ICON_FA_FEATHER_POINTED "\xef\x95\xab" // U+f56b +#define ICON_FA_FERRY "\xee\x93\xaa" // U+e4ea +#define ICON_FA_FILE "\xef\x85\x9b" // U+f15b +#define ICON_FA_FILE_ARROW_DOWN "\xef\x95\xad" // U+f56d +#define ICON_FA_FILE_ARROW_UP "\xef\x95\xb4" // U+f574 +#define ICON_FA_FILE_AUDIO "\xef\x87\x87" // U+f1c7 +#define ICON_FA_FILE_CIRCLE_CHECK "\xee\x92\x93" // U+e493 +#define ICON_FA_FILE_CIRCLE_EXCLAMATION "\xee\x93\xab" // U+e4eb +#define ICON_FA_FILE_CIRCLE_MINUS "\xee\x93\xad" // U+e4ed +#define ICON_FA_FILE_CIRCLE_PLUS "\xee\x93\xae" // U+e4ee +#define ICON_FA_FILE_CIRCLE_QUESTION "\xee\x93\xaf" // U+e4ef +#define ICON_FA_FILE_CIRCLE_XMARK "\xee\x92\x94" // U+e494 +#define ICON_FA_FILE_CODE "\xef\x87\x89" // U+f1c9 +#define ICON_FA_FILE_CONTRACT "\xef\x95\xac" // U+f56c +#define ICON_FA_FILE_CSV "\xef\x9b\x9d" // U+f6dd +#define ICON_FA_FILE_EXCEL "\xef\x87\x83" // U+f1c3 +#define ICON_FA_FILE_EXPORT "\xef\x95\xae" // U+f56e +#define ICON_FA_FILE_IMAGE "\xef\x87\x85" // U+f1c5 +#define ICON_FA_FILE_IMPORT "\xef\x95\xaf" // U+f56f +#define ICON_FA_FILE_INVOICE "\xef\x95\xb0" // U+f570 +#define ICON_FA_FILE_INVOICE_DOLLAR "\xef\x95\xb1" // U+f571 +#define ICON_FA_FILE_LINES "\xef\x85\x9c" // U+f15c +#define ICON_FA_FILE_MEDICAL "\xef\x91\xb7" // U+f477 +#define ICON_FA_FILE_PDF "\xef\x87\x81" // U+f1c1 +#define ICON_FA_FILE_PEN "\xef\x8c\x9c" // U+f31c +#define ICON_FA_FILE_POWERPOINT "\xef\x87\x84" // U+f1c4 +#define ICON_FA_FILE_PRESCRIPTION "\xef\x95\xb2" // U+f572 +#define ICON_FA_FILE_SHIELD "\xee\x93\xb0" // U+e4f0 +#define ICON_FA_FILE_SIGNATURE "\xef\x95\xb3" // U+f573 +#define ICON_FA_FILE_VIDEO "\xef\x87\x88" // U+f1c8 +#define ICON_FA_FILE_WAVEFORM "\xef\x91\xb8" // U+f478 +#define ICON_FA_FILE_WORD "\xef\x87\x82" // U+f1c2 +#define ICON_FA_FILE_ZIPPER "\xef\x87\x86" // U+f1c6 +#define ICON_FA_FILL "\xef\x95\xb5" // U+f575 +#define ICON_FA_FILL_DRIP "\xef\x95\xb6" // U+f576 +#define ICON_FA_FILM "\xef\x80\x88" // U+f008 +#define ICON_FA_FILTER "\xef\x82\xb0" // U+f0b0 +#define ICON_FA_FILTER_CIRCLE_DOLLAR "\xef\x99\xa2" // U+f662 +#define ICON_FA_FILTER_CIRCLE_XMARK "\xee\x85\xbb" // U+e17b +#define ICON_FA_FINGERPRINT "\xef\x95\xb7" // U+f577 +#define ICON_FA_FIRE "\xef\x81\xad" // U+f06d +#define ICON_FA_FIRE_BURNER "\xee\x93\xb1" // U+e4f1 +#define ICON_FA_FIRE_EXTINGUISHER "\xef\x84\xb4" // U+f134 +#define ICON_FA_FIRE_FLAME_CURVED "\xef\x9f\xa4" // U+f7e4 +#define ICON_FA_FIRE_FLAME_SIMPLE "\xef\x91\xaa" // U+f46a +#define ICON_FA_FISH "\xef\x95\xb8" // U+f578 +#define ICON_FA_FISH_FINS "\xee\x93\xb2" // U+e4f2 +#define ICON_FA_FLAG "\xef\x80\xa4" // U+f024 +#define ICON_FA_FLAG_CHECKERED "\xef\x84\x9e" // U+f11e +#define ICON_FA_FLAG_USA "\xef\x9d\x8d" // U+f74d +#define ICON_FA_FLASK "\xef\x83\x83" // U+f0c3 +#define ICON_FA_FLASK_VIAL "\xee\x93\xb3" // U+e4f3 +#define ICON_FA_FLOPPY_DISK "\xef\x83\x87" // U+f0c7 +#define ICON_FA_FLORIN_SIGN "\xee\x86\x84" // U+e184 +#define ICON_FA_FOLDER "\xef\x81\xbb" // U+f07b +#define ICON_FA_FOLDER_CLOSED "\xee\x86\x85" // U+e185 +#define ICON_FA_FOLDER_MINUS "\xef\x99\x9d" // U+f65d +#define ICON_FA_FOLDER_OPEN "\xef\x81\xbc" // U+f07c +#define ICON_FA_FOLDER_PLUS "\xef\x99\x9e" // U+f65e +#define ICON_FA_FOLDER_TREE "\xef\xa0\x82" // U+f802 +#define ICON_FA_FONT "\xef\x80\xb1" // U+f031 +#define ICON_FA_FONT_AWESOME "\xef\x8a\xb4" // U+f2b4 +#define ICON_FA_FOOTBALL "\xef\x91\x8e" // U+f44e +#define ICON_FA_FORWARD "\xef\x81\x8e" // U+f04e +#define ICON_FA_FORWARD_FAST "\xef\x81\x90" // U+f050 +#define ICON_FA_FORWARD_STEP "\xef\x81\x91" // U+f051 +#define ICON_FA_FRANC_SIGN "\xee\x86\x8f" // U+e18f +#define ICON_FA_FROG "\xef\x94\xae" // U+f52e +#define ICON_FA_FUTBOL "\xef\x87\xa3" // U+f1e3 +#define ICON_FA_G "G" // U+47 +#define ICON_FA_GAMEPAD "\xef\x84\x9b" // U+f11b +#define ICON_FA_GAS_PUMP "\xef\x94\xaf" // U+f52f +#define ICON_FA_GAUGE "\xef\x98\xa4" // U+f624 +#define ICON_FA_GAUGE_HIGH "\xef\x98\xa5" // U+f625 +#define ICON_FA_GAUGE_SIMPLE "\xef\x98\xa9" // U+f629 +#define ICON_FA_GAUGE_SIMPLE_HIGH "\xef\x98\xaa" // U+f62a +#define ICON_FA_GAVEL "\xef\x83\xa3" // U+f0e3 +#define ICON_FA_GEAR "\xef\x80\x93" // U+f013 +#define ICON_FA_GEARS "\xef\x82\x85" // U+f085 +#define ICON_FA_GEM "\xef\x8e\xa5" // U+f3a5 +#define ICON_FA_GENDERLESS "\xef\x88\xad" // U+f22d +#define ICON_FA_GHOST "\xef\x9b\xa2" // U+f6e2 +#define ICON_FA_GIFT "\xef\x81\xab" // U+f06b +#define ICON_FA_GIFTS "\xef\x9e\x9c" // U+f79c +#define ICON_FA_GLASS_WATER "\xee\x93\xb4" // U+e4f4 +#define ICON_FA_GLASS_WATER_DROPLET "\xee\x93\xb5" // U+e4f5 +#define ICON_FA_GLASSES "\xef\x94\xb0" // U+f530 +#define ICON_FA_GLOBE "\xef\x82\xac" // U+f0ac +#define ICON_FA_GOLF_BALL_TEE "\xef\x91\x90" // U+f450 +#define ICON_FA_GOPURAM "\xef\x99\xa4" // U+f664 +#define ICON_FA_GRADUATION_CAP "\xef\x86\x9d" // U+f19d +#define ICON_FA_GREATER_THAN ">" // U+3e +#define ICON_FA_GREATER_THAN_EQUAL "\xef\x94\xb2" // U+f532 +#define ICON_FA_GRIP "\xef\x96\x8d" // U+f58d +#define ICON_FA_GRIP_LINES "\xef\x9e\xa4" // U+f7a4 +#define ICON_FA_GRIP_LINES_VERTICAL "\xef\x9e\xa5" // U+f7a5 +#define ICON_FA_GRIP_VERTICAL "\xef\x96\x8e" // U+f58e +#define ICON_FA_GROUP_ARROWS_ROTATE "\xee\x93\xb6" // U+e4f6 +#define ICON_FA_GUARANI_SIGN "\xee\x86\x9a" // U+e19a +#define ICON_FA_GUITAR "\xef\x9e\xa6" // U+f7a6 +#define ICON_FA_GUN "\xee\x86\x9b" // U+e19b +#define ICON_FA_H "H" // U+48 +#define ICON_FA_HAMMER "\xef\x9b\xa3" // U+f6e3 +#define ICON_FA_HAMSA "\xef\x99\xa5" // U+f665 +#define ICON_FA_HAND "\xef\x89\x96" // U+f256 +#define ICON_FA_HAND_BACK_FIST "\xef\x89\x95" // U+f255 +#define ICON_FA_HAND_DOTS "\xef\x91\xa1" // U+f461 +#define ICON_FA_HAND_FIST "\xef\x9b\x9e" // U+f6de +#define ICON_FA_HAND_HOLDING "\xef\x92\xbd" // U+f4bd +#define ICON_FA_HAND_HOLDING_DOLLAR "\xef\x93\x80" // U+f4c0 +#define ICON_FA_HAND_HOLDING_DROPLET "\xef\x93\x81" // U+f4c1 +#define ICON_FA_HAND_HOLDING_HAND "\xee\x93\xb7" // U+e4f7 +#define ICON_FA_HAND_HOLDING_HEART "\xef\x92\xbe" // U+f4be +#define ICON_FA_HAND_HOLDING_MEDICAL "\xee\x81\x9c" // U+e05c +#define ICON_FA_HAND_LIZARD "\xef\x89\x98" // U+f258 +#define ICON_FA_HAND_MIDDLE_FINGER "\xef\xa0\x86" // U+f806 +#define ICON_FA_HAND_PEACE "\xef\x89\x9b" // U+f25b +#define ICON_FA_HAND_POINT_DOWN "\xef\x82\xa7" // U+f0a7 +#define ICON_FA_HAND_POINT_LEFT "\xef\x82\xa5" // U+f0a5 +#define ICON_FA_HAND_POINT_RIGHT "\xef\x82\xa4" // U+f0a4 +#define ICON_FA_HAND_POINT_UP "\xef\x82\xa6" // U+f0a6 +#define ICON_FA_HAND_POINTER "\xef\x89\x9a" // U+f25a +#define ICON_FA_HAND_SCISSORS "\xef\x89\x97" // U+f257 +#define ICON_FA_HAND_SPARKLES "\xee\x81\x9d" // U+e05d +#define ICON_FA_HAND_SPOCK "\xef\x89\x99" // U+f259 +#define ICON_FA_HANDCUFFS "\xee\x93\xb8" // U+e4f8 +#define ICON_FA_HANDS "\xef\x8a\xa7" // U+f2a7 +#define ICON_FA_HANDS_ASL_INTERPRETING "\xef\x8a\xa3" // U+f2a3 +#define ICON_FA_HANDS_BOUND "\xee\x93\xb9" // U+e4f9 +#define ICON_FA_HANDS_BUBBLES "\xee\x81\x9e" // U+e05e +#define ICON_FA_HANDS_CLAPPING "\xee\x86\xa8" // U+e1a8 +#define ICON_FA_HANDS_HOLDING "\xef\x93\x82" // U+f4c2 +#define ICON_FA_HANDS_HOLDING_CHILD "\xee\x93\xba" // U+e4fa +#define ICON_FA_HANDS_HOLDING_CIRCLE "\xee\x93\xbb" // U+e4fb +#define ICON_FA_HANDS_PRAYING "\xef\x9a\x84" // U+f684 +#define ICON_FA_HANDSHAKE "\xef\x8a\xb5" // U+f2b5 +#define ICON_FA_HANDSHAKE_ANGLE "\xef\x93\x84" // U+f4c4 +#define ICON_FA_HANDSHAKE_SIMPLE "\xef\x93\x86" // U+f4c6 +#define ICON_FA_HANDSHAKE_SIMPLE_SLASH "\xee\x81\x9f" // U+e05f +#define ICON_FA_HANDSHAKE_SLASH "\xee\x81\xa0" // U+e060 +#define ICON_FA_HANUKIAH "\xef\x9b\xa6" // U+f6e6 +#define ICON_FA_HARD_DRIVE "\xef\x82\xa0" // U+f0a0 +#define ICON_FA_HASHTAG "#" // U+23 +#define ICON_FA_HAT_COWBOY "\xef\xa3\x80" // U+f8c0 +#define ICON_FA_HAT_COWBOY_SIDE "\xef\xa3\x81" // U+f8c1 +#define ICON_FA_HAT_WIZARD "\xef\x9b\xa8" // U+f6e8 +#define ICON_FA_HEAD_SIDE_COUGH "\xee\x81\xa1" // U+e061 +#define ICON_FA_HEAD_SIDE_COUGH_SLASH "\xee\x81\xa2" // U+e062 +#define ICON_FA_HEAD_SIDE_MASK "\xee\x81\xa3" // U+e063 +#define ICON_FA_HEAD_SIDE_VIRUS "\xee\x81\xa4" // U+e064 +#define ICON_FA_HEADING "\xef\x87\x9c" // U+f1dc +#define ICON_FA_HEADPHONES "\xef\x80\xa5" // U+f025 +#define ICON_FA_HEADPHONES_SIMPLE "\xef\x96\x8f" // U+f58f +#define ICON_FA_HEADSET "\xef\x96\x90" // U+f590 +#define ICON_FA_HEART "\xef\x80\x84" // U+f004 +#define ICON_FA_HEART_CIRCLE_BOLT "\xee\x93\xbc" // U+e4fc +#define ICON_FA_HEART_CIRCLE_CHECK "\xee\x93\xbd" // U+e4fd +#define ICON_FA_HEART_CIRCLE_EXCLAMATION "\xee\x93\xbe" // U+e4fe +#define ICON_FA_HEART_CIRCLE_MINUS "\xee\x93\xbf" // U+e4ff +#define ICON_FA_HEART_CIRCLE_PLUS "\xee\x94\x80" // U+e500 +#define ICON_FA_HEART_CIRCLE_XMARK "\xee\x94\x81" // U+e501 +#define ICON_FA_HEART_CRACK "\xef\x9e\xa9" // U+f7a9 +#define ICON_FA_HEART_PULSE "\xef\x88\x9e" // U+f21e +#define ICON_FA_HELICOPTER "\xef\x94\xb3" // U+f533 +#define ICON_FA_HELICOPTER_SYMBOL "\xee\x94\x82" // U+e502 +#define ICON_FA_HELMET_SAFETY "\xef\xa0\x87" // U+f807 +#define ICON_FA_HELMET_UN "\xee\x94\x83" // U+e503 +#define ICON_FA_HIGHLIGHTER "\xef\x96\x91" // U+f591 +#define ICON_FA_HILL_AVALANCHE "\xee\x94\x87" // U+e507 +#define ICON_FA_HILL_ROCKSLIDE "\xee\x94\x88" // U+e508 +#define ICON_FA_HIPPO "\xef\x9b\xad" // U+f6ed +#define ICON_FA_HOCKEY_PUCK "\xef\x91\x93" // U+f453 +#define ICON_FA_HOLLY_BERRY "\xef\x9e\xaa" // U+f7aa +#define ICON_FA_HORSE "\xef\x9b\xb0" // U+f6f0 +#define ICON_FA_HORSE_HEAD "\xef\x9e\xab" // U+f7ab +#define ICON_FA_HOSPITAL "\xef\x83\xb8" // U+f0f8 +#define ICON_FA_HOSPITAL_USER "\xef\xa0\x8d" // U+f80d +#define ICON_FA_HOT_TUB_PERSON "\xef\x96\x93" // U+f593 +#define ICON_FA_HOTDOG "\xef\xa0\x8f" // U+f80f +#define ICON_FA_HOTEL "\xef\x96\x94" // U+f594 +#define ICON_FA_HOURGLASS "\xef\x89\x94" // U+f254 +#define ICON_FA_HOURGLASS_EMPTY "\xef\x89\x92" // U+f252 +#define ICON_FA_HOURGLASS_END "\xef\x89\x93" // U+f253 +#define ICON_FA_HOURGLASS_START "\xef\x89\x91" // U+f251 +#define ICON_FA_HOUSE "\xef\x80\x95" // U+f015 +#define ICON_FA_HOUSE_CHIMNEY "\xee\x8e\xaf" // U+e3af +#define ICON_FA_HOUSE_CHIMNEY_CRACK "\xef\x9b\xb1" // U+f6f1 +#define ICON_FA_HOUSE_CHIMNEY_MEDICAL "\xef\x9f\xb2" // U+f7f2 +#define ICON_FA_HOUSE_CHIMNEY_USER "\xee\x81\xa5" // U+e065 +#define ICON_FA_HOUSE_CHIMNEY_WINDOW "\xee\x80\x8d" // U+e00d +#define ICON_FA_HOUSE_CIRCLE_CHECK "\xee\x94\x89" // U+e509 +#define ICON_FA_HOUSE_CIRCLE_EXCLAMATION "\xee\x94\x8a" // U+e50a +#define ICON_FA_HOUSE_CIRCLE_XMARK "\xee\x94\x8b" // U+e50b +#define ICON_FA_HOUSE_CRACK "\xee\x8e\xb1" // U+e3b1 +#define ICON_FA_HOUSE_FIRE "\xee\x94\x8c" // U+e50c +#define ICON_FA_HOUSE_FLAG "\xee\x94\x8d" // U+e50d +#define ICON_FA_HOUSE_FLOOD_WATER "\xee\x94\x8e" // U+e50e +#define ICON_FA_HOUSE_FLOOD_WATER_CIRCLE_ARROW_RIGHT "\xee\x94\x8f" // U+e50f +#define ICON_FA_HOUSE_LAPTOP "\xee\x81\xa6" // U+e066 +#define ICON_FA_HOUSE_LOCK "\xee\x94\x90" // U+e510 +#define ICON_FA_HOUSE_MEDICAL "\xee\x8e\xb2" // U+e3b2 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_CHECK "\xee\x94\x91" // U+e511 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_EXCLAMATION "\xee\x94\x92" // U+e512 +#define ICON_FA_HOUSE_MEDICAL_CIRCLE_XMARK "\xee\x94\x93" // U+e513 +#define ICON_FA_HOUSE_MEDICAL_FLAG "\xee\x94\x94" // U+e514 +#define ICON_FA_HOUSE_SIGNAL "\xee\x80\x92" // U+e012 +#define ICON_FA_HOUSE_TSUNAMI "\xee\x94\x95" // U+e515 +#define ICON_FA_HOUSE_USER "\xee\x86\xb0" // U+e1b0 +#define ICON_FA_HRYVNIA_SIGN "\xef\x9b\xb2" // U+f6f2 +#define ICON_FA_HURRICANE "\xef\x9d\x91" // U+f751 +#define ICON_FA_I "I" // U+49 +#define ICON_FA_I_CURSOR "\xef\x89\x86" // U+f246 +#define ICON_FA_ICE_CREAM "\xef\xa0\x90" // U+f810 +#define ICON_FA_ICICLES "\xef\x9e\xad" // U+f7ad +#define ICON_FA_ICONS "\xef\xa1\xad" // U+f86d +#define ICON_FA_ID_BADGE "\xef\x8b\x81" // U+f2c1 +#define ICON_FA_ID_CARD "\xef\x8b\x82" // U+f2c2 +#define ICON_FA_ID_CARD_CLIP "\xef\x91\xbf" // U+f47f +#define ICON_FA_IGLOO "\xef\x9e\xae" // U+f7ae +#define ICON_FA_IMAGE "\xef\x80\xbe" // U+f03e +#define ICON_FA_IMAGE_PORTRAIT "\xef\x8f\xa0" // U+f3e0 +#define ICON_FA_IMAGES "\xef\x8c\x82" // U+f302 +#define ICON_FA_INBOX "\xef\x80\x9c" // U+f01c +#define ICON_FA_INDENT "\xef\x80\xbc" // U+f03c +#define ICON_FA_INDIAN_RUPEE_SIGN "\xee\x86\xbc" // U+e1bc +#define ICON_FA_INDUSTRY "\xef\x89\xb5" // U+f275 +#define ICON_FA_INFINITY "\xef\x94\xb4" // U+f534 +#define ICON_FA_INFO "\xef\x84\xa9" // U+f129 +#define ICON_FA_ITALIC "\xef\x80\xb3" // U+f033 +#define ICON_FA_J "J" // U+4a +#define ICON_FA_JAR "\xee\x94\x96" // U+e516 +#define ICON_FA_JAR_WHEAT "\xee\x94\x97" // U+e517 +#define ICON_FA_JEDI "\xef\x99\xa9" // U+f669 +#define ICON_FA_JET_FIGHTER "\xef\x83\xbb" // U+f0fb +#define ICON_FA_JET_FIGHTER_UP "\xee\x94\x98" // U+e518 +#define ICON_FA_JOINT "\xef\x96\x95" // U+f595 +#define ICON_FA_JUG_DETERGENT "\xee\x94\x99" // U+e519 +#define ICON_FA_K "K" // U+4b +#define ICON_FA_KAABA "\xef\x99\xab" // U+f66b +#define ICON_FA_KEY "\xef\x82\x84" // U+f084 +#define ICON_FA_KEYBOARD "\xef\x84\x9c" // U+f11c +#define ICON_FA_KHANDA "\xef\x99\xad" // U+f66d +#define ICON_FA_KIP_SIGN "\xee\x87\x84" // U+e1c4 +#define ICON_FA_KIT_MEDICAL "\xef\x91\xb9" // U+f479 +#define ICON_FA_KITCHEN_SET "\xee\x94\x9a" // U+e51a +#define ICON_FA_KIWI_BIRD "\xef\x94\xb5" // U+f535 +#define ICON_FA_L "L" // U+4c +#define ICON_FA_LAND_MINE_ON "\xee\x94\x9b" // U+e51b +#define ICON_FA_LANDMARK "\xef\x99\xaf" // U+f66f +#define ICON_FA_LANDMARK_DOME "\xef\x9d\x92" // U+f752 +#define ICON_FA_LANDMARK_FLAG "\xee\x94\x9c" // U+e51c +#define ICON_FA_LANGUAGE "\xef\x86\xab" // U+f1ab +#define ICON_FA_LAPTOP "\xef\x84\x89" // U+f109 +#define ICON_FA_LAPTOP_CODE "\xef\x97\xbc" // U+f5fc +#define ICON_FA_LAPTOP_FILE "\xee\x94\x9d" // U+e51d +#define ICON_FA_LAPTOP_MEDICAL "\xef\xa0\x92" // U+f812 +#define ICON_FA_LARI_SIGN "\xee\x87\x88" // U+e1c8 +#define ICON_FA_LAYER_GROUP "\xef\x97\xbd" // U+f5fd +#define ICON_FA_LEAF "\xef\x81\xac" // U+f06c +#define ICON_FA_LEFT_LONG "\xef\x8c\x8a" // U+f30a +#define ICON_FA_LEFT_RIGHT "\xef\x8c\xb7" // U+f337 +#define ICON_FA_LEMON "\xef\x82\x94" // U+f094 +#define ICON_FA_LESS_THAN "<" // U+3c +#define ICON_FA_LESS_THAN_EQUAL "\xef\x94\xb7" // U+f537 +#define ICON_FA_LIFE_RING "\xef\x87\x8d" // U+f1cd +#define ICON_FA_LIGHTBULB "\xef\x83\xab" // U+f0eb +#define ICON_FA_LINES_LEANING "\xee\x94\x9e" // U+e51e +#define ICON_FA_LINK "\xef\x83\x81" // U+f0c1 +#define ICON_FA_LINK_SLASH "\xef\x84\xa7" // U+f127 +#define ICON_FA_LIRA_SIGN "\xef\x86\x95" // U+f195 +#define ICON_FA_LIST "\xef\x80\xba" // U+f03a +#define ICON_FA_LIST_CHECK "\xef\x82\xae" // U+f0ae +#define ICON_FA_LIST_OL "\xef\x83\x8b" // U+f0cb +#define ICON_FA_LIST_UL "\xef\x83\x8a" // U+f0ca +#define ICON_FA_LITECOIN_SIGN "\xee\x87\x93" // U+e1d3 +#define ICON_FA_LOCATION_ARROW "\xef\x84\xa4" // U+f124 +#define ICON_FA_LOCATION_CROSSHAIRS "\xef\x98\x81" // U+f601 +#define ICON_FA_LOCATION_DOT "\xef\x8f\x85" // U+f3c5 +#define ICON_FA_LOCATION_PIN "\xef\x81\x81" // U+f041 +#define ICON_FA_LOCATION_PIN_LOCK "\xee\x94\x9f" // U+e51f +#define ICON_FA_LOCK "\xef\x80\xa3" // U+f023 +#define ICON_FA_LOCK_OPEN "\xef\x8f\x81" // U+f3c1 +#define ICON_FA_LOCUST "\xee\x94\xa0" // U+e520 +#define ICON_FA_LUNGS "\xef\x98\x84" // U+f604 +#define ICON_FA_LUNGS_VIRUS "\xee\x81\xa7" // U+e067 +#define ICON_FA_M "M" // U+4d +#define ICON_FA_MAGNET "\xef\x81\xb6" // U+f076 +#define ICON_FA_MAGNIFYING_GLASS "\xef\x80\x82" // U+f002 +#define ICON_FA_MAGNIFYING_GLASS_ARROW_RIGHT "\xee\x94\xa1" // U+e521 +#define ICON_FA_MAGNIFYING_GLASS_CHART "\xee\x94\xa2" // U+e522 +#define ICON_FA_MAGNIFYING_GLASS_DOLLAR "\xef\x9a\x88" // U+f688 +#define ICON_FA_MAGNIFYING_GLASS_LOCATION "\xef\x9a\x89" // U+f689 +#define ICON_FA_MAGNIFYING_GLASS_MINUS "\xef\x80\x90" // U+f010 +#define ICON_FA_MAGNIFYING_GLASS_PLUS "\xef\x80\x8e" // U+f00e +#define ICON_FA_MANAT_SIGN "\xee\x87\x95" // U+e1d5 +#define ICON_FA_MAP "\xef\x89\xb9" // U+f279 +#define ICON_FA_MAP_LOCATION "\xef\x96\x9f" // U+f59f +#define ICON_FA_MAP_LOCATION_DOT "\xef\x96\xa0" // U+f5a0 +#define ICON_FA_MAP_PIN "\xef\x89\xb6" // U+f276 +#define ICON_FA_MARKER "\xef\x96\xa1" // U+f5a1 +#define ICON_FA_MARS "\xef\x88\xa2" // U+f222 +#define ICON_FA_MARS_AND_VENUS "\xef\x88\xa4" // U+f224 +#define ICON_FA_MARS_AND_VENUS_BURST "\xee\x94\xa3" // U+e523 +#define ICON_FA_MARS_DOUBLE "\xef\x88\xa7" // U+f227 +#define ICON_FA_MARS_STROKE "\xef\x88\xa9" // U+f229 +#define ICON_FA_MARS_STROKE_RIGHT "\xef\x88\xab" // U+f22b +#define ICON_FA_MARS_STROKE_UP "\xef\x88\xaa" // U+f22a +#define ICON_FA_MARTINI_GLASS "\xef\x95\xbb" // U+f57b +#define ICON_FA_MARTINI_GLASS_CITRUS "\xef\x95\xa1" // U+f561 +#define ICON_FA_MARTINI_GLASS_EMPTY "\xef\x80\x80" // U+f000 +#define ICON_FA_MASK "\xef\x9b\xba" // U+f6fa +#define ICON_FA_MASK_FACE "\xee\x87\x97" // U+e1d7 +#define ICON_FA_MASK_VENTILATOR "\xee\x94\xa4" // U+e524 +#define ICON_FA_MASKS_THEATER "\xef\x98\xb0" // U+f630 +#define ICON_FA_MATTRESS_PILLOW "\xee\x94\xa5" // U+e525 +#define ICON_FA_MAXIMIZE "\xef\x8c\x9e" // U+f31e +#define ICON_FA_MEDAL "\xef\x96\xa2" // U+f5a2 +#define ICON_FA_MEMORY "\xef\x94\xb8" // U+f538 +#define ICON_FA_MENORAH "\xef\x99\xb6" // U+f676 +#define ICON_FA_MERCURY "\xef\x88\xa3" // U+f223 +#define ICON_FA_MESSAGE "\xef\x89\xba" // U+f27a +#define ICON_FA_METEOR "\xef\x9d\x93" // U+f753 +#define ICON_FA_MICROCHIP "\xef\x8b\x9b" // U+f2db +#define ICON_FA_MICROPHONE "\xef\x84\xb0" // U+f130 +#define ICON_FA_MICROPHONE_LINES "\xef\x8f\x89" // U+f3c9 +#define ICON_FA_MICROPHONE_LINES_SLASH "\xef\x94\xb9" // U+f539 +#define ICON_FA_MICROPHONE_SLASH "\xef\x84\xb1" // U+f131 +#define ICON_FA_MICROSCOPE "\xef\x98\x90" // U+f610 +#define ICON_FA_MILL_SIGN "\xee\x87\xad" // U+e1ed +#define ICON_FA_MINIMIZE "\xef\x9e\x8c" // U+f78c +#define ICON_FA_MINUS "\xef\x81\xa8" // U+f068 +#define ICON_FA_MITTEN "\xef\x9e\xb5" // U+f7b5 +#define ICON_FA_MOBILE "\xef\x8f\x8e" // U+f3ce +#define ICON_FA_MOBILE_BUTTON "\xef\x84\x8b" // U+f10b +#define ICON_FA_MOBILE_RETRO "\xee\x94\xa7" // U+e527 +#define ICON_FA_MOBILE_SCREEN "\xef\x8f\x8f" // U+f3cf +#define ICON_FA_MOBILE_SCREEN_BUTTON "\xef\x8f\x8d" // U+f3cd +#define ICON_FA_MONEY_BILL "\xef\x83\x96" // U+f0d6 +#define ICON_FA_MONEY_BILL_1 "\xef\x8f\x91" // U+f3d1 +#define ICON_FA_MONEY_BILL_1_WAVE "\xef\x94\xbb" // U+f53b +#define ICON_FA_MONEY_BILL_TRANSFER "\xee\x94\xa8" // U+e528 +#define ICON_FA_MONEY_BILL_TREND_UP "\xee\x94\xa9" // U+e529 +#define ICON_FA_MONEY_BILL_WAVE "\xef\x94\xba" // U+f53a +#define ICON_FA_MONEY_BILL_WHEAT "\xee\x94\xaa" // U+e52a +#define ICON_FA_MONEY_BILLS "\xee\x87\xb3" // U+e1f3 +#define ICON_FA_MONEY_CHECK "\xef\x94\xbc" // U+f53c +#define ICON_FA_MONEY_CHECK_DOLLAR "\xef\x94\xbd" // U+f53d +#define ICON_FA_MONUMENT "\xef\x96\xa6" // U+f5a6 +#define ICON_FA_MOON "\xef\x86\x86" // U+f186 +#define ICON_FA_MORTAR_PESTLE "\xef\x96\xa7" // U+f5a7 +#define ICON_FA_MOSQUE "\xef\x99\xb8" // U+f678 +#define ICON_FA_MOSQUITO "\xee\x94\xab" // U+e52b +#define ICON_FA_MOSQUITO_NET "\xee\x94\xac" // U+e52c +#define ICON_FA_MOTORCYCLE "\xef\x88\x9c" // U+f21c +#define ICON_FA_MOUND "\xee\x94\xad" // U+e52d +#define ICON_FA_MOUNTAIN "\xef\x9b\xbc" // U+f6fc +#define ICON_FA_MOUNTAIN_CITY "\xee\x94\xae" // U+e52e +#define ICON_FA_MOUNTAIN_SUN "\xee\x94\xaf" // U+e52f +#define ICON_FA_MUG_HOT "\xef\x9e\xb6" // U+f7b6 +#define ICON_FA_MUG_SAUCER "\xef\x83\xb4" // U+f0f4 +#define ICON_FA_MUSIC "\xef\x80\x81" // U+f001 +#define ICON_FA_N "N" // U+4e +#define ICON_FA_NAIRA_SIGN "\xee\x87\xb6" // U+e1f6 +#define ICON_FA_NETWORK_WIRED "\xef\x9b\xbf" // U+f6ff +#define ICON_FA_NEUTER "\xef\x88\xac" // U+f22c +#define ICON_FA_NEWSPAPER "\xef\x87\xaa" // U+f1ea +#define ICON_FA_NOT_EQUAL "\xef\x94\xbe" // U+f53e +#define ICON_FA_NOTE_STICKY "\xef\x89\x89" // U+f249 +#define ICON_FA_NOTES_MEDICAL "\xef\x92\x81" // U+f481 +#define ICON_FA_O "O" // U+4f +#define ICON_FA_OBJECT_GROUP "\xef\x89\x87" // U+f247 +#define ICON_FA_OBJECT_UNGROUP "\xef\x89\x88" // U+f248 +#define ICON_FA_OIL_CAN "\xef\x98\x93" // U+f613 +#define ICON_FA_OIL_WELL "\xee\x94\xb2" // U+e532 +#define ICON_FA_OM "\xef\x99\xb9" // U+f679 +#define ICON_FA_OTTER "\xef\x9c\x80" // U+f700 +#define ICON_FA_OUTDENT "\xef\x80\xbb" // U+f03b +#define ICON_FA_P "P" // U+50 +#define ICON_FA_PAGER "\xef\xa0\x95" // U+f815 +#define ICON_FA_PAINT_ROLLER "\xef\x96\xaa" // U+f5aa +#define ICON_FA_PAINTBRUSH "\xef\x87\xbc" // U+f1fc +#define ICON_FA_PALETTE "\xef\x94\xbf" // U+f53f +#define ICON_FA_PALLET "\xef\x92\x82" // U+f482 +#define ICON_FA_PANORAMA "\xee\x88\x89" // U+e209 +#define ICON_FA_PAPER_PLANE "\xef\x87\x98" // U+f1d8 +#define ICON_FA_PAPERCLIP "\xef\x83\x86" // U+f0c6 +#define ICON_FA_PARACHUTE_BOX "\xef\x93\x8d" // U+f4cd +#define ICON_FA_PARAGRAPH "\xef\x87\x9d" // U+f1dd +#define ICON_FA_PASSPORT "\xef\x96\xab" // U+f5ab +#define ICON_FA_PASTE "\xef\x83\xaa" // U+f0ea +#define ICON_FA_PAUSE "\xef\x81\x8c" // U+f04c +#define ICON_FA_PAW "\xef\x86\xb0" // U+f1b0 +#define ICON_FA_PEACE "\xef\x99\xbc" // U+f67c +#define ICON_FA_PEN "\xef\x8c\x84" // U+f304 +#define ICON_FA_PEN_CLIP "\xef\x8c\x85" // U+f305 +#define ICON_FA_PEN_FANCY "\xef\x96\xac" // U+f5ac +#define ICON_FA_PEN_NIB "\xef\x96\xad" // U+f5ad +#define ICON_FA_PEN_RULER "\xef\x96\xae" // U+f5ae +#define ICON_FA_PEN_TO_SQUARE "\xef\x81\x84" // U+f044 +#define ICON_FA_PENCIL "\xef\x8c\x83" // U+f303 +#define ICON_FA_PEOPLE_ARROWS_LEFT_RIGHT "\xee\x81\xa8" // U+e068 +#define ICON_FA_PEOPLE_CARRY_BOX "\xef\x93\x8e" // U+f4ce +#define ICON_FA_PEOPLE_GROUP "\xee\x94\xb3" // U+e533 +#define ICON_FA_PEOPLE_LINE "\xee\x94\xb4" // U+e534 +#define ICON_FA_PEOPLE_PULLING "\xee\x94\xb5" // U+e535 +#define ICON_FA_PEOPLE_ROBBERY "\xee\x94\xb6" // U+e536 +#define ICON_FA_PEOPLE_ROOF "\xee\x94\xb7" // U+e537 +#define ICON_FA_PEPPER_HOT "\xef\xa0\x96" // U+f816 +#define ICON_FA_PERCENT "%" // U+25 +#define ICON_FA_PERSON "\xef\x86\x83" // U+f183 +#define ICON_FA_PERSON_ARROW_DOWN_TO_LINE "\xee\x94\xb8" // U+e538 +#define ICON_FA_PERSON_ARROW_UP_FROM_LINE "\xee\x94\xb9" // U+e539 +#define ICON_FA_PERSON_BIKING "\xef\xa1\x8a" // U+f84a +#define ICON_FA_PERSON_BOOTH "\xef\x9d\x96" // U+f756 +#define ICON_FA_PERSON_BREASTFEEDING "\xee\x94\xba" // U+e53a +#define ICON_FA_PERSON_BURST "\xee\x94\xbb" // U+e53b +#define ICON_FA_PERSON_CANE "\xee\x94\xbc" // U+e53c +#define ICON_FA_PERSON_CHALKBOARD "\xee\x94\xbd" // U+e53d +#define ICON_FA_PERSON_CIRCLE_CHECK "\xee\x94\xbe" // U+e53e +#define ICON_FA_PERSON_CIRCLE_EXCLAMATION "\xee\x94\xbf" // U+e53f +#define ICON_FA_PERSON_CIRCLE_MINUS "\xee\x95\x80" // U+e540 +#define ICON_FA_PERSON_CIRCLE_PLUS "\xee\x95\x81" // U+e541 +#define ICON_FA_PERSON_CIRCLE_QUESTION "\xee\x95\x82" // U+e542 +#define ICON_FA_PERSON_CIRCLE_XMARK "\xee\x95\x83" // U+e543 +#define ICON_FA_PERSON_DIGGING "\xef\xa1\x9e" // U+f85e +#define ICON_FA_PERSON_DOTS_FROM_LINE "\xef\x91\xb0" // U+f470 +#define ICON_FA_PERSON_DRESS "\xef\x86\x82" // U+f182 +#define ICON_FA_PERSON_DRESS_BURST "\xee\x95\x84" // U+e544 +#define ICON_FA_PERSON_DROWNING "\xee\x95\x85" // U+e545 +#define ICON_FA_PERSON_FALLING "\xee\x95\x86" // U+e546 +#define ICON_FA_PERSON_FALLING_BURST "\xee\x95\x87" // U+e547 +#define ICON_FA_PERSON_HALF_DRESS "\xee\x95\x88" // U+e548 +#define ICON_FA_PERSON_HARASSING "\xee\x95\x89" // U+e549 +#define ICON_FA_PERSON_HIKING "\xef\x9b\xac" // U+f6ec +#define ICON_FA_PERSON_MILITARY_POINTING "\xee\x95\x8a" // U+e54a +#define ICON_FA_PERSON_MILITARY_RIFLE "\xee\x95\x8b" // U+e54b +#define ICON_FA_PERSON_MILITARY_TO_PERSON "\xee\x95\x8c" // U+e54c +#define ICON_FA_PERSON_PRAYING "\xef\x9a\x83" // U+f683 +#define ICON_FA_PERSON_PREGNANT "\xee\x8c\x9e" // U+e31e +#define ICON_FA_PERSON_RAYS "\xee\x95\x8d" // U+e54d +#define ICON_FA_PERSON_RIFLE "\xee\x95\x8e" // U+e54e +#define ICON_FA_PERSON_RUNNING "\xef\x9c\x8c" // U+f70c +#define ICON_FA_PERSON_SHELTER "\xee\x95\x8f" // U+e54f +#define ICON_FA_PERSON_SKATING "\xef\x9f\x85" // U+f7c5 +#define ICON_FA_PERSON_SKIING "\xef\x9f\x89" // U+f7c9 +#define ICON_FA_PERSON_SKIING_NORDIC "\xef\x9f\x8a" // U+f7ca +#define ICON_FA_PERSON_SNOWBOARDING "\xef\x9f\x8e" // U+f7ce +#define ICON_FA_PERSON_SWIMMING "\xef\x97\x84" // U+f5c4 +#define ICON_FA_PERSON_THROUGH_WINDOW "\xee\x90\xb3" // U+e433 +#define ICON_FA_PERSON_WALKING "\xef\x95\x94" // U+f554 +#define ICON_FA_PERSON_WALKING_ARROW_LOOP_LEFT "\xee\x95\x91" // U+e551 +#define ICON_FA_PERSON_WALKING_ARROW_RIGHT "\xee\x95\x92" // U+e552 +#define ICON_FA_PERSON_WALKING_DASHED_LINE_ARROW_RIGHT "\xee\x95\x93" // U+e553 +#define ICON_FA_PERSON_WALKING_LUGGAGE "\xee\x95\x94" // U+e554 +#define ICON_FA_PERSON_WALKING_WITH_CANE "\xef\x8a\x9d" // U+f29d +#define ICON_FA_PESETA_SIGN "\xee\x88\xa1" // U+e221 +#define ICON_FA_PESO_SIGN "\xee\x88\xa2" // U+e222 +#define ICON_FA_PHONE "\xef\x82\x95" // U+f095 +#define ICON_FA_PHONE_FLIP "\xef\xa1\xb9" // U+f879 +#define ICON_FA_PHONE_SLASH "\xef\x8f\x9d" // U+f3dd +#define ICON_FA_PHONE_VOLUME "\xef\x8a\xa0" // U+f2a0 +#define ICON_FA_PHOTO_FILM "\xef\xa1\xbc" // U+f87c +#define ICON_FA_PIGGY_BANK "\xef\x93\x93" // U+f4d3 +#define ICON_FA_PILLS "\xef\x92\x84" // U+f484 +#define ICON_FA_PIZZA_SLICE "\xef\xa0\x98" // U+f818 +#define ICON_FA_PLACE_OF_WORSHIP "\xef\x99\xbf" // U+f67f +#define ICON_FA_PLANE "\xef\x81\xb2" // U+f072 +#define ICON_FA_PLANE_ARRIVAL "\xef\x96\xaf" // U+f5af +#define ICON_FA_PLANE_CIRCLE_CHECK "\xee\x95\x95" // U+e555 +#define ICON_FA_PLANE_CIRCLE_EXCLAMATION "\xee\x95\x96" // U+e556 +#define ICON_FA_PLANE_CIRCLE_XMARK "\xee\x95\x97" // U+e557 +#define ICON_FA_PLANE_DEPARTURE "\xef\x96\xb0" // U+f5b0 +#define ICON_FA_PLANE_LOCK "\xee\x95\x98" // U+e558 +#define ICON_FA_PLANE_SLASH "\xee\x81\xa9" // U+e069 +#define ICON_FA_PLANE_UP "\xee\x88\xad" // U+e22d +#define ICON_FA_PLANT_WILT "\xee\x90\xbb" // U+e43b +#define ICON_FA_PLATE_WHEAT "\xee\x95\x9a" // U+e55a +#define ICON_FA_PLAY "\xef\x81\x8b" // U+f04b +#define ICON_FA_PLUG "\xef\x87\xa6" // U+f1e6 +#define ICON_FA_PLUG_CIRCLE_BOLT "\xee\x95\x9b" // U+e55b +#define ICON_FA_PLUG_CIRCLE_CHECK "\xee\x95\x9c" // U+e55c +#define ICON_FA_PLUG_CIRCLE_EXCLAMATION "\xee\x95\x9d" // U+e55d +#define ICON_FA_PLUG_CIRCLE_MINUS "\xee\x95\x9e" // U+e55e +#define ICON_FA_PLUG_CIRCLE_PLUS "\xee\x95\x9f" // U+e55f +#define ICON_FA_PLUG_CIRCLE_XMARK "\xee\x95\xa0" // U+e560 +#define ICON_FA_PLUS "+" // U+2b +#define ICON_FA_PLUS_MINUS "\xee\x90\xbc" // U+e43c +#define ICON_FA_PODCAST "\xef\x8b\x8e" // U+f2ce +#define ICON_FA_POO "\xef\x8b\xbe" // U+f2fe +#define ICON_FA_POO_STORM "\xef\x9d\x9a" // U+f75a +#define ICON_FA_POOP "\xef\x98\x99" // U+f619 +#define ICON_FA_POWER_OFF "\xef\x80\x91" // U+f011 +#define ICON_FA_PRESCRIPTION "\xef\x96\xb1" // U+f5b1 +#define ICON_FA_PRESCRIPTION_BOTTLE "\xef\x92\x85" // U+f485 +#define ICON_FA_PRESCRIPTION_BOTTLE_MEDICAL "\xef\x92\x86" // U+f486 +#define ICON_FA_PRINT "\xef\x80\xaf" // U+f02f +#define ICON_FA_PUMP_MEDICAL "\xee\x81\xaa" // U+e06a +#define ICON_FA_PUMP_SOAP "\xee\x81\xab" // U+e06b +#define ICON_FA_PUZZLE_PIECE "\xef\x84\xae" // U+f12e +#define ICON_FA_Q "Q" // U+51 +#define ICON_FA_QRCODE "\xef\x80\xa9" // U+f029 +#define ICON_FA_QUESTION "?" // U+3f +#define ICON_FA_QUOTE_LEFT "\xef\x84\x8d" // U+f10d +#define ICON_FA_QUOTE_RIGHT "\xef\x84\x8e" // U+f10e +#define ICON_FA_R "R" // U+52 +#define ICON_FA_RADIATION "\xef\x9e\xb9" // U+f7b9 +#define ICON_FA_RADIO "\xef\xa3\x97" // U+f8d7 +#define ICON_FA_RAINBOW "\xef\x9d\x9b" // U+f75b +#define ICON_FA_RANKING_STAR "\xee\x95\xa1" // U+e561 +#define ICON_FA_RECEIPT "\xef\x95\x83" // U+f543 +#define ICON_FA_RECORD_VINYL "\xef\xa3\x99" // U+f8d9 +#define ICON_FA_RECTANGLE_AD "\xef\x99\x81" // U+f641 +#define ICON_FA_RECTANGLE_LIST "\xef\x80\xa2" // U+f022 +#define ICON_FA_RECTANGLE_XMARK "\xef\x90\x90" // U+f410 +#define ICON_FA_RECYCLE "\xef\x86\xb8" // U+f1b8 +#define ICON_FA_REGISTERED "\xef\x89\x9d" // U+f25d +#define ICON_FA_REPEAT "\xef\x8d\xa3" // U+f363 +#define ICON_FA_REPLY "\xef\x8f\xa5" // U+f3e5 +#define ICON_FA_REPLY_ALL "\xef\x84\xa2" // U+f122 +#define ICON_FA_REPUBLICAN "\xef\x9d\x9e" // U+f75e +#define ICON_FA_RESTROOM "\xef\x9e\xbd" // U+f7bd +#define ICON_FA_RETWEET "\xef\x81\xb9" // U+f079 +#define ICON_FA_RIBBON "\xef\x93\x96" // U+f4d6 +#define ICON_FA_RIGHT_FROM_BRACKET "\xef\x8b\xb5" // U+f2f5 +#define ICON_FA_RIGHT_LEFT "\xef\x8d\xa2" // U+f362 +#define ICON_FA_RIGHT_LONG "\xef\x8c\x8b" // U+f30b +#define ICON_FA_RIGHT_TO_BRACKET "\xef\x8b\xb6" // U+f2f6 +#define ICON_FA_RING "\xef\x9c\x8b" // U+f70b +#define ICON_FA_ROAD "\xef\x80\x98" // U+f018 +#define ICON_FA_ROAD_BARRIER "\xee\x95\xa2" // U+e562 +#define ICON_FA_ROAD_BRIDGE "\xee\x95\xa3" // U+e563 +#define ICON_FA_ROAD_CIRCLE_CHECK "\xee\x95\xa4" // U+e564 +#define ICON_FA_ROAD_CIRCLE_EXCLAMATION "\xee\x95\xa5" // U+e565 +#define ICON_FA_ROAD_CIRCLE_XMARK "\xee\x95\xa6" // U+e566 +#define ICON_FA_ROAD_LOCK "\xee\x95\xa7" // U+e567 +#define ICON_FA_ROAD_SPIKES "\xee\x95\xa8" // U+e568 +#define ICON_FA_ROBOT "\xef\x95\x84" // U+f544 +#define ICON_FA_ROCKET "\xef\x84\xb5" // U+f135 +#define ICON_FA_ROTATE "\xef\x8b\xb1" // U+f2f1 +#define ICON_FA_ROTATE_LEFT "\xef\x8b\xaa" // U+f2ea +#define ICON_FA_ROTATE_RIGHT "\xef\x8b\xb9" // U+f2f9 +#define ICON_FA_ROUTE "\xef\x93\x97" // U+f4d7 +#define ICON_FA_RSS "\xef\x82\x9e" // U+f09e +#define ICON_FA_RUBLE_SIGN "\xef\x85\x98" // U+f158 +#define ICON_FA_RUG "\xee\x95\xa9" // U+e569 +#define ICON_FA_RULER "\xef\x95\x85" // U+f545 +#define ICON_FA_RULER_COMBINED "\xef\x95\x86" // U+f546 +#define ICON_FA_RULER_HORIZONTAL "\xef\x95\x87" // U+f547 +#define ICON_FA_RULER_VERTICAL "\xef\x95\x88" // U+f548 +#define ICON_FA_RUPEE_SIGN "\xef\x85\x96" // U+f156 +#define ICON_FA_RUPIAH_SIGN "\xee\x88\xbd" // U+e23d +#define ICON_FA_S "S" // U+53 +#define ICON_FA_SACK_DOLLAR "\xef\xa0\x9d" // U+f81d +#define ICON_FA_SACK_XMARK "\xee\x95\xaa" // U+e56a +#define ICON_FA_SAILBOAT "\xee\x91\x85" // U+e445 +#define ICON_FA_SATELLITE "\xef\x9e\xbf" // U+f7bf +#define ICON_FA_SATELLITE_DISH "\xef\x9f\x80" // U+f7c0 +#define ICON_FA_SCALE_BALANCED "\xef\x89\x8e" // U+f24e +#define ICON_FA_SCALE_UNBALANCED "\xef\x94\x95" // U+f515 +#define ICON_FA_SCALE_UNBALANCED_FLIP "\xef\x94\x96" // U+f516 +#define ICON_FA_SCHOOL "\xef\x95\x89" // U+f549 +#define ICON_FA_SCHOOL_CIRCLE_CHECK "\xee\x95\xab" // U+e56b +#define ICON_FA_SCHOOL_CIRCLE_EXCLAMATION "\xee\x95\xac" // U+e56c +#define ICON_FA_SCHOOL_CIRCLE_XMARK "\xee\x95\xad" // U+e56d +#define ICON_FA_SCHOOL_FLAG "\xee\x95\xae" // U+e56e +#define ICON_FA_SCHOOL_LOCK "\xee\x95\xaf" // U+e56f +#define ICON_FA_SCISSORS "\xef\x83\x84" // U+f0c4 +#define ICON_FA_SCREWDRIVER "\xef\x95\x8a" // U+f54a +#define ICON_FA_SCREWDRIVER_WRENCH "\xef\x9f\x99" // U+f7d9 +#define ICON_FA_SCROLL "\xef\x9c\x8e" // U+f70e +#define ICON_FA_SCROLL_TORAH "\xef\x9a\xa0" // U+f6a0 +#define ICON_FA_SD_CARD "\xef\x9f\x82" // U+f7c2 +#define ICON_FA_SECTION "\xee\x91\x87" // U+e447 +#define ICON_FA_SEEDLING "\xef\x93\x98" // U+f4d8 +#define ICON_FA_SERVER "\xef\x88\xb3" // U+f233 +#define ICON_FA_SHAPES "\xef\x98\x9f" // U+f61f +#define ICON_FA_SHARE "\xef\x81\xa4" // U+f064 +#define ICON_FA_SHARE_FROM_SQUARE "\xef\x85\x8d" // U+f14d +#define ICON_FA_SHARE_NODES "\xef\x87\xa0" // U+f1e0 +#define ICON_FA_SHEET_PLASTIC "\xee\x95\xb1" // U+e571 +#define ICON_FA_SHEKEL_SIGN "\xef\x88\x8b" // U+f20b +#define ICON_FA_SHIELD "\xef\x84\xb2" // U+f132 +#define ICON_FA_SHIELD_CAT "\xee\x95\xb2" // U+e572 +#define ICON_FA_SHIELD_DOG "\xee\x95\xb3" // U+e573 +#define ICON_FA_SHIELD_HALVED "\xef\x8f\xad" // U+f3ed +#define ICON_FA_SHIELD_HEART "\xee\x95\xb4" // U+e574 +#define ICON_FA_SHIELD_VIRUS "\xee\x81\xac" // U+e06c +#define ICON_FA_SHIP "\xef\x88\x9a" // U+f21a +#define ICON_FA_SHIRT "\xef\x95\x93" // U+f553 +#define ICON_FA_SHOE_PRINTS "\xef\x95\x8b" // U+f54b +#define ICON_FA_SHOP "\xef\x95\x8f" // U+f54f +#define ICON_FA_SHOP_LOCK "\xee\x92\xa5" // U+e4a5 +#define ICON_FA_SHOP_SLASH "\xee\x81\xb0" // U+e070 +#define ICON_FA_SHOWER "\xef\x8b\x8c" // U+f2cc +#define ICON_FA_SHRIMP "\xee\x91\x88" // U+e448 +#define ICON_FA_SHUFFLE "\xef\x81\xb4" // U+f074 +#define ICON_FA_SHUTTLE_SPACE "\xef\x86\x97" // U+f197 +#define ICON_FA_SIGN_HANGING "\xef\x93\x99" // U+f4d9 +#define ICON_FA_SIGNAL "\xef\x80\x92" // U+f012 +#define ICON_FA_SIGNATURE "\xef\x96\xb7" // U+f5b7 +#define ICON_FA_SIGNS_POST "\xef\x89\xb7" // U+f277 +#define ICON_FA_SIM_CARD "\xef\x9f\x84" // U+f7c4 +#define ICON_FA_SINK "\xee\x81\xad" // U+e06d +#define ICON_FA_SITEMAP "\xef\x83\xa8" // U+f0e8 +#define ICON_FA_SKULL "\xef\x95\x8c" // U+f54c +#define ICON_FA_SKULL_CROSSBONES "\xef\x9c\x94" // U+f714 +#define ICON_FA_SLASH "\xef\x9c\x95" // U+f715 +#define ICON_FA_SLEIGH "\xef\x9f\x8c" // U+f7cc +#define ICON_FA_SLIDERS "\xef\x87\x9e" // U+f1de +#define ICON_FA_SMOG "\xef\x9d\x9f" // U+f75f +#define ICON_FA_SMOKING "\xef\x92\x8d" // U+f48d +#define ICON_FA_SNOWFLAKE "\xef\x8b\x9c" // U+f2dc +#define ICON_FA_SNOWMAN "\xef\x9f\x90" // U+f7d0 +#define ICON_FA_SNOWPLOW "\xef\x9f\x92" // U+f7d2 +#define ICON_FA_SOAP "\xee\x81\xae" // U+e06e +#define ICON_FA_SOCKS "\xef\x9a\x96" // U+f696 +#define ICON_FA_SOLAR_PANEL "\xef\x96\xba" // U+f5ba +#define ICON_FA_SORT "\xef\x83\x9c" // U+f0dc +#define ICON_FA_SORT_DOWN "\xef\x83\x9d" // U+f0dd +#define ICON_FA_SORT_UP "\xef\x83\x9e" // U+f0de +#define ICON_FA_SPA "\xef\x96\xbb" // U+f5bb +#define ICON_FA_SPAGHETTI_MONSTER_FLYING "\xef\x99\xbb" // U+f67b +#define ICON_FA_SPELL_CHECK "\xef\xa2\x91" // U+f891 +#define ICON_FA_SPIDER "\xef\x9c\x97" // U+f717 +#define ICON_FA_SPINNER "\xef\x84\x90" // U+f110 +#define ICON_FA_SPLOTCH "\xef\x96\xbc" // U+f5bc +#define ICON_FA_SPOON "\xef\x8b\xa5" // U+f2e5 +#define ICON_FA_SPRAY_CAN "\xef\x96\xbd" // U+f5bd +#define ICON_FA_SPRAY_CAN_SPARKLES "\xef\x97\x90" // U+f5d0 +#define ICON_FA_SQUARE "\xef\x83\x88" // U+f0c8 +#define ICON_FA_SQUARE_ARROW_UP_RIGHT "\xef\x85\x8c" // U+f14c +#define ICON_FA_SQUARE_CARET_DOWN "\xef\x85\x90" // U+f150 +#define ICON_FA_SQUARE_CARET_LEFT "\xef\x86\x91" // U+f191 +#define ICON_FA_SQUARE_CARET_RIGHT "\xef\x85\x92" // U+f152 +#define ICON_FA_SQUARE_CARET_UP "\xef\x85\x91" // U+f151 +#define ICON_FA_SQUARE_CHECK "\xef\x85\x8a" // U+f14a +#define ICON_FA_SQUARE_ENVELOPE "\xef\x86\x99" // U+f199 +#define ICON_FA_SQUARE_FULL "\xef\x91\x9c" // U+f45c +#define ICON_FA_SQUARE_H "\xef\x83\xbd" // U+f0fd +#define ICON_FA_SQUARE_MINUS "\xef\x85\x86" // U+f146 +#define ICON_FA_SQUARE_NFI "\xee\x95\xb6" // U+e576 +#define ICON_FA_SQUARE_PARKING "\xef\x95\x80" // U+f540 +#define ICON_FA_SQUARE_PEN "\xef\x85\x8b" // U+f14b +#define ICON_FA_SQUARE_PERSON_CONFINED "\xee\x95\xb7" // U+e577 +#define ICON_FA_SQUARE_PHONE "\xef\x82\x98" // U+f098 +#define ICON_FA_SQUARE_PHONE_FLIP "\xef\xa1\xbb" // U+f87b +#define ICON_FA_SQUARE_PLUS "\xef\x83\xbe" // U+f0fe +#define ICON_FA_SQUARE_POLL_HORIZONTAL "\xef\x9a\x82" // U+f682 +#define ICON_FA_SQUARE_POLL_VERTICAL "\xef\x9a\x81" // U+f681 +#define ICON_FA_SQUARE_ROOT_VARIABLE "\xef\x9a\x98" // U+f698 +#define ICON_FA_SQUARE_RSS "\xef\x85\x83" // U+f143 +#define ICON_FA_SQUARE_SHARE_NODES "\xef\x87\xa1" // U+f1e1 +#define ICON_FA_SQUARE_UP_RIGHT "\xef\x8d\xa0" // U+f360 +#define ICON_FA_SQUARE_VIRUS "\xee\x95\xb8" // U+e578 +#define ICON_FA_SQUARE_XMARK "\xef\x8b\x93" // U+f2d3 +#define ICON_FA_STAFF_AESCULAPIUS "\xee\x95\xb9" // U+e579 +#define ICON_FA_STAIRS "\xee\x8a\x89" // U+e289 +#define ICON_FA_STAMP "\xef\x96\xbf" // U+f5bf +#define ICON_FA_STAR "\xef\x80\x85" // U+f005 +#define ICON_FA_STAR_AND_CRESCENT "\xef\x9a\x99" // U+f699 +#define ICON_FA_STAR_HALF "\xef\x82\x89" // U+f089 +#define ICON_FA_STAR_HALF_STROKE "\xef\x97\x80" // U+f5c0 +#define ICON_FA_STAR_OF_DAVID "\xef\x9a\x9a" // U+f69a +#define ICON_FA_STAR_OF_LIFE "\xef\x98\xa1" // U+f621 +#define ICON_FA_STERLING_SIGN "\xef\x85\x94" // U+f154 +#define ICON_FA_STETHOSCOPE "\xef\x83\xb1" // U+f0f1 +#define ICON_FA_STOP "\xef\x81\x8d" // U+f04d +#define ICON_FA_STOPWATCH "\xef\x8b\xb2" // U+f2f2 +#define ICON_FA_STOPWATCH_20 "\xee\x81\xaf" // U+e06f +#define ICON_FA_STORE "\xef\x95\x8e" // U+f54e +#define ICON_FA_STORE_SLASH "\xee\x81\xb1" // U+e071 +#define ICON_FA_STREET_VIEW "\xef\x88\x9d" // U+f21d +#define ICON_FA_STRIKETHROUGH "\xef\x83\x8c" // U+f0cc +#define ICON_FA_STROOPWAFEL "\xef\x95\x91" // U+f551 +#define ICON_FA_SUBSCRIPT "\xef\x84\xac" // U+f12c +#define ICON_FA_SUITCASE "\xef\x83\xb2" // U+f0f2 +#define ICON_FA_SUITCASE_MEDICAL "\xef\x83\xba" // U+f0fa +#define ICON_FA_SUITCASE_ROLLING "\xef\x97\x81" // U+f5c1 +#define ICON_FA_SUN "\xef\x86\x85" // U+f185 +#define ICON_FA_SUN_PLANT_WILT "\xee\x95\xba" // U+e57a +#define ICON_FA_SUPERSCRIPT "\xef\x84\xab" // U+f12b +#define ICON_FA_SWATCHBOOK "\xef\x97\x83" // U+f5c3 +#define ICON_FA_SYNAGOGUE "\xef\x9a\x9b" // U+f69b +#define ICON_FA_SYRINGE "\xef\x92\x8e" // U+f48e +#define ICON_FA_T "T" // U+54 +#define ICON_FA_TABLE "\xef\x83\x8e" // U+f0ce +#define ICON_FA_TABLE_CELLS "\xef\x80\x8a" // U+f00a +#define ICON_FA_TABLE_CELLS_LARGE "\xef\x80\x89" // U+f009 +#define ICON_FA_TABLE_COLUMNS "\xef\x83\x9b" // U+f0db +#define ICON_FA_TABLE_LIST "\xef\x80\x8b" // U+f00b +#define ICON_FA_TABLE_TENNIS_PADDLE_BALL "\xef\x91\x9d" // U+f45d +#define ICON_FA_TABLET "\xef\x8f\xbb" // U+f3fb +#define ICON_FA_TABLET_BUTTON "\xef\x84\x8a" // U+f10a +#define ICON_FA_TABLET_SCREEN_BUTTON "\xef\x8f\xba" // U+f3fa +#define ICON_FA_TABLETS "\xef\x92\x90" // U+f490 +#define ICON_FA_TACHOGRAPH_DIGITAL "\xef\x95\xa6" // U+f566 +#define ICON_FA_TAG "\xef\x80\xab" // U+f02b +#define ICON_FA_TAGS "\xef\x80\xac" // U+f02c +#define ICON_FA_TAPE "\xef\x93\x9b" // U+f4db +#define ICON_FA_TARP "\xee\x95\xbb" // U+e57b +#define ICON_FA_TARP_DROPLET "\xee\x95\xbc" // U+e57c +#define ICON_FA_TAXI "\xef\x86\xba" // U+f1ba +#define ICON_FA_TEETH "\xef\x98\xae" // U+f62e +#define ICON_FA_TEETH_OPEN "\xef\x98\xaf" // U+f62f +#define ICON_FA_TEMPERATURE_ARROW_DOWN "\xee\x80\xbf" // U+e03f +#define ICON_FA_TEMPERATURE_ARROW_UP "\xee\x81\x80" // U+e040 +#define ICON_FA_TEMPERATURE_EMPTY "\xef\x8b\x8b" // U+f2cb +#define ICON_FA_TEMPERATURE_FULL "\xef\x8b\x87" // U+f2c7 +#define ICON_FA_TEMPERATURE_HALF "\xef\x8b\x89" // U+f2c9 +#define ICON_FA_TEMPERATURE_HIGH "\xef\x9d\xa9" // U+f769 +#define ICON_FA_TEMPERATURE_LOW "\xef\x9d\xab" // U+f76b +#define ICON_FA_TEMPERATURE_QUARTER "\xef\x8b\x8a" // U+f2ca +#define ICON_FA_TEMPERATURE_THREE_QUARTERS "\xef\x8b\x88" // U+f2c8 +#define ICON_FA_TENGE_SIGN "\xef\x9f\x97" // U+f7d7 +#define ICON_FA_TENT "\xee\x95\xbd" // U+e57d +#define ICON_FA_TENT_ARROW_DOWN_TO_LINE "\xee\x95\xbe" // U+e57e +#define ICON_FA_TENT_ARROW_LEFT_RIGHT "\xee\x95\xbf" // U+e57f +#define ICON_FA_TENT_ARROW_TURN_LEFT "\xee\x96\x80" // U+e580 +#define ICON_FA_TENT_ARROWS_DOWN "\xee\x96\x81" // U+e581 +#define ICON_FA_TENTS "\xee\x96\x82" // U+e582 +#define ICON_FA_TERMINAL "\xef\x84\xa0" // U+f120 +#define ICON_FA_TEXT_HEIGHT "\xef\x80\xb4" // U+f034 +#define ICON_FA_TEXT_SLASH "\xef\xa1\xbd" // U+f87d +#define ICON_FA_TEXT_WIDTH "\xef\x80\xb5" // U+f035 +#define ICON_FA_THERMOMETER "\xef\x92\x91" // U+f491 +#define ICON_FA_THUMBS_DOWN "\xef\x85\xa5" // U+f165 +#define ICON_FA_THUMBS_UP "\xef\x85\xa4" // U+f164 +#define ICON_FA_THUMBTACK "\xef\x82\x8d" // U+f08d +#define ICON_FA_TICKET "\xef\x85\x85" // U+f145 +#define ICON_FA_TICKET_SIMPLE "\xef\x8f\xbf" // U+f3ff +#define ICON_FA_TIMELINE "\xee\x8a\x9c" // U+e29c +#define ICON_FA_TOGGLE_OFF "\xef\x88\x84" // U+f204 +#define ICON_FA_TOGGLE_ON "\xef\x88\x85" // U+f205 +#define ICON_FA_TOILET "\xef\x9f\x98" // U+f7d8 +#define ICON_FA_TOILET_PAPER "\xef\x9c\x9e" // U+f71e +#define ICON_FA_TOILET_PAPER_SLASH "\xee\x81\xb2" // U+e072 +#define ICON_FA_TOILET_PORTABLE "\xee\x96\x83" // U+e583 +#define ICON_FA_TOILETS_PORTABLE "\xee\x96\x84" // U+e584 +#define ICON_FA_TOOLBOX "\xef\x95\x92" // U+f552 +#define ICON_FA_TOOTH "\xef\x97\x89" // U+f5c9 +#define ICON_FA_TORII_GATE "\xef\x9a\xa1" // U+f6a1 +#define ICON_FA_TORNADO "\xef\x9d\xaf" // U+f76f +#define ICON_FA_TOWER_BROADCAST "\xef\x94\x99" // U+f519 +#define ICON_FA_TOWER_CELL "\xee\x96\x85" // U+e585 +#define ICON_FA_TOWER_OBSERVATION "\xee\x96\x86" // U+e586 +#define ICON_FA_TRACTOR "\xef\x9c\xa2" // U+f722 +#define ICON_FA_TRADEMARK "\xef\x89\x9c" // U+f25c +#define ICON_FA_TRAFFIC_LIGHT "\xef\x98\xb7" // U+f637 +#define ICON_FA_TRAILER "\xee\x81\x81" // U+e041 +#define ICON_FA_TRAIN "\xef\x88\xb8" // U+f238 +#define ICON_FA_TRAIN_SUBWAY "\xef\x88\xb9" // U+f239 +#define ICON_FA_TRAIN_TRAM "\xef\x9f\x9a" // U+f7da +#define ICON_FA_TRANSGENDER "\xef\x88\xa5" // U+f225 +#define ICON_FA_TRASH "\xef\x87\xb8" // U+f1f8 +#define ICON_FA_TRASH_ARROW_UP "\xef\xa0\xa9" // U+f829 +#define ICON_FA_TRASH_CAN "\xef\x8b\xad" // U+f2ed +#define ICON_FA_TRASH_CAN_ARROW_UP "\xef\xa0\xaa" // U+f82a +#define ICON_FA_TREE "\xef\x86\xbb" // U+f1bb +#define ICON_FA_TREE_CITY "\xee\x96\x87" // U+e587 +#define ICON_FA_TRIANGLE_EXCLAMATION "\xef\x81\xb1" // U+f071 +#define ICON_FA_TROPHY "\xef\x82\x91" // U+f091 +#define ICON_FA_TROWEL "\xee\x96\x89" // U+e589 +#define ICON_FA_TROWEL_BRICKS "\xee\x96\x8a" // U+e58a +#define ICON_FA_TRUCK "\xef\x83\x91" // U+f0d1 +#define ICON_FA_TRUCK_ARROW_RIGHT "\xee\x96\x8b" // U+e58b +#define ICON_FA_TRUCK_DROPLET "\xee\x96\x8c" // U+e58c +#define ICON_FA_TRUCK_FAST "\xef\x92\x8b" // U+f48b +#define ICON_FA_TRUCK_FIELD "\xee\x96\x8d" // U+e58d +#define ICON_FA_TRUCK_FIELD_UN "\xee\x96\x8e" // U+e58e +#define ICON_FA_TRUCK_FRONT "\xee\x8a\xb7" // U+e2b7 +#define ICON_FA_TRUCK_MEDICAL "\xef\x83\xb9" // U+f0f9 +#define ICON_FA_TRUCK_MONSTER "\xef\x98\xbb" // U+f63b +#define ICON_FA_TRUCK_MOVING "\xef\x93\x9f" // U+f4df +#define ICON_FA_TRUCK_PICKUP "\xef\x98\xbc" // U+f63c +#define ICON_FA_TRUCK_PLANE "\xee\x96\x8f" // U+e58f +#define ICON_FA_TRUCK_RAMP_BOX "\xef\x93\x9e" // U+f4de +#define ICON_FA_TTY "\xef\x87\xa4" // U+f1e4 +#define ICON_FA_TURKISH_LIRA_SIGN "\xee\x8a\xbb" // U+e2bb +#define ICON_FA_TURN_DOWN "\xef\x8e\xbe" // U+f3be +#define ICON_FA_TURN_UP "\xef\x8e\xbf" // U+f3bf +#define ICON_FA_TV "\xef\x89\xac" // U+f26c +#define ICON_FA_U "U" // U+55 +#define ICON_FA_UMBRELLA "\xef\x83\xa9" // U+f0e9 +#define ICON_FA_UMBRELLA_BEACH "\xef\x97\x8a" // U+f5ca +#define ICON_FA_UNDERLINE "\xef\x83\x8d" // U+f0cd +#define ICON_FA_UNIVERSAL_ACCESS "\xef\x8a\x9a" // U+f29a +#define ICON_FA_UNLOCK "\xef\x82\x9c" // U+f09c +#define ICON_FA_UNLOCK_KEYHOLE "\xef\x84\xbe" // U+f13e +#define ICON_FA_UP_DOWN "\xef\x8c\xb8" // U+f338 +#define ICON_FA_UP_DOWN_LEFT_RIGHT "\xef\x82\xb2" // U+f0b2 +#define ICON_FA_UP_LONG "\xef\x8c\x8c" // U+f30c +#define ICON_FA_UP_RIGHT_AND_DOWN_LEFT_FROM_CENTER "\xef\x90\xa4" // U+f424 +#define ICON_FA_UP_RIGHT_FROM_SQUARE "\xef\x8d\x9d" // U+f35d +#define ICON_FA_UPLOAD "\xef\x82\x93" // U+f093 +#define ICON_FA_USER "\xef\x80\x87" // U+f007 +#define ICON_FA_USER_ASTRONAUT "\xef\x93\xbb" // U+f4fb +#define ICON_FA_USER_CHECK "\xef\x93\xbc" // U+f4fc +#define ICON_FA_USER_CLOCK "\xef\x93\xbd" // U+f4fd +#define ICON_FA_USER_DOCTOR "\xef\x83\xb0" // U+f0f0 +#define ICON_FA_USER_GEAR "\xef\x93\xbe" // U+f4fe +#define ICON_FA_USER_GRADUATE "\xef\x94\x81" // U+f501 +#define ICON_FA_USER_GROUP "\xef\x94\x80" // U+f500 +#define ICON_FA_USER_INJURED "\xef\x9c\xa8" // U+f728 +#define ICON_FA_USER_LARGE "\xef\x90\x86" // U+f406 +#define ICON_FA_USER_LARGE_SLASH "\xef\x93\xba" // U+f4fa +#define ICON_FA_USER_LOCK "\xef\x94\x82" // U+f502 +#define ICON_FA_USER_MINUS "\xef\x94\x83" // U+f503 +#define ICON_FA_USER_NINJA "\xef\x94\x84" // U+f504 +#define ICON_FA_USER_NURSE "\xef\xa0\xaf" // U+f82f +#define ICON_FA_USER_PEN "\xef\x93\xbf" // U+f4ff +#define ICON_FA_USER_PLUS "\xef\x88\xb4" // U+f234 +#define ICON_FA_USER_SECRET "\xef\x88\x9b" // U+f21b +#define ICON_FA_USER_SHIELD "\xef\x94\x85" // U+f505 +#define ICON_FA_USER_SLASH "\xef\x94\x86" // U+f506 +#define ICON_FA_USER_TAG "\xef\x94\x87" // U+f507 +#define ICON_FA_USER_TIE "\xef\x94\x88" // U+f508 +#define ICON_FA_USER_XMARK "\xef\x88\xb5" // U+f235 +#define ICON_FA_USERS "\xef\x83\x80" // U+f0c0 +#define ICON_FA_USERS_BETWEEN_LINES "\xee\x96\x91" // U+e591 +#define ICON_FA_USERS_GEAR "\xef\x94\x89" // U+f509 +#define ICON_FA_USERS_LINE "\xee\x96\x92" // U+e592 +#define ICON_FA_USERS_RAYS "\xee\x96\x93" // U+e593 +#define ICON_FA_USERS_RECTANGLE "\xee\x96\x94" // U+e594 +#define ICON_FA_USERS_SLASH "\xee\x81\xb3" // U+e073 +#define ICON_FA_USERS_VIEWFINDER "\xee\x96\x95" // U+e595 +#define ICON_FA_UTENSILS "\xef\x8b\xa7" // U+f2e7 +#define ICON_FA_V "V" // U+56 +#define ICON_FA_VAN_SHUTTLE "\xef\x96\xb6" // U+f5b6 +#define ICON_FA_VAULT "\xee\x8b\x85" // U+e2c5 +#define ICON_FA_VECTOR_SQUARE "\xef\x97\x8b" // U+f5cb +#define ICON_FA_VENUS "\xef\x88\xa1" // U+f221 +#define ICON_FA_VENUS_DOUBLE "\xef\x88\xa6" // U+f226 +#define ICON_FA_VENUS_MARS "\xef\x88\xa8" // U+f228 +#define ICON_FA_VEST "\xee\x82\x85" // U+e085 +#define ICON_FA_VEST_PATCHES "\xee\x82\x86" // U+e086 +#define ICON_FA_VIAL "\xef\x92\x92" // U+f492 +#define ICON_FA_VIAL_CIRCLE_CHECK "\xee\x96\x96" // U+e596 +#define ICON_FA_VIAL_VIRUS "\xee\x96\x97" // U+e597 +#define ICON_FA_VIALS "\xef\x92\x93" // U+f493 +#define ICON_FA_VIDEO "\xef\x80\xbd" // U+f03d +#define ICON_FA_VIDEO_SLASH "\xef\x93\xa2" // U+f4e2 +#define ICON_FA_VIHARA "\xef\x9a\xa7" // U+f6a7 +#define ICON_FA_VIRUS "\xee\x81\xb4" // U+e074 +#define ICON_FA_VIRUS_COVID "\xee\x92\xa8" // U+e4a8 +#define ICON_FA_VIRUS_COVID_SLASH "\xee\x92\xa9" // U+e4a9 +#define ICON_FA_VIRUS_SLASH "\xee\x81\xb5" // U+e075 +#define ICON_FA_VIRUSES "\xee\x81\xb6" // U+e076 +#define ICON_FA_VOICEMAIL "\xef\xa2\x97" // U+f897 +#define ICON_FA_VOLCANO "\xef\x9d\xb0" // U+f770 +#define ICON_FA_VOLLEYBALL "\xef\x91\x9f" // U+f45f +#define ICON_FA_VOLUME_HIGH "\xef\x80\xa8" // U+f028 +#define ICON_FA_VOLUME_LOW "\xef\x80\xa7" // U+f027 +#define ICON_FA_VOLUME_OFF "\xef\x80\xa6" // U+f026 +#define ICON_FA_VOLUME_XMARK "\xef\x9a\xa9" // U+f6a9 +#define ICON_FA_VR_CARDBOARD "\xef\x9c\xa9" // U+f729 +#define ICON_FA_W "W" // U+57 +#define ICON_FA_WALKIE_TALKIE "\xef\xa3\xaf" // U+f8ef +#define ICON_FA_WALLET "\xef\x95\x95" // U+f555 +#define ICON_FA_WAND_MAGIC "\xef\x83\x90" // U+f0d0 +#define ICON_FA_WAND_MAGIC_SPARKLES "\xee\x8b\x8a" // U+e2ca +#define ICON_FA_WAND_SPARKLES "\xef\x9c\xab" // U+f72b +#define ICON_FA_WAREHOUSE "\xef\x92\x94" // U+f494 +#define ICON_FA_WATER "\xef\x9d\xb3" // U+f773 +#define ICON_FA_WATER_LADDER "\xef\x97\x85" // U+f5c5 +#define ICON_FA_WAVE_SQUARE "\xef\xa0\xbe" // U+f83e +#define ICON_FA_WEIGHT_HANGING "\xef\x97\x8d" // U+f5cd +#define ICON_FA_WEIGHT_SCALE "\xef\x92\x96" // U+f496 +#define ICON_FA_WHEAT_AWN "\xee\x8b\x8d" // U+e2cd +#define ICON_FA_WHEAT_AWN_CIRCLE_EXCLAMATION "\xee\x96\x98" // U+e598 +#define ICON_FA_WHEELCHAIR "\xef\x86\x93" // U+f193 +#define ICON_FA_WHEELCHAIR_MOVE "\xee\x8b\x8e" // U+e2ce +#define ICON_FA_WHISKEY_GLASS "\xef\x9e\xa0" // U+f7a0 +#define ICON_FA_WIFI "\xef\x87\xab" // U+f1eb +#define ICON_FA_WIND "\xef\x9c\xae" // U+f72e +#define ICON_FA_WINDOW_MAXIMIZE "\xef\x8b\x90" // U+f2d0 +#define ICON_FA_WINDOW_MINIMIZE "\xef\x8b\x91" // U+f2d1 +#define ICON_FA_WINDOW_RESTORE "\xef\x8b\x92" // U+f2d2 +#define ICON_FA_WINE_BOTTLE "\xef\x9c\xaf" // U+f72f +#define ICON_FA_WINE_GLASS "\xef\x93\xa3" // U+f4e3 +#define ICON_FA_WINE_GLASS_EMPTY "\xef\x97\x8e" // U+f5ce +#define ICON_FA_WON_SIGN "\xef\x85\x99" // U+f159 +#define ICON_FA_WORM "\xee\x96\x99" // U+e599 +#define ICON_FA_WRENCH "\xef\x82\xad" // U+f0ad +#define ICON_FA_X "X" // U+58 +#define ICON_FA_X_RAY "\xef\x92\x97" // U+f497 +#define ICON_FA_XMARK "\xef\x80\x8d" // U+f00d +#define ICON_FA_XMARKS_LINES "\xee\x96\x9a" // U+e59a +#define ICON_FA_Y "Y" // U+59 +#define ICON_FA_YEN_SIGN "\xef\x85\x97" // U+f157 +#define ICON_FA_YIN_YANG "\xef\x9a\xad" // U+f6ad +#define ICON_FA_Z "Z" // U+5a diff --git a/ui/thirdparty/fpng/HEAD b/ui/thirdparty/fpng/HEAD new file mode 100644 index 0000000000..5adb30a452 --- /dev/null +++ b/ui/thirdparty/fpng/HEAD @@ -0,0 +1 @@ +645d49cf6b2e82ce25b5b59f6a2e2df30e6f5fa6 diff --git a/ui/thirdparty/fpng/fpng.cpp b/ui/thirdparty/fpng/fpng.cpp new file mode 100644 index 0000000000..0b34e1cfcb --- /dev/null +++ b/ui/thirdparty/fpng/fpng.cpp @@ -0,0 +1,3222 @@ +// fpng.cpp 1.0.6 - Fast 24/32bpp .PNG image writer/reader. See unlicense at the end of this file. +// PNG's generated by this code have been tested to load successfully with stb_image.h, lodepng.cpp, wuffs, libpng, and pngcheck. +// +// Uses code from the simple PNG writer function by Alex Evans, 2011. Released into the public domain: https://gist.github.com/908299 +// Some low-level Deflate/Huffman functions derived from the original 2011 Google Code version of miniz (public domain by R. Geldreich, Jr.): https://code.google.com/archive/p/miniz/ +// Low-level Huffman code size function: public domain, originally written by: Alistair Moffat, alistair@cs.mu.oz.au, Jyrki Katajainen, jyrki@diku.dk, November 1996. +// +// Optional config macros: +// FPNG_NO_SSE - Set to 1 to completely disable SSE usage, even on x86/x64. By default, on x86/x64 it's enabled. +// FPNG_DISABLE_DECODE_CRC32_CHECKS - Set to 1 to disable PNG chunk CRC-32 tests, for improved fuzzing. Defaults to 0. +// FPNG_USE_UNALIGNED_LOADS - Set to 1 to indicate it's OK to read/write unaligned 32-bit/64-bit values. Defaults to 0, unless x86/x64. +// +// With gcc/clang on x86, compile with -msse4.1 -mpclmul -fno-strict-aliasing +// Only tested with -fno-strict-aliasing (which the Linux kernel uses, and MSVC's default). +// +#include "fpng.h" +#include <assert.h> +#include <string.h> + +#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 <intrin.h> + #endif + #include <xmmintrin.h> // SSE + #include <emmintrin.h> // SSE2 + #include <smmintrin.h> // SSE4.1 + #include <wmmintrin.h> // pclmul +#endif + +#ifndef FPNG_NO_STDIO + #include <stdio.h> +#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 <sys/param.h> + + #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 <typename S> static inline S maximum(S a, S b) { return (a > b) ? a : b; } + template <typename S> 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<const uint32_t *>(p)) + #define WRITE_LE32(p, v) *reinterpret_cast<uint32_t *>(p) = swap32((uint32_t)(v)) + #define WRITE_LE64(p, v) *reinterpret_cast<uint64_t *>(p) = swap64((uint64_t)(v)) + + #define READ_BE32(p) *reinterpret_cast<const uint32_t *>(p) + #else + #define READ_LE32(p) (*reinterpret_cast<const uint32_t *>(p)) + #define WRITE_LE32(p, v) *reinterpret_cast<uint32_t *>(p) = (uint32_t)(v) + #define WRITE_LE64(p, v) *reinterpret_cast<uint64_t *>(p) = (uint64_t)(v) + + #define READ_BE32(p) swap32(*reinterpret_cast<const uint32_t *>(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<const uint32_t*>(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<const uint8_t*>(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<const __m128i*>(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<const __m128i*>(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<const __m128i*>(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<const __m128i*>(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<const __m128i*>(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<const uint8_t *>(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<size_t>(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<const uint8_t*>(&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<int>(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<uint32_t>(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<uint32_t>(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<uint32_t>(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<uint8_t>& 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<uint64_t>(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<uint32_t> 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<int>(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<int>(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<uint64_t> 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<int>(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<int>(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<uint8_t>& 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<uint8_t>& 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<uint8_t> 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<uint8_t> 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<uint32_t dst_comps> + 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<uint32_t dst_comps> + 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<const uint8_t*>(pImage) + 8; + + const png_ihdr& ihdr = *reinterpret_cast<const png_ihdr*>(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<const uint8_t*>(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<const png_chunk_prefix*>(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<uint8_t> &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<const uint8_t*>(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<uint8_t>& 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<uint8_t> 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 <http://unlicense.org/> + + 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 <stdlib.h> +#include <stdint.h> +#include <vector> + +#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<uint8_t>& 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<uint8_t>& 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<uint8_t>& 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<uint8_t>& 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 <epoxy/gl.h> 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 <stdio.h> @@ -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 <arm_neon.h> -// 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 <http://www.gnu.org/licenses/>. - */ - -#include <SDL.h> -#include <epoxy/gl.h> -#include <stdio.h> -#include <math.h> - -#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 <http://www.gnu.org/licenses/>. - */ - -#ifndef XEMU_CUSTOM_WIDGETS -#define XEMU_CUSTOM_WIDGETS - -#include <stdint.h> -#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 <http://www.gnu.org/licenses/>. - */ - -#include <SDL.h> -#include <epoxy/gl.h> -#include <stdio.h> -#include <deque> -#include <vector> -#include <string> -#include <memory> - -#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<const char *> 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<char*> Items; - ImVector<const char*> Commands; - ImVector<char*> 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 <cpuid.h> -#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<std::unique_ptr<NetworkInterface>> 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<NetworkInterfaceManager> 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 <IP address>:<Port> 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 <IP address>:<Port> 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 <math.h> - -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<ImVec2> 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<const char *> 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 <libslirp.h> #if defined(_WIN32) #include <pcap/pcap.h> #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 <cpuid.h> +#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 <http://www.gnu.org/licenses/>. - */ - -#include <SDL.h> -#include <epoxy/gl.h> -#include <stdio.h> -#include <math.h> -#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 <http://www.gnu.org/licenses/>. - */ - -#ifndef XEMU_SHADERS_H -#define XEMU_SHADERS_H - -#include <SDL2/SDL.h> -#include <epoxy/gl.h> - -#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 <http://www.gnu.org/licenses/>. - */ - -#include <stdio.h> -#include <stdlib.h> -#include <SDL_filesystem.h> - -#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 <http://www.gnu.org/licenses/>. - */ - -#ifndef XEMU_UPDATE_H -#define XEMU_UPDATE_H - -#include <string> -#include <stdint.h> -#include <functional> - -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<void(void)>; - -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 <stb_image.h> + #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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#include <cmath> +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#pragma once + +#include <SDL.h> +#include <epoxy/gl.h> +#include "ui/xemu-settings.h" + +#define IMGUI_DEFINE_MATH_OPERATORS +#include <imgui.h> +#include <imgui_internal.h> +#include <imgui_impl_sdl.h> +#include <imgui_impl_opengl3.h> +#include <implot.h> +#include <stb_image.h> + +extern "C" { +#include <noc_file_dialog.h> + +// 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 <http://www.gnu.org/licenses/>. +// +#include <string> +#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 <http://www.gnu.org/licenses/>. +// +#pragma once +#include <string> +#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 <http://www.gnu.org/licenses/>. +// +#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<ImVec2> 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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#include "common.hh" +#include <stdio.h> +#include <math.h> +#include <vector> +#include <fpng.h> +#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<uint8_t> 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<uint8_t> 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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#pragma once +#include <string> +#include <vector> +#include <memory> +#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<std::unique_ptr<NetworkInterface>> 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<NetworkInterfaceManager> 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<MainMenuTabButton*> 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 <http://www.gnu.org/licenses/>. +// + +#include <SDL.h> +#include <epoxy/gl.h> +#include <stdio.h> +#include <stdlib.h> +#include <math.h> +#include <functional> +#include <assert.h> +#include <fpng.h> + +#include <deque> +#include <vector> +#include <string> +#include <memory> + +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#pragma once +#include <string> +#include <memory> +#include <stdexcept> +#include <cstdio> +#include "common.hh" +#include "xemu-hud.h" + +extern "C" { +#include <noc_file_dialog.h> +} + +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<typename ... Args> +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_t>( size_s ); + std::unique_ptr<char[]> 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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#pragma once +#include "../xemu-monitor.h" +#include "common.hh" + +class MonitorWindow +{ +public: + bool is_open; + +private: + char InputBuf[256]; + ImVector<char*> Items; + ImVector<const char*> Commands; + ImVector<char*> 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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#pragma once +#include <stdint.h> +#include <deque> + +#include "../xemu-notifications.h" + +class NotificationManager +{ +private: + std::deque<const char *> m_notification_queue; + std::deque<const char *> 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 <http://www.gnu.org/licenses/>. +// +#include <string> +#include <vector> +#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<ImGuiID, ImRect>(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 <http://www.gnu.org/licenses/>. +// +#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<PopupMenu *> m_view_stack; + std::vector<PopupMenu *> m_menus_in_transition; + std::vector<std::pair<ImGuiID, ImRect>> 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 <http://www.gnu.org/licenses/>. - */ - +// +// 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 <http://www.gnu.org/licenses/>. +// #include <glib.h> #include <glib/gi18n.h> #include <stdio.h> -#include "xemu-reporting.h" +#include "reporting.hh" #define CPPHTTPLIB_OPENSSL_SUPPORT 1 -#include "httplib.h" -#include "json.hpp" +#include <httplib.h> +#include <json.hpp> 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 <http://www.gnu.org/licenses/>. - */ - -#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 <http://www.gnu.org/licenses/>. +#pragma once #include <string> #include <stdint.h> @@ -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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#pragma once +#include <string> +#include <vector> +#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<NavControlItem> 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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#pragma once +#include <vector> +#include "scene.hh" + +class SceneManager +{ +protected: + Scene *m_active_scene; + std::vector<Scene *> 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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#include "common.hh" +#include "update.hh" +#include "viewport-manager.hh" +#include <stdio.h> +#include <stdlib.h> +#include <SDL_filesystem.h> +#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 <httplib.h> + +#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 <http://www.gnu.org/licenses/>. +// +#pragma once +#if defined(_WIN32) +#include <string> +#include <stdint.h> +#include <functional> + +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<void(void)>; + +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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 <http://www.gnu.org/licenses/>. +// +#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 }