summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--dist/license.md31
-rw-r--r--dist/qt_themes/colorful/icons/16x16/lock.pngbin0 -> 330 bytes
-rw-r--r--dist/qt_themes/colorful/icons/256x256/plus_folder.pngbin0 -> 4643 bytes
-rw-r--r--dist/qt_themes/colorful/icons/48x48/bad_folder.pngbin0 -> 15494 bytes
-rw-r--r--dist/qt_themes/colorful/icons/48x48/chip.pngbin0 -> 582 bytes
-rw-r--r--dist/qt_themes/colorful/icons/48x48/folder.pngbin0 -> 460 bytes
-rw-r--r--dist/qt_themes/colorful/icons/48x48/plus.pngbin0 -> 496 bytes
-rw-r--r--dist/qt_themes/colorful/icons/48x48/sd_card.pngbin0 -> 680 bytes
-rw-r--r--dist/qt_themes/colorful/icons/index.theme14
-rw-r--r--dist/qt_themes/colorful/style.qrc15
-rw-r--r--dist/qt_themes/colorful/style.qss4
-rw-r--r--dist/qt_themes/colorful_dark/icons/16x16/lock.pngbin0 -> 401 bytes
-rw-r--r--dist/qt_themes/colorful_dark/icons/index.theme8
-rw-r--r--dist/qt_themes/colorful_dark/style.qrc57
-rw-r--r--dist/qt_themes/default/default.qrc14
-rw-r--r--dist/qt_themes/default/icons/16x16/lock.pngbin0 -> 279 bytes
-rw-r--r--dist/qt_themes/default/icons/256x256/plus_folder.pngbin0 -> 3135 bytes
-rw-r--r--dist/qt_themes/default/icons/48x48/bad_folder.pngbin0 -> 1088 bytes
-rw-r--r--dist/qt_themes/default/icons/48x48/chip.pngbin0 -> 15070 bytes
-rw-r--r--dist/qt_themes/default/icons/48x48/folder.pngbin0 -> 410 bytes
-rw-r--r--dist/qt_themes/default/icons/48x48/plus.pngbin0 -> 316 bytes
-rw-r--r--dist/qt_themes/default/icons/48x48/sd_card.pngbin0 -> 614 bytes
-rw-r--r--dist/qt_themes/default/icons/index.theme5
-rw-r--r--dist/qt_themes/qdarkstyle/icons/16x16/lock.pngbin0 -> 304 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.pngbin0 -> 3438 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.pngbin0 -> 1098 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/chip.pngbin0 -> 15120 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/folder.pngbin0 -> 542 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/plus.pngbin0 -> 339 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/48x48/sd_card.pngbin0 -> 676 bytes
-rw-r--r--dist/qt_themes/qdarkstyle/icons/index.theme7
-rw-r--r--dist/qt_themes/qdarkstyle/style.qrc7
m---------externals/fmt0
-rw-r--r--license.txt16
-rw-r--r--src/core/CMakeLists.txt2
-rw-r--r--src/core/core.cpp3
-rw-r--r--src/core/crypto/key_manager.cpp241
-rw-r--r--src/core/crypto/key_manager.h116
-rw-r--r--src/core/file_sys/system_archive/mii_model.cpp46
-rw-r--r--src/core/file_sys/system_archive/mii_model.h13
-rw-r--r--src/core/file_sys/system_archive/system_archive.cpp3
-rw-r--r--src/core/hle/kernel/vm_manager.cpp72
-rw-r--r--src/core/hle/kernel/vm_manager.h8
-rw-r--r--src/core/hle/service/am/am.cpp63
-rw-r--r--src/core/hle/service/am/am.h31
-rw-r--r--src/core/hle/service/am/applet_ae.cpp14
-rw-r--r--src/core/hle/service/am/applet_oe.cpp9
-rw-r--r--src/core/hle/service/am/applets/applets.cpp25
-rw-r--r--src/core/hle/service/am/applets/applets.h17
-rw-r--r--src/core/hle/service/am/applets/error.cpp7
-rw-r--r--src/core/hle/service/am/applets/error.h7
-rw-r--r--src/core/hle/service/am/applets/general_backend.cpp13
-rw-r--r--src/core/hle/service/am/applets/general_backend.h12
-rw-r--r--src/core/hle/service/am/applets/profile_select.cpp5
-rw-r--r--src/core/hle/service/am/applets/profile_select.h7
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.cpp5
-rw-r--r--src/core/hle/service/am/applets/software_keyboard.h7
-rw-r--r--src/core/hle/service/am/applets/web_browser.cpp19
-rw-r--r--src/core/hle/service/am/applets/web_browser.h12
-rw-r--r--src/core/hle/service/audio/audren_u.cpp33
-rw-r--r--src/core/hle/service/es/es.cpp230
-rw-r--r--src/core/hle/service/fatal/fatal.cpp2
-rw-r--r--src/core/hle/service/hid/controllers/npad.cpp6
-rw-r--r--src/core/hle/service/hid/controllers/npad.h1
-rw-r--r--src/core/hle/service/hid/hid.cpp25
-rw-r--r--src/core/hle/service/hid/hid.h2
-rw-r--r--src/core/loader/nro.cpp9
-rw-r--r--src/core/loader/nro.h1
-rw-r--r--src/core/reporter.cpp2
-rw-r--r--src/video_core/engines/kepler_compute.cpp53
-rw-r--r--src/video_core/engines/kepler_compute.h23
-rw-r--r--src/video_core/engines/maxwell_3d.cpp7
-rw-r--r--src/video_core/engines/maxwell_3d.h4
-rw-r--r--src/video_core/macro_interpreter.cpp18
-rw-r--r--src/video_core/macro_interpreter.h8
-rw-r--r--src/video_core/renderer_opengl/gl_device.cpp43
-rw-r--r--src/video_core/renderer_opengl/gl_device.h6
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp154
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h19
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.cpp68
-rw-r--r--src/video_core/renderer_opengl/gl_shader_cache.h21
-rw-r--r--src/video_core/renderer_opengl/gl_shader_decompiler.cpp948
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp15
-rw-r--r--src/video_core/renderer_opengl/gl_state.cpp86
-rw-r--r--src/video_core/renderer_opengl/gl_state.h19
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.h22
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp4
-rw-r--r--src/video_core/shader/decode/half_set_predicate.cpp19
-rw-r--r--src/video_core/shader/decode/image.cpp40
-rw-r--r--src/video_core/shader/node.h48
-rw-r--r--src/video_core/shader/shader_ir.h8
-rw-r--r--src/video_core/texture_cache/surface_base.h12
-rw-r--r--src/video_core/texture_cache/surface_params.cpp134
-rw-r--r--src/video_core/texture_cache/surface_params.h9
-rw-r--r--src/video_core/texture_cache/surface_view.cpp2
-rw-r--r--src/video_core/texture_cache/surface_view.h20
-rw-r--r--src/video_core/texture_cache/texture_cache.h21
-rw-r--r--src/yuzu/configuration/config.cpp44
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp44
-rw-r--r--src/yuzu/configuration/configure_general.cpp12
-rw-r--r--src/yuzu/configuration/configure_general.ui30
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp6
-rw-r--r--src/yuzu/configuration/configure_graphics.ui27
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp17
-rw-r--r--src/yuzu/game_list.cpp436
-rw-r--r--src/yuzu/game_list.h43
-rw-r--r--src/yuzu/game_list_p.h127
-rw-r--r--src/yuzu/game_list_worker.cpp86
-rw-r--r--src/yuzu/game_list_worker.h26
-rw-r--r--src/yuzu/main.cpp98
-rw-r--r--src/yuzu/main.h8
-rw-r--r--src/yuzu/main.ui1
-rw-r--r--src/yuzu/uisettings.h21
113 files changed, 2879 insertions, 1224 deletions
diff --git a/dist/license.md b/dist/license.md
new file mode 100644
index 000000000..b777ebb20
--- /dev/null
+++ b/dist/license.md
@@ -0,0 +1,31 @@
1The icons in this folder and its subfolders have the following licenses:
2
3Icon Name | License | Origin/Author
4--- | --- | ---
5qt_themes/default/icons/16x16/checked.png | Free for non-commercial use
6qt_themes/default/icons/16x16/failed.png | Free for non-commercial use
7qt_themes/default/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
8qt_themes/default/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
9qt_themes/default/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
10qt_themes/default/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
11qt_themes/default/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
12qt_themes/default/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
13qt_themes/default/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
14qt_themes/qdarkstyle/icons/16x16/checked.png | Free for non-commercial use
15qt_themes/qdarkstyle/icons/16x16/failed.png | Free for non-commercial use
16qt_themes/qdarkstyle/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
17qt_themes/qdarkstyle/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
18qt_themes/qdarkstyle/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
19qt_themes/qdarkstyle/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
20qt_themes/qdarkstyle/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
21qt_themes/qdarkstyle/icons/48x48/plus.png | CC0 1.0 | Designed by BreadFish64 from the Citra team
22qt_themes/qdarkstyle/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
23qt_themes/colorful/icons/16x16/lock.png | CC BY-ND 3.0 | https://icons8.com
24qt_themes/colorful/icons/256x256/plus_folder.png | CC BY-ND 3.0 | https://icons8.com
25qt_themes/colorful/icons/48x48/bad_folder.png | CC BY-ND 3.0 | https://icons8.com
26qt_themes/colorful/icons/48x48/chip.png | CC BY-ND 3.0 | https://icons8.com
27qt_themes/colorful/icons/48x48/folder.png | CC BY-ND 3.0 | https://icons8.com
28qt_themes/colorful/icons/48x48/plus.png | CC BY-ND 3.0 | https://icons8.com
29qt_themes/colorful/icons/48x48/sd_card.png | CC BY-ND 3.0 | https://icons8.com
30
31<!-- TODO: Add the license of the yuzu icon --> \ No newline at end of file
diff --git a/dist/qt_themes/colorful/icons/16x16/lock.png b/dist/qt_themes/colorful/icons/16x16/lock.png
new file mode 100644
index 000000000..fd27069d8
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/16x16/lock.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/256x256/plus_folder.png b/dist/qt_themes/colorful/icons/256x256/plus_folder.png
new file mode 100644
index 000000000..760fe6245
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/256x256/plus_folder.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/48x48/bad_folder.png b/dist/qt_themes/colorful/icons/48x48/bad_folder.png
new file mode 100644
index 000000000..a7ab7a1f6
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/bad_folder.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/48x48/chip.png b/dist/qt_themes/colorful/icons/48x48/chip.png
new file mode 100644
index 000000000..6fa158999
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/chip.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/48x48/folder.png b/dist/qt_themes/colorful/icons/48x48/folder.png
new file mode 100644
index 000000000..498de4c62
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/folder.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/48x48/plus.png b/dist/qt_themes/colorful/icons/48x48/plus.png
new file mode 100644
index 000000000..bc2c47c91
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/plus.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/48x48/sd_card.png b/dist/qt_themes/colorful/icons/48x48/sd_card.png
new file mode 100644
index 000000000..29be71a0d
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/48x48/sd_card.png
Binary files differ
diff --git a/dist/qt_themes/colorful/icons/index.theme b/dist/qt_themes/colorful/icons/index.theme
new file mode 100644
index 000000000..b452aca16
--- /dev/null
+++ b/dist/qt_themes/colorful/icons/index.theme
@@ -0,0 +1,14 @@
1[Icon Theme]
2Name=colorful
3Comment=Colorful theme
4Inherits=default
5Directories=16x16,48x48,256x256
6
7[16x16]
8Size=16
9
10[48x48]
11Size=48
12
13[256x256]
14Size=256
diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/style.qrc
new file mode 100644
index 000000000..af2f3fd56
--- /dev/null
+++ b/dist/qt_themes/colorful/style.qrc
@@ -0,0 +1,15 @@
1<RCC>
2 <qresource prefix="icons/colorful">
3 <file alias="index.theme">icons/index.theme</file>
4 <file alias="16x16/lock.png">icons/16x16/lock.png</file>
5 <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
6 <file alias="48x48/chip.png">icons/48x48/chip.png</file>
7 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
8 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
9 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
10 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
11 </qresource>
12 <qresource prefix="colorful">
13 <file>style.qss</file>
14 </qresource>
15</RCC>
diff --git a/dist/qt_themes/colorful/style.qss b/dist/qt_themes/colorful/style.qss
new file mode 100644
index 000000000..413fc81da
--- /dev/null
+++ b/dist/qt_themes/colorful/style.qss
@@ -0,0 +1,4 @@
1/*
2 This file is intentionally left blank.
3 We do not want to apply any stylesheet for colorful, only icons.
4*/
diff --git a/dist/qt_themes/colorful_dark/icons/16x16/lock.png b/dist/qt_themes/colorful_dark/icons/16x16/lock.png
new file mode 100644
index 000000000..32c505848
--- /dev/null
+++ b/dist/qt_themes/colorful_dark/icons/16x16/lock.png
Binary files differ
diff --git a/dist/qt_themes/colorful_dark/icons/index.theme b/dist/qt_themes/colorful_dark/icons/index.theme
new file mode 100644
index 000000000..94d5ae8aa
--- /dev/null
+++ b/dist/qt_themes/colorful_dark/icons/index.theme
@@ -0,0 +1,8 @@
1[Icon Theme]
2Name=colorful_dark
3Comment=Colorful theme (Dark style)
4Inherits=default
5Directories=16x16
6
7[16x16]
8Size=16
diff --git a/dist/qt_themes/colorful_dark/style.qrc b/dist/qt_themes/colorful_dark/style.qrc
new file mode 100644
index 000000000..27a6cc87d
--- /dev/null
+++ b/dist/qt_themes/colorful_dark/style.qrc
@@ -0,0 +1,57 @@
1<RCC>
2 <qresource prefix="icons/colorful_dark">
3 <file alias="index.theme">icons/index.theme</file>
4 <file alias="16x16/lock.png">icons/16x16/lock.png</file>
5 <file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file>
6 <file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file>
7 <file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file>
8 <file alias="48x48/plus.png">../colorful/icons/48x48/plus.png</file>
9 <file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file>
10 <file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file>
11 </qresource>
12
13 <qresource prefix="qss_icons">
14 <file alias="rc/up_arrow_disabled.png">../qdarkstyle/rc/up_arrow_disabled.png</file>
15 <file alias="rc/Hmovetoolbar.png">../qdarkstyle/rc/Hmovetoolbar.png</file>
16 <file alias="rc/stylesheet-branch-end.png">../qdarkstyle/rc/stylesheet-branch-end.png</file>
17 <file alias="rc/branch_closed-on.png">../qdarkstyle/rc/branch_closed-on.png</file>
18 <file alias="rc/stylesheet-vline.png">../qdarkstyle/rc/stylesheet-vline.png</file>
19 <file alias="rc/branch_closed.png">../qdarkstyle/rc/branch_closed.png</file>
20 <file alias="rc/branch_open-on.png">../qdarkstyle/rc/branch_open-on.png</file>
21 <file alias="rc/transparent.png">../qdarkstyle/rc/transparent.png</file>
22 <file alias="rc/right_arrow_disabled.png">../qdarkstyle/rc/right_arrow_disabled.png</file>
23 <file alias="rc/sizegrip.png">../qdarkstyle/rc/sizegrip.png</file>
24 <file alias="rc/close.png">../qdarkstyle/rc/close.png</file>
25 <file alias="rc/close-hover.png">../qdarkstyle/rc/close-hover.png</file>
26 <file alias="rc/close-pressed.png">../qdarkstyle/rc/close-pressed.png</file>
27 <file alias="rc/down_arrow.png">../qdarkstyle/rc/down_arrow.png</file>
28 <file alias="rc/Vmovetoolbar.png">../qdarkstyle/rc/Vmovetoolbar.png</file>
29 <file alias="rc/left_arrow.png">../qdarkstyle/rc/left_arrow.png</file>
30 <file alias="rc/stylesheet-branch-more.png">../qdarkstyle/rc/stylesheet-branch-more.png</file>
31 <file alias="rc/up_arrow.png">../qdarkstyle/rc/up_arrow.png</file>
32 <file alias="rc/right_arrow.png">../qdarkstyle/rc/right_arrow.png</file>
33 <file alias="rc/left_arrow_disabled.png">../qdarkstyle/rc/left_arrow_disabled.png</file>
34 <file alias="rc/Hsepartoolbar.png">../qdarkstyle/rc/Hsepartoolbar.png</file>
35 <file alias="rc/branch_open.png">../qdarkstyle/rc/branch_open.png</file>
36 <file alias="rc/Vsepartoolbar.png">../qdarkstyle/rc/Vsepartoolbar.png</file>
37 <file alias="rc/down_arrow_disabled.png">../qdarkstyle/rc/down_arrow_disabled.png</file>
38 <file alias="rc/undock.png">../qdarkstyle/rc/undock.png</file>
39 <file alias="rc/checkbox_checked_disabled.png">../qdarkstyle/rc/checkbox_checked_disabled.png</file>
40 <file alias="rc/checkbox_checked_focus.png">../qdarkstyle/rc/checkbox_checked_focus.png</file>
41 <file alias="rc/checkbox_checked.png">../qdarkstyle/rc/checkbox_checked.png</file>
42 <file alias="rc/checkbox_indeterminate.png">../qdarkstyle/rc/checkbox_indeterminate.png</file>
43 <file alias="rc/checkbox_indeterminate_focus.png">../qdarkstyle/rc/checkbox_indeterminate_focus.png</file>
44 <file alias="rc/checkbox_unchecked_disabled.png">../qdarkstyle/rc/checkbox_unchecked_disabled.png</file>
45 <file alias="rc/checkbox_unchecked_focus.png">../qdarkstyle/rc/checkbox_unchecked_focus.png</file>
46 <file alias="rc/checkbox_unchecked.png">../qdarkstyle/rc/checkbox_unchecked.png</file>
47 <file alias="rc/radio_checked_disabled.png">../qdarkstyle/rc/radio_checked_disabled.png</file>
48 <file alias="rc/radio_checked_focus.png">../qdarkstyle/rc/radio_checked_focus.png</file>
49 <file alias="rc/radio_checked.png">../qdarkstyle/rc/radio_checked.png</file>
50 <file alias="rc/radio_unchecked_disabled.png">../qdarkstyle/rc/radio_unchecked_disabled.png</file>
51 <file alias="rc/radio_unchecked_focus.png">../qdarkstyle/rc/radio_unchecked_focus.png</file>
52 <file alias="rc/radio_unchecked.png">../qdarkstyle/rc/radio_unchecked.png</file>
53 </qresource>
54 <qresource prefix="colorful_dark">
55 <file alias="style.qss">../qdarkstyle/style.qss</file>
56 </qresource>
57</RCC>
diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc
index 14a0cf6f9..d1a0ee1be 100644
--- a/dist/qt_themes/default/default.qrc
+++ b/dist/qt_themes/default/default.qrc
@@ -5,7 +5,21 @@
5 <file alias="16x16/checked.png">icons/16x16/checked.png</file> 5 <file alias="16x16/checked.png">icons/16x16/checked.png</file>
6 6
7 <file alias="16x16/failed.png">icons/16x16/failed.png</file> 7 <file alias="16x16/failed.png">icons/16x16/failed.png</file>
8
9 <file alias="16x16/lock.png">icons/16x16/lock.png</file>
10
11 <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
12
13 <file alias="48x48/chip.png">icons/48x48/chip.png</file>
14
15 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
16
17 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
18
19 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
8 20
9 <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file> 21 <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file>
22
23 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
10 </qresource> 24 </qresource>
11</RCC> 25</RCC>
diff --git a/dist/qt_themes/default/icons/16x16/lock.png b/dist/qt_themes/default/icons/16x16/lock.png
new file mode 100644
index 000000000..496b58078
--- /dev/null
+++ b/dist/qt_themes/default/icons/16x16/lock.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/256x256/plus_folder.png b/dist/qt_themes/default/icons/256x256/plus_folder.png
new file mode 100644
index 000000000..ae4afccc7
--- /dev/null
+++ b/dist/qt_themes/default/icons/256x256/plus_folder.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/48x48/bad_folder.png b/dist/qt_themes/default/icons/48x48/bad_folder.png
new file mode 100644
index 000000000..2527c1318
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/bad_folder.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/48x48/chip.png b/dist/qt_themes/default/icons/48x48/chip.png
new file mode 100644
index 000000000..3efdf301e
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/chip.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/48x48/folder.png b/dist/qt_themes/default/icons/48x48/folder.png
new file mode 100644
index 000000000..2e67d8b38
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/folder.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/48x48/plus.png b/dist/qt_themes/default/icons/48x48/plus.png
new file mode 100644
index 000000000..dbc74687b
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/plus.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/48x48/sd_card.png b/dist/qt_themes/default/icons/48x48/sd_card.png
new file mode 100644
index 000000000..edacaeeb5
--- /dev/null
+++ b/dist/qt_themes/default/icons/48x48/sd_card.png
Binary files differ
diff --git a/dist/qt_themes/default/icons/index.theme b/dist/qt_themes/default/icons/index.theme
index ac67cb236..1edbe6408 100644
--- a/dist/qt_themes/default/icons/index.theme
+++ b/dist/qt_themes/default/icons/index.theme
@@ -1,10 +1,13 @@
1[Icon Theme] 1[Icon Theme]
2Name=default 2Name=default
3Comment=default theme 3Comment=default theme
4Directories=16x16,256x256 4Directories=16x16,48x48,256x256
5 5
6[16x16] 6[16x16]
7Size=16 7Size=16
8
9[48x48]
10Size=48
8 11
9[256x256] 12[256x256]
10Size=256 \ No newline at end of file 13Size=256 \ No newline at end of file
diff --git a/dist/qt_themes/qdarkstyle/icons/16x16/lock.png b/dist/qt_themes/qdarkstyle/icons/16x16/lock.png
new file mode 100644
index 000000000..c750a39e8
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/16x16/lock.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png b/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png
new file mode 100644
index 000000000..303f9a321
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/256x256/plus_folder.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.png b/dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.png
new file mode 100644
index 000000000..4a9709623
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/bad_folder.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/chip.png b/dist/qt_themes/qdarkstyle/icons/48x48/chip.png
new file mode 100644
index 000000000..973fabd05
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/chip.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/folder.png b/dist/qt_themes/qdarkstyle/icons/48x48/folder.png
new file mode 100644
index 000000000..0f1e987d6
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/folder.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/plus.png b/dist/qt_themes/qdarkstyle/icons/48x48/plus.png
new file mode 100644
index 000000000..16cc8b4f4
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/plus.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png
new file mode 100644
index 000000000..0291c6542
--- /dev/null
+++ b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png
Binary files differ
diff --git a/dist/qt_themes/qdarkstyle/icons/index.theme b/dist/qt_themes/qdarkstyle/icons/index.theme
index 558ece40b..d1e12f3ef 100644
--- a/dist/qt_themes/qdarkstyle/icons/index.theme
+++ b/dist/qt_themes/qdarkstyle/icons/index.theme
@@ -2,10 +2,13 @@
2Name=qdarkstyle 2Name=qdarkstyle
3Comment=dark theme 3Comment=dark theme
4Inherits=default 4Inherits=default
5Directories=16x16,256x256 5Directories=16x16,48x48,256x256
6 6
7[16x16] 7[16x16]
8Size=16 8Size=16
9 9
10[48x48]
11Size=48
12
10[256x256] 13[256x256]
11Size=256 \ No newline at end of file 14Size=256 \ No newline at end of file
diff --git a/dist/qt_themes/qdarkstyle/style.qrc b/dist/qt_themes/qdarkstyle/style.qrc
index efbd0b9dc..c2c14c28a 100644
--- a/dist/qt_themes/qdarkstyle/style.qrc
+++ b/dist/qt_themes/qdarkstyle/style.qrc
@@ -1,6 +1,13 @@
1<RCC> 1<RCC>
2 <qresource prefix="icons/qdarkstyle"> 2 <qresource prefix="icons/qdarkstyle">
3 <file alias="index.theme">icons/index.theme</file> 3 <file alias="index.theme">icons/index.theme</file>
4 <file alias="16x16/lock.png">icons/16x16/lock.png</file>
5 <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file>
6 <file alias="48x48/chip.png">icons/48x48/chip.png</file>
7 <file alias="48x48/folder.png">icons/48x48/folder.png</file>
8 <file alias="48x48/plus.png">icons/48x48/plus.png</file>
9 <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file>
10 <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file>
4 </qresource> 11 </qresource>
5 <qresource prefix="qss_icons"> 12 <qresource prefix="qss_icons">
6 <file>rc/up_arrow_disabled.png</file> 13 <file>rc/up_arrow_disabled.png</file>
diff --git a/externals/fmt b/externals/fmt
Subproject 9e554999ce02cf86fcdfe74fe740c4fe3f5a56d Subproject 7512a55aa3ae309587ca89668ef9ec4074a51a1
diff --git a/license.txt b/license.txt
index d511905c1..2b858f9a7 100644
--- a/license.txt
+++ b/license.txt
@@ -337,3 +337,19 @@ proprietary programs. If your program is a subroutine library, you may
337consider it more useful to permit linking proprietary applications with the 337consider it more useful to permit linking proprietary applications with the
338library. If this is what you want to do, use the GNU Lesser General 338library. If this is what you want to do, use the GNU Lesser General
339Public License instead of this License. 339Public License instead of this License.
340
341
342The icons used in this project have the following licenses:
343
344Icon Name | License | Origin/Author
345--- | --- | ---
346checked.png | Free for non-commercial use
347failed.png | Free for non-commercial use
348lock.png | CC BY-ND 3.0 | https://icons8.com
349plus_folder.png | CC BY-ND 3.0 | https://icons8.com
350bad_folder.png | CC BY-ND 3.0 | https://icons8.com
351chip.png | CC BY-ND 3.0 | https://icons8.com
352folder.png | CC BY-ND 3.0 | https://icons8.com
353plus.png (Default, Dark) | CC0 1.0 | Designed by BreadFish64 from the Citra team
354plus.png (Colorful, Colorful Dark) | CC BY-ND 3.0 | https://icons8.com
355sd_card.png | CC BY-ND 3.0 | https://icons8.com
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 5462decee..877a9e353 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -70,6 +70,8 @@ add_library(core STATIC
70 file_sys/sdmc_factory.h 70 file_sys/sdmc_factory.h
71 file_sys/submission_package.cpp 71 file_sys/submission_package.cpp
72 file_sys/submission_package.h 72 file_sys/submission_package.h
73 file_sys/system_archive/mii_model.cpp
74 file_sys/system_archive/mii_model.h
73 file_sys/system_archive/ng_word.cpp 75 file_sys/system_archive/ng_word.cpp
74 file_sys/system_archive/ng_word.h 76 file_sys/system_archive/ng_word.h
75 file_sys/system_archive/system_archive.cpp 77 file_sys/system_archive/system_archive.cpp
diff --git a/src/core/core.cpp b/src/core/core.cpp
index 20d64f3b0..3d0978cbf 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -104,7 +104,8 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
104 return vfs->OpenFile(path, FileSys::Mode::Read); 104 return vfs->OpenFile(path, FileSys::Mode::Read);
105} 105}
106struct System::Impl { 106struct System::Impl {
107 explicit Impl(System& system) : kernel{system}, cpu_core_manager{system}, reporter{system} {} 107 explicit Impl(System& system)
108 : kernel{system}, cpu_core_manager{system}, applet_manager{system}, reporter{system} {}
108 109
109 Cpu& CurrentCpuCore() { 110 Cpu& CurrentCpuCore() {
110 return cpu_core_manager.GetCurrentCore(); 111 return cpu_core_manager.GetCurrentCore();
diff --git a/src/core/crypto/key_manager.cpp b/src/core/crypto/key_manager.cpp
index 6dd633363..46aceec3d 100644
--- a/src/core/crypto/key_manager.cpp
+++ b/src/core/crypto/key_manager.cpp
@@ -37,6 +37,7 @@
37namespace Core::Crypto { 37namespace Core::Crypto {
38 38
39constexpr u64 CURRENT_CRYPTO_REVISION = 0x5; 39constexpr u64 CURRENT_CRYPTO_REVISION = 0x5;
40constexpr u64 FULL_TICKET_SIZE = 0x400;
40 41
41using namespace Common; 42using namespace Common;
42 43
@@ -55,6 +56,99 @@ const std::map<std::pair<S128KeyType, u64>, std::string> KEYS_VARIABLE_LENGTH{
55 {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"}, 56 {{S128KeyType::KeyblobMAC, 0}, "keyblob_mac_key_"},
56}; 57};
57 58
59namespace {
60template <std::size_t Size>
61bool IsAllZeroArray(const std::array<u8, Size>& array) {
62 return std::all_of(array.begin(), array.end(), [](const auto& elem) { return elem == 0; });
63}
64} // namespace
65
66u64 GetSignatureTypeDataSize(SignatureType type) {
67 switch (type) {
68 case SignatureType::RSA_4096_SHA1:
69 case SignatureType::RSA_4096_SHA256:
70 return 0x200;
71 case SignatureType::RSA_2048_SHA1:
72 case SignatureType::RSA_2048_SHA256:
73 return 0x100;
74 case SignatureType::ECDSA_SHA1:
75 case SignatureType::ECDSA_SHA256:
76 return 0x3C;
77 }
78 UNREACHABLE();
79}
80
81u64 GetSignatureTypePaddingSize(SignatureType type) {
82 switch (type) {
83 case SignatureType::RSA_4096_SHA1:
84 case SignatureType::RSA_4096_SHA256:
85 case SignatureType::RSA_2048_SHA1:
86 case SignatureType::RSA_2048_SHA256:
87 return 0x3C;
88 case SignatureType::ECDSA_SHA1:
89 case SignatureType::ECDSA_SHA256:
90 return 0x40;
91 }
92 UNREACHABLE();
93}
94
95SignatureType Ticket::GetSignatureType() const {
96 if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
97 return ticket->sig_type;
98 }
99 if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
100 return ticket->sig_type;
101 }
102 if (auto ticket = std::get_if<ECDSATicket>(&data)) {
103 return ticket->sig_type;
104 }
105
106 UNREACHABLE();
107}
108
109TicketData& Ticket::GetData() {
110 if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
111 return ticket->data;
112 }
113 if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
114 return ticket->data;
115 }
116 if (auto ticket = std::get_if<ECDSATicket>(&data)) {
117 return ticket->data;
118 }
119
120 UNREACHABLE();
121}
122
123const TicketData& Ticket::GetData() const {
124 if (auto ticket = std::get_if<RSA4096Ticket>(&data)) {
125 return ticket->data;
126 }
127 if (auto ticket = std::get_if<RSA2048Ticket>(&data)) {
128 return ticket->data;
129 }
130 if (auto ticket = std::get_if<ECDSATicket>(&data)) {
131 return ticket->data;
132 }
133
134 UNREACHABLE();
135}
136
137u64 Ticket::GetSize() const {
138 const auto sig_type = GetSignatureType();
139
140 return sizeof(SignatureType) + GetSignatureTypeDataSize(sig_type) +
141 GetSignatureTypePaddingSize(sig_type) + sizeof(TicketData);
142}
143
144Ticket Ticket::SynthesizeCommon(Key128 title_key, const std::array<u8, 16>& rights_id) {
145 RSA2048Ticket out{};
146 out.sig_type = SignatureType::RSA_2048_SHA256;
147 out.data.rights_id = rights_id;
148 out.data.title_key_common = title_key;
149 return Ticket{out};
150}
151
58Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) { 152Key128 GenerateKeyEncryptionKey(Key128 source, Key128 master, Key128 kek_seed, Key128 key_seed) {
59 Key128 out{}; 153 Key128 out{};
60 154
@@ -135,6 +229,27 @@ void KeyManager::DeriveGeneralPurposeKeys(std::size_t crypto_revision) {
135 } 229 }
136} 230}
137 231
232RSAKeyPair<2048> KeyManager::GetETicketRSAKey() const {
233 if (IsAllZeroArray(eticket_extended_kek) || !HasKey(S128KeyType::ETicketRSAKek))
234 return {};
235
236 const auto eticket_final = GetKey(S128KeyType::ETicketRSAKek);
237
238 std::vector<u8> extended_iv(eticket_extended_kek.begin(), eticket_extended_kek.begin() + 0x10);
239 std::array<u8, 0x230> extended_dec{};
240 AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
241 rsa_1.SetIV(extended_iv);
242 rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
243 extended_dec.data(), Op::Decrypt);
244
245 RSAKeyPair<2048> rsa_key{};
246 std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size());
247 std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size());
248 std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size());
249
250 return rsa_key;
251}
252
138Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) { 253Key128 DeriveKeyblobMACKey(const Key128& keyblob_key, const Key128& mac_source) {
139 AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB); 254 AESCipher<Key128> mac_cipher(keyblob_key, Mode::ECB);
140 Key128 mac_key{}; 255 Key128 mac_key{};
@@ -237,7 +352,7 @@ Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& ke
237 return Loader::ResultStatus::Success; 352 return Loader::ResultStatus::Success;
238} 353}
239 354
240std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) { 355std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save) {
241 if (!ticket_save.IsOpen()) 356 if (!ticket_save.IsOpen())
242 return {}; 357 return {};
243 358
@@ -246,14 +361,14 @@ std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save) {
246 return {}; 361 return {};
247 } 362 }
248 363
249 std::vector<TicketRaw> out; 364 std::vector<Ticket> out;
250 for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) { 365 for (std::size_t offset = 0; offset + 0x4 < buffer.size(); ++offset) {
251 if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 && 366 if (buffer[offset] == 0x4 && buffer[offset + 1] == 0x0 && buffer[offset + 2] == 0x1 &&
252 buffer[offset + 3] == 0x0) { 367 buffer[offset + 3] == 0x0) {
253 out.emplace_back(); 368 out.emplace_back();
254 auto& next = out.back(); 369 auto& next = out.back();
255 std::memcpy(&next, buffer.data() + offset, sizeof(TicketRaw)); 370 std::memcpy(&next, buffer.data() + offset, sizeof(Ticket));
256 offset += next.size(); 371 offset += FULL_TICKET_SIZE;
257 } 372 }
258 } 373 }
259 374
@@ -305,29 +420,23 @@ static std::optional<u64> FindTicketOffset(const std::array<u8, size>& data) {
305 return offset; 420 return offset;
306} 421}
307 422
308std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, 423std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
309 const RSAKeyPair<2048>& key) { 424 const RSAKeyPair<2048>& key) {
310 u32 cert_authority; 425 const auto issuer = ticket.GetData().issuer;
311 std::memcpy(&cert_authority, ticket.data() + 0x140, sizeof(cert_authority)); 426 if (issuer == std::array<u8, 0x40>{})
312 if (cert_authority == 0)
313 return {}; 427 return {};
314 if (cert_authority != Common::MakeMagic('R', 'o', 'o', 't')) { 428 if (issuer[0] != 'R' || issuer[1] != 'o' || issuer[2] != 'o' || issuer[3] != 't') {
315 LOG_INFO(Crypto, 429 LOG_INFO(Crypto, "Attempting to parse ticket with non-standard certificate authority.");
316 "Attempting to parse ticket with non-standard certificate authority {:08X}.",
317 cert_authority);
318 } 430 }
319 431
320 Key128 rights_id; 432 Key128 rights_id = ticket.GetData().rights_id;
321 std::memcpy(rights_id.data(), ticket.data() + 0x2A0, sizeof(Key128));
322 433
323 if (rights_id == Key128{}) 434 if (rights_id == Key128{})
324 return {}; 435 return {};
325 436
326 Key128 key_temp{}; 437 if (!std::any_of(ticket.GetData().title_key_common_pad.begin(),
327 438 ticket.GetData().title_key_common_pad.end(), [](u8 b) { return b != 0; })) {
328 if (!std::any_of(ticket.begin() + 0x190, ticket.begin() + 0x280, [](u8 b) { return b != 0; })) { 439 return std::make_pair(rights_id, ticket.GetData().title_key_common);
329 std::memcpy(key_temp.data(), ticket.data() + 0x180, key_temp.size());
330 return std::make_pair(rights_id, key_temp);
331 } 440 }
332 441
333 mbedtls_mpi D; // RSA Private Exponent 442 mbedtls_mpi D; // RSA Private Exponent
@@ -342,7 +451,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
342 451
343 mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size()); 452 mbedtls_mpi_read_binary(&D, key.decryption_key.data(), key.decryption_key.size());
344 mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size()); 453 mbedtls_mpi_read_binary(&N, key.modulus.data(), key.modulus.size());
345 mbedtls_mpi_read_binary(&S, ticket.data() + 0x180, 0x100); 454 mbedtls_mpi_read_binary(&S, ticket.GetData().title_key_block.data(), 0x100);
346 455
347 mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr); 456 mbedtls_mpi_exp_mod(&M, &S, &D, &N, nullptr);
348 457
@@ -366,6 +475,7 @@ std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket,
366 return {}; 475 return {};
367 ASSERT(*offset > 0); 476 ASSERT(*offset > 0);
368 477
478 Key128 key_temp{};
369 std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size()); 479 std::memcpy(key_temp.data(), m_2.data() + *offset, key_temp.size());
370 480
371 return std::make_pair(rights_id, key_temp); 481 return std::make_pair(rights_id, key_temp);
@@ -450,6 +560,8 @@ void KeyManager::LoadFromFile(const std::string& filename, bool is_title_keys) {
450 560
451 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16); 561 const auto index = std::stoul(out[0].substr(18, 2), nullptr, 16);
452 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]); 562 encrypted_keyblobs[index] = Common::HexStringToArray<0xB0>(out[1]);
563 } else if (out[0].compare(0, 20, "eticket_extended_kek") == 0) {
564 eticket_extended_kek = Common::HexStringToArray<576>(out[1]);
453 } else { 565 } else {
454 for (const auto& kv : KEYS_VARIABLE_LENGTH) { 566 for (const auto& kv : KEYS_VARIABLE_LENGTH) {
455 if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2)) 567 if (!ValidCryptoRevisionString(out[0], kv.second.size(), 2))
@@ -862,20 +974,19 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
862 // Titlekeys 974 // Titlekeys
863 data.DecryptProdInfo(GetBISKey(0)); 975 data.DecryptProdInfo(GetBISKey(0));
864 976
865 const auto eticket_extended_kek = data.GetETicketExtendedKek(); 977 eticket_extended_kek = data.GetETicketExtendedKek();
978 WriteKeyToFile(KeyCategory::Console, "eticket_extended_kek", eticket_extended_kek);
979 PopulateTickets();
980}
866 981
867 std::vector<u8> extended_iv(0x10); 982void KeyManager::PopulateTickets() {
868 std::memcpy(extended_iv.data(), eticket_extended_kek.data(), extended_iv.size()); 983 const auto rsa_key = GetETicketRSAKey();
869 std::array<u8, 0x230> extended_dec{};
870 AESCipher<Key128> rsa_1(eticket_final, Mode::CTR);
871 rsa_1.SetIV(extended_iv);
872 rsa_1.Transcode(eticket_extended_kek.data() + 0x10, eticket_extended_kek.size() - 0x10,
873 extended_dec.data(), Op::Decrypt);
874 984
875 RSAKeyPair<2048> rsa_key{}; 985 if (rsa_key == RSAKeyPair<2048>{})
876 std::memcpy(rsa_key.decryption_key.data(), extended_dec.data(), rsa_key.decryption_key.size()); 986 return;
877 std::memcpy(rsa_key.modulus.data(), extended_dec.data() + 0x100, rsa_key.modulus.size()); 987
878 std::memcpy(rsa_key.exponent.data(), extended_dec.data() + 0x200, rsa_key.exponent.size()); 988 if (!common_tickets.empty() && !personal_tickets.empty())
989 return;
879 990
880 const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 991 const FileUtil::IOFile save1(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
881 "/system/save/80000000000000e1", 992 "/system/save/80000000000000e1",
@@ -886,19 +997,41 @@ void KeyManager::DeriveETicket(PartitionDataManager& data) {
886 997
887 const auto blob2 = GetTicketblob(save2); 998 const auto blob2 = GetTicketblob(save2);
888 auto res = GetTicketblob(save1); 999 auto res = GetTicketblob(save1);
1000 const auto idx = res.size();
889 res.insert(res.end(), blob2.begin(), blob2.end()); 1001 res.insert(res.end(), blob2.begin(), blob2.end());
890 1002
891 for (const auto& raw : res) { 1003 for (std::size_t i = 0; i < res.size(); ++i) {
892 const auto pair = ParseTicket(raw, rsa_key); 1004 const auto common = i < idx;
1005 const auto pair = ParseTicket(res[i], rsa_key);
893 if (!pair) 1006 if (!pair)
894 continue; 1007 continue;
895 const auto& [rid, key] = *pair; 1008 const auto& [rid, key] = *pair;
896 u128 rights_id; 1009 u128 rights_id;
897 std::memcpy(rights_id.data(), rid.data(), rid.size()); 1010 std::memcpy(rights_id.data(), rid.data(), rid.size());
1011
1012 if (common) {
1013 common_tickets[rights_id] = res[i];
1014 } else {
1015 personal_tickets[rights_id] = res[i];
1016 }
1017
898 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]); 1018 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
899 } 1019 }
900} 1020}
901 1021
1022void KeyManager::SynthesizeTickets() {
1023 for (const auto& key : s128_keys) {
1024 if (key.first.type != S128KeyType::Titlekey) {
1025 continue;
1026 }
1027 u128 rights_id{key.first.field1, key.first.field2};
1028 Key128 rights_id_2;
1029 std::memcpy(rights_id_2.data(), rights_id.data(), rights_id_2.size());
1030 const auto ticket = Ticket::SynthesizeCommon(key.second, rights_id_2);
1031 common_tickets.insert_or_assign(rights_id, ticket);
1032 }
1033}
1034
902void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) { 1035void KeyManager::SetKeyWrapped(S128KeyType id, Key128 key, u64 field1, u64 field2) {
903 if (key == Key128{}) 1036 if (key == Key128{})
904 return; 1037 return;
@@ -997,6 +1130,46 @@ void KeyManager::PopulateFromPartitionData(PartitionDataManager& data) {
997 DeriveBase(); 1130 DeriveBase();
998} 1131}
999 1132
1133const std::map<u128, Ticket>& KeyManager::GetCommonTickets() const {
1134 return common_tickets;
1135}
1136
1137const std::map<u128, Ticket>& KeyManager::GetPersonalizedTickets() const {
1138 return personal_tickets;
1139}
1140
1141bool KeyManager::AddTicketCommon(Ticket raw) {
1142 const auto rsa_key = GetETicketRSAKey();
1143 if (rsa_key == RSAKeyPair<2048>{})
1144 return false;
1145
1146 const auto pair = ParseTicket(raw, rsa_key);
1147 if (!pair)
1148 return false;
1149 const auto& [rid, key] = *pair;
1150 u128 rights_id;
1151 std::memcpy(rights_id.data(), rid.data(), rid.size());
1152 common_tickets[rights_id] = raw;
1153 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
1154 return true;
1155}
1156
1157bool KeyManager::AddTicketPersonalized(Ticket raw) {
1158 const auto rsa_key = GetETicketRSAKey();
1159 if (rsa_key == RSAKeyPair<2048>{})
1160 return false;
1161
1162 const auto pair = ParseTicket(raw, rsa_key);
1163 if (!pair)
1164 return false;
1165 const auto& [rid, key] = *pair;
1166 u128 rights_id;
1167 std::memcpy(rights_id.data(), rid.data(), rid.size());
1168 common_tickets[rights_id] = raw;
1169 SetKey(S128KeyType::Titlekey, key, rights_id[1], rights_id[0]);
1170 return true;
1171}
1172
1000const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = { 1173const boost::container::flat_map<std::string, KeyIndex<S128KeyType>> KeyManager::s128_file_id = {
1001 {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}}, 1174 {"eticket_rsa_kek", {S128KeyType::ETicketRSAKek, 0, 0}},
1002 {"eticket_rsa_kek_source", 1175 {"eticket_rsa_kek_source",
diff --git a/src/core/crypto/key_manager.h b/src/core/crypto/key_manager.h
index 22f268c65..7265c4171 100644
--- a/src/core/crypto/key_manager.h
+++ b/src/core/crypto/key_manager.h
@@ -9,8 +9,10 @@
9#include <optional> 9#include <optional>
10#include <string> 10#include <string>
11 11
12#include <variant>
12#include <boost/container/flat_map.hpp> 13#include <boost/container/flat_map.hpp>
13#include <fmt/format.h> 14#include <fmt/format.h>
15#include "common/common_funcs.h"
14#include "common/common_types.h" 16#include "common/common_types.h"
15#include "core/crypto/partition_data_manager.h" 17#include "core/crypto/partition_data_manager.h"
16#include "core/file_sys/vfs_types.h" 18#include "core/file_sys/vfs_types.h"
@@ -30,7 +32,79 @@ constexpr u64 TICKET_FILE_TITLEKEY_OFFSET = 0x180;
30using Key128 = std::array<u8, 0x10>; 32using Key128 = std::array<u8, 0x10>;
31using Key256 = std::array<u8, 0x20>; 33using Key256 = std::array<u8, 0x20>;
32using SHA256Hash = std::array<u8, 0x20>; 34using SHA256Hash = std::array<u8, 0x20>;
33using TicketRaw = std::array<u8, 0x400>; 35
36enum class SignatureType {
37 RSA_4096_SHA1 = 0x10000,
38 RSA_2048_SHA1 = 0x10001,
39 ECDSA_SHA1 = 0x10002,
40 RSA_4096_SHA256 = 0x10003,
41 RSA_2048_SHA256 = 0x10004,
42 ECDSA_SHA256 = 0x10005,
43};
44
45u64 GetSignatureTypeDataSize(SignatureType type);
46u64 GetSignatureTypePaddingSize(SignatureType type);
47
48enum class TitleKeyType : u8 {
49 Common = 0,
50 Personalized = 1,
51};
52
53struct TicketData {
54 std::array<u8, 0x40> issuer;
55 union {
56 std::array<u8, 0x100> title_key_block;
57
58 struct {
59 Key128 title_key_common;
60 std::array<u8, 0xF0> title_key_common_pad;
61 };
62 };
63
64 INSERT_PADDING_BYTES(0x1);
65 TitleKeyType type;
66 INSERT_PADDING_BYTES(0x3);
67 u8 revision;
68 INSERT_PADDING_BYTES(0xA);
69 u64 ticket_id;
70 u64 device_id;
71 std::array<u8, 0x10> rights_id;
72 u32 account_id;
73 INSERT_PADDING_BYTES(0x14C);
74};
75static_assert(sizeof(TicketData) == 0x2C0, "TicketData has incorrect size.");
76
77struct RSA4096Ticket {
78 SignatureType sig_type;
79 std::array<u8, 0x200> sig_data;
80 INSERT_PADDING_BYTES(0x3C);
81 TicketData data;
82};
83
84struct RSA2048Ticket {
85 SignatureType sig_type;
86 std::array<u8, 0x100> sig_data;
87 INSERT_PADDING_BYTES(0x3C);
88 TicketData data;
89};
90
91struct ECDSATicket {
92 SignatureType sig_type;
93 std::array<u8, 0x3C> sig_data;
94 INSERT_PADDING_BYTES(0x40);
95 TicketData data;
96};
97
98struct Ticket {
99 std::variant<RSA4096Ticket, RSA2048Ticket, ECDSATicket> data;
100
101 SignatureType GetSignatureType() const;
102 TicketData& GetData();
103 const TicketData& GetData() const;
104 u64 GetSize() const;
105
106 static Ticket SynthesizeCommon(Key128 title_key, const std::array<u8, 0x10>& rights_id);
107};
34 108
35static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big."); 109static_assert(sizeof(Key128) == 16, "Key128 must be 128 bytes big.");
36static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big."); 110static_assert(sizeof(Key256) == 32, "Key256 must be 256 bytes big.");
@@ -43,6 +117,19 @@ struct RSAKeyPair {
43 std::array<u8, 4> exponent; 117 std::array<u8, 4> exponent;
44}; 118};
45 119
120template <size_t bit_size, size_t byte_size>
121bool operator==(const RSAKeyPair<bit_size, byte_size>& lhs,
122 const RSAKeyPair<bit_size, byte_size>& rhs) {
123 return std::tie(lhs.encryption_key, lhs.decryption_key, lhs.modulus, lhs.exponent) ==
124 std::tie(rhs.encryption_key, rhs.decryption_key, rhs.modulus, rhs.exponent);
125}
126
127template <size_t bit_size, size_t byte_size>
128bool operator!=(const RSAKeyPair<bit_size, byte_size>& lhs,
129 const RSAKeyPair<bit_size, byte_size>& rhs) {
130 return !(lhs == rhs);
131}
132
46enum class KeyCategory : u8 { 133enum class KeyCategory : u8 {
47 Standard, 134 Standard,
48 Title, 135 Title,
@@ -151,22 +238,35 @@ public:
151 238
152 static bool KeyFileExists(bool title); 239 static bool KeyFileExists(bool title);
153 240
154 // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system save 241 // Call before using the sd seed to attempt to derive it if it dosen't exist. Needs system
155 // 8*43 and the private file to exist. 242 // save 8*43 and the private file to exist.
156 void DeriveSDSeedLazy(); 243 void DeriveSDSeedLazy();
157 244
158 bool BaseDeriveNecessary() const; 245 bool BaseDeriveNecessary() const;
159 void DeriveBase(); 246 void DeriveBase();
160 void DeriveETicket(PartitionDataManager& data); 247 void DeriveETicket(PartitionDataManager& data);
248 void PopulateTickets();
249 void SynthesizeTickets();
161 250
162 void PopulateFromPartitionData(PartitionDataManager& data); 251 void PopulateFromPartitionData(PartitionDataManager& data);
163 252
253 const std::map<u128, Ticket>& GetCommonTickets() const;
254 const std::map<u128, Ticket>& GetPersonalizedTickets() const;
255
256 bool AddTicketCommon(Ticket raw);
257 bool AddTicketPersonalized(Ticket raw);
258
164private: 259private:
165 std::map<KeyIndex<S128KeyType>, Key128> s128_keys; 260 std::map<KeyIndex<S128KeyType>, Key128> s128_keys;
166 std::map<KeyIndex<S256KeyType>, Key256> s256_keys; 261 std::map<KeyIndex<S256KeyType>, Key256> s256_keys;
167 262
263 // Map from rights ID to ticket
264 std::map<u128, Ticket> common_tickets;
265 std::map<u128, Ticket> personal_tickets;
266
168 std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{}; 267 std::array<std::array<u8, 0xB0>, 0x20> encrypted_keyblobs{};
169 std::array<std::array<u8, 0x90>, 0x20> keyblobs{}; 268 std::array<std::array<u8, 0x90>, 0x20> keyblobs{};
269 std::array<u8, 576> eticket_extended_kek{};
170 270
171 bool dev_mode; 271 bool dev_mode;
172 void LoadFromFile(const std::string& filename, bool is_title_keys); 272 void LoadFromFile(const std::string& filename, bool is_title_keys);
@@ -178,6 +278,8 @@ private:
178 278
179 void DeriveGeneralPurposeKeys(std::size_t crypto_revision); 279 void DeriveGeneralPurposeKeys(std::size_t crypto_revision);
180 280
281 RSAKeyPair<2048> GetETicketRSAKey() const;
282
181 void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0); 283 void SetKeyWrapped(S128KeyType id, Key128 key, u64 field1 = 0, u64 field2 = 0);
182 void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0); 284 void SetKeyWrapped(S256KeyType id, Key256 key, u64 field1 = 0, u64 field2 = 0);
183 285
@@ -195,11 +297,11 @@ std::array<u8, 0x90> DecryptKeyblob(const std::array<u8, 0xB0>& encrypted_keyblo
195std::optional<Key128> DeriveSDSeed(); 297std::optional<Key128> DeriveSDSeed();
196Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys); 298Loader::ResultStatus DeriveSDKeys(std::array<Key256, 2>& sd_keys, KeyManager& keys);
197 299
198std::vector<TicketRaw> GetTicketblob(const FileUtil::IOFile& ticket_save); 300std::vector<Ticket> GetTicketblob(const FileUtil::IOFile& ticket_save);
199 301
200// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority (offset 302// Returns a pair of {rights_id, titlekey}. Fails if the ticket has no certificate authority
201// 0x140-0x144 is zero) 303// (offset 0x140-0x144 is zero)
202std::optional<std::pair<Key128, Key128>> ParseTicket(const TicketRaw& ticket, 304std::optional<std::pair<Key128, Key128>> ParseTicket(const Ticket& ticket,
203 const RSAKeyPair<2048>& eticket_extended_key); 305 const RSAKeyPair<2048>& eticket_extended_key);
204 306
205} // namespace Core::Crypto 307} // namespace Core::Crypto
diff --git a/src/core/file_sys/system_archive/mii_model.cpp b/src/core/file_sys/system_archive/mii_model.cpp
new file mode 100644
index 000000000..6a9add87c
--- /dev/null
+++ b/src/core/file_sys/system_archive/mii_model.cpp
@@ -0,0 +1,46 @@
1// Copyright 2019 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include "core/file_sys/system_archive/mii_model.h"
6#include "core/file_sys/vfs_vector.h"
7
8namespace FileSys::SystemArchive {
9
10namespace MiiModelData {
11
12constexpr std::array<u8, 0x10> NFTR_STANDARD{'N', 'F', 'T', 'R', 0x01, 0x00, 0x00, 0x00,
13 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
14constexpr std::array<u8, 0x10> NFSR_STANDARD{'N', 'F', 'S', 'R', 0x01, 0x00, 0x00, 0x00,
15 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
16
17constexpr auto TEXTURE_LOW_LINEAR = NFTR_STANDARD;
18constexpr auto TEXTURE_LOW_SRGB = NFTR_STANDARD;
19constexpr auto TEXTURE_MID_LINEAR = NFTR_STANDARD;
20constexpr auto TEXTURE_MID_SRGB = NFTR_STANDARD;
21constexpr auto SHAPE_HIGH = NFSR_STANDARD;
22constexpr auto SHAPE_MID = NFSR_STANDARD;
23
24} // namespace MiiModelData
25
26VirtualDir MiiModel() {
27 auto out = std::make_shared<VectorVfsDirectory>(std::vector<VirtualFile>{},
28 std::vector<VirtualDir>{}, "data");
29
30 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_LINEAR.size()>>(
31 MiiModelData::TEXTURE_LOW_LINEAR, "NXTextureLowLinear.dat"));
32 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_LOW_SRGB.size()>>(
33 MiiModelData::TEXTURE_LOW_SRGB, "NXTextureLowSRGB.dat"));
34 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_LINEAR.size()>>(
35 MiiModelData::TEXTURE_MID_LINEAR, "NXTextureMidLinear.dat"));
36 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::TEXTURE_MID_SRGB.size()>>(
37 MiiModelData::TEXTURE_MID_SRGB, "NXTextureMidSRGB.dat"));
38 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_HIGH.size()>>(
39 MiiModelData::SHAPE_HIGH, "ShapeHigh.dat"));
40 out->AddFile(std::make_shared<ArrayVfsFile<MiiModelData::SHAPE_MID.size()>>(
41 MiiModelData::SHAPE_MID, "ShapeMid.dat"));
42
43 return std::move(out);
44}
45
46} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/mii_model.h b/src/core/file_sys/system_archive/mii_model.h
new file mode 100644
index 000000000..6c2d9398b
--- /dev/null
+++ b/src/core/file_sys/system_archive/mii_model.h
@@ -0,0 +1,13 @@
1// Copyright 2019 yuzu emulator team
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include "core/file_sys/vfs_types.h"
8
9namespace FileSys::SystemArchive {
10
11VirtualDir MiiModel();
12
13} // namespace FileSys::SystemArchive
diff --git a/src/core/file_sys/system_archive/system_archive.cpp b/src/core/file_sys/system_archive/system_archive.cpp
index c9722ed77..6d8445383 100644
--- a/src/core/file_sys/system_archive/system_archive.cpp
+++ b/src/core/file_sys/system_archive/system_archive.cpp
@@ -4,6 +4,7 @@
4 4
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "core/file_sys/romfs.h" 6#include "core/file_sys/romfs.h"
7#include "core/file_sys/system_archive/mii_model.h"
7#include "core/file_sys/system_archive/ng_word.h" 8#include "core/file_sys/system_archive/ng_word.h"
8#include "core/file_sys/system_archive/system_archive.h" 9#include "core/file_sys/system_archive/system_archive.h"
9#include "core/file_sys/system_archive/system_version.h" 10#include "core/file_sys/system_archive/system_version.h"
@@ -24,7 +25,7 @@ struct SystemArchiveDescriptor {
24constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{ 25constexpr std::array<SystemArchiveDescriptor, SYSTEM_ARCHIVE_COUNT> SYSTEM_ARCHIVES{{
25 {0x0100000000000800, "CertStore", nullptr}, 26 {0x0100000000000800, "CertStore", nullptr},
26 {0x0100000000000801, "ErrorMessage", nullptr}, 27 {0x0100000000000801, "ErrorMessage", nullptr},
27 {0x0100000000000802, "MiiModel", nullptr}, 28 {0x0100000000000802, "MiiModel", &MiiModel},
28 {0x0100000000000803, "BrowserDll", nullptr}, 29 {0x0100000000000803, "BrowserDll", nullptr},
29 {0x0100000000000804, "Help", nullptr}, 30 {0x0100000000000804, "Help", nullptr},
30 {0x0100000000000805, "SharedFont", nullptr}, 31 {0x0100000000000805, "SharedFont", nullptr},
diff --git a/src/core/hle/kernel/vm_manager.cpp b/src/core/hle/kernel/vm_manager.cpp
index 40cea1e7c..c7af87073 100644
--- a/src/core/hle/kernel/vm_manager.cpp
+++ b/src/core/hle/kernel/vm_manager.cpp
@@ -296,12 +296,6 @@ ResultVal<VAddr> VMManager::SetHeapSize(u64 size) {
296} 296}
297 297
298ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) { 298ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
299 const auto end_addr = target + size;
300 const auto last_addr = end_addr - 1;
301 VAddr cur_addr = target;
302
303 ResultCode result = RESULT_SUCCESS;
304
305 // Check how much memory we've already mapped. 299 // Check how much memory we've already mapped.
306 const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size); 300 const auto mapped_size_result = SizeOfAllocatedVMAsInRange(target, size);
307 if (mapped_size_result.Failed()) { 301 if (mapped_size_result.Failed()) {
@@ -324,13 +318,16 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
324 318
325 // Keep track of the memory regions we unmap. 319 // Keep track of the memory regions we unmap.
326 std::vector<std::pair<u64, u64>> mapped_regions; 320 std::vector<std::pair<u64, u64>> mapped_regions;
321 ResultCode result = RESULT_SUCCESS;
327 322
328 // Iterate, trying to map memory. 323 // Iterate, trying to map memory.
329 { 324 {
330 cur_addr = target; 325 const auto end_addr = target + size;
326 const auto last_addr = end_addr - 1;
327 VAddr cur_addr = target;
331 328
332 auto iter = FindVMA(target); 329 auto iter = FindVMA(target);
333 ASSERT_MSG(iter != vma_map.end(), "MapPhysicalMemory iter != end"); 330 ASSERT(iter != vma_map.end());
334 331
335 while (true) { 332 while (true) {
336 const auto& vma = iter->second; 333 const auto& vma = iter->second;
@@ -342,7 +339,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
342 const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr); 339 const auto map_size = std::min(end_addr - cur_addr, vma_end - cur_addr);
343 if (vma.state == MemoryState::Unmapped) { 340 if (vma.state == MemoryState::Unmapped) {
344 const auto map_res = 341 const auto map_res =
345 MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size, 0), 0, 342 MapMemoryBlock(cur_addr, std::make_shared<PhysicalMemory>(map_size), 0,
346 map_size, MemoryState::Heap, VMAPermission::ReadWrite); 343 map_size, MemoryState::Heap, VMAPermission::ReadWrite);
347 result = map_res.Code(); 344 result = map_res.Code();
348 if (result.IsError()) { 345 if (result.IsError()) {
@@ -360,7 +357,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
360 // Advance to the next block. 357 // Advance to the next block.
361 cur_addr = vma_end; 358 cur_addr = vma_end;
362 iter = FindVMA(cur_addr); 359 iter = FindVMA(cur_addr);
363 ASSERT_MSG(iter != vma_map.end(), "MapPhysicalMemory iter != end"); 360 ASSERT(iter != vma_map.end());
364 } 361 }
365 } 362 }
366 363
@@ -368,7 +365,7 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
368 if (result.IsError()) { 365 if (result.IsError()) {
369 for (const auto [unmap_address, unmap_size] : mapped_regions) { 366 for (const auto [unmap_address, unmap_size] : mapped_regions) {
370 ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(), 367 ASSERT_MSG(UnmapRange(unmap_address, unmap_size).IsSuccess(),
371 "MapPhysicalMemory un-map on error"); 368 "Failed to unmap memory range.");
372 } 369 }
373 370
374 return result; 371 return result;
@@ -381,12 +378,6 @@ ResultCode VMManager::MapPhysicalMemory(VAddr target, u64 size) {
381} 378}
382 379
383ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) { 380ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
384 const auto end_addr = target + size;
385 const auto last_addr = end_addr - 1;
386 VAddr cur_addr = target;
387
388 ResultCode result = RESULT_SUCCESS;
389
390 // Check how much memory is currently mapped. 381 // Check how much memory is currently mapped.
391 const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size); 382 const auto mapped_size_result = SizeOfUnmappablePhysicalMemoryInRange(target, size);
392 if (mapped_size_result.Failed()) { 383 if (mapped_size_result.Failed()) {
@@ -401,13 +392,16 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
401 392
402 // Keep track of the memory regions we unmap. 393 // Keep track of the memory regions we unmap.
403 std::vector<std::pair<u64, u64>> unmapped_regions; 394 std::vector<std::pair<u64, u64>> unmapped_regions;
395 ResultCode result = RESULT_SUCCESS;
404 396
405 // Try to unmap regions. 397 // Try to unmap regions.
406 { 398 {
407 cur_addr = target; 399 const auto end_addr = target + size;
400 const auto last_addr = end_addr - 1;
401 VAddr cur_addr = target;
408 402
409 auto iter = FindVMA(target); 403 auto iter = FindVMA(target);
410 ASSERT_MSG(iter != vma_map.end(), "UnmapPhysicalMemory iter != end"); 404 ASSERT(iter != vma_map.end());
411 405
412 while (true) { 406 while (true) {
413 const auto& vma = iter->second; 407 const auto& vma = iter->second;
@@ -434,7 +428,7 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
434 // Advance to the next block. 428 // Advance to the next block.
435 cur_addr = vma_end; 429 cur_addr = vma_end;
436 iter = FindVMA(cur_addr); 430 iter = FindVMA(cur_addr);
437 ASSERT_MSG(iter != vma_map.end(), "UnmapPhysicalMemory iter != end"); 431 ASSERT(iter != vma_map.end());
438 } 432 }
439 } 433 }
440 434
@@ -443,10 +437,12 @@ ResultCode VMManager::UnmapPhysicalMemory(VAddr target, u64 size) {
443 if (result.IsError()) { 437 if (result.IsError()) {
444 for (const auto [map_address, map_size] : unmapped_regions) { 438 for (const auto [map_address, map_size] : unmapped_regions) {
445 const auto remap_res = 439 const auto remap_res =
446 MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size, 0), 0, 440 MapMemoryBlock(map_address, std::make_shared<PhysicalMemory>(map_size), 0, map_size,
447 map_size, MemoryState::Heap, VMAPermission::None); 441 MemoryState::Heap, VMAPermission::None);
448 ASSERT_MSG(remap_res.Succeeded(), "UnmapPhysicalMemory re-map on error"); 442 ASSERT_MSG(remap_res.Succeeded(), "Failed to remap a memory block.");
449 } 443 }
444
445 return result;
450 } 446 }
451 447
452 // Update mapped amount 448 // Update mapped amount
@@ -757,20 +753,26 @@ void VMManager::MergeAdjacentVMA(VirtualMemoryArea& left, const VirtualMemoryAre
757 // Always merge allocated memory blocks, even when they don't share the same backing block. 753 // Always merge allocated memory blocks, even when they don't share the same backing block.
758 if (left.type == VMAType::AllocatedMemoryBlock && 754 if (left.type == VMAType::AllocatedMemoryBlock &&
759 (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) { 755 (left.backing_block != right.backing_block || left.offset + left.size != right.offset)) {
756 const auto right_begin = right.backing_block->begin() + right.offset;
757 const auto right_end = right_begin + right.size;
758
760 // Check if we can save work. 759 // Check if we can save work.
761 if (left.offset == 0 && left.size == left.backing_block->size()) { 760 if (left.offset == 0 && left.size == left.backing_block->size()) {
762 // Fast case: left is an entire backing block. 761 // Fast case: left is an entire backing block.
763 left.backing_block->insert(left.backing_block->end(), 762 left.backing_block->insert(left.backing_block->end(), right_begin, right_end);
764 right.backing_block->begin() + right.offset,
765 right.backing_block->begin() + right.offset + right.size);
766 } else { 763 } else {
767 // Slow case: make a new memory block for left and right. 764 // Slow case: make a new memory block for left and right.
765 const auto left_begin = left.backing_block->begin() + left.offset;
766 const auto left_end = left_begin + left.size;
767 const auto left_size = static_cast<std::size_t>(std::distance(left_begin, left_end));
768 const auto right_size = static_cast<std::size_t>(std::distance(right_begin, right_end));
769
768 auto new_memory = std::make_shared<PhysicalMemory>(); 770 auto new_memory = std::make_shared<PhysicalMemory>();
769 new_memory->insert(new_memory->end(), left.backing_block->begin() + left.offset, 771 new_memory->reserve(left_size + right_size);
770 left.backing_block->begin() + left.offset + left.size); 772 new_memory->insert(new_memory->end(), left_begin, left_end);
771 new_memory->insert(new_memory->end(), right.backing_block->begin() + right.offset, 773 new_memory->insert(new_memory->end(), right_begin, right_end);
772 right.backing_block->begin() + right.offset + right.size); 774
773 left.backing_block = new_memory; 775 left.backing_block = std::move(new_memory);
774 left.offset = 0; 776 left.offset = 0;
775 } 777 }
776 778
@@ -965,7 +967,7 @@ ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address,
965 967
966 VAddr cur_addr = address; 968 VAddr cur_addr = address;
967 auto iter = FindVMA(cur_addr); 969 auto iter = FindVMA(cur_addr);
968 ASSERT_MSG(iter != vma_map.end(), "SizeOfAllocatedVMAsInRange iter != end"); 970 ASSERT(iter != vma_map.end());
969 971
970 while (true) { 972 while (true) {
971 const auto& vma = iter->second; 973 const auto& vma = iter->second;
@@ -986,7 +988,7 @@ ResultVal<std::size_t> VMManager::SizeOfAllocatedVMAsInRange(VAddr address,
986 // Advance to the next block. 988 // Advance to the next block.
987 cur_addr = vma_end; 989 cur_addr = vma_end;
988 iter = std::next(iter); 990 iter = std::next(iter);
989 ASSERT_MSG(iter != vma_map.end(), "SizeOfAllocatedVMAsInRange iter != end"); 991 ASSERT(iter != vma_map.end());
990 } 992 }
991 993
992 return MakeResult(mapped_size); 994 return MakeResult(mapped_size);
@@ -1000,7 +1002,7 @@ ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr ad
1000 1002
1001 VAddr cur_addr = address; 1003 VAddr cur_addr = address;
1002 auto iter = FindVMA(cur_addr); 1004 auto iter = FindVMA(cur_addr);
1003 ASSERT_MSG(iter != vma_map.end(), "SizeOfUnmappablePhysicalMemoryInRange iter != end"); 1005 ASSERT(iter != vma_map.end());
1004 1006
1005 while (true) { 1007 while (true) {
1006 const auto& vma = iter->second; 1008 const auto& vma = iter->second;
@@ -1029,7 +1031,7 @@ ResultVal<std::size_t> VMManager::SizeOfUnmappablePhysicalMemoryInRange(VAddr ad
1029 // Advance to the next block. 1031 // Advance to the next block.
1030 cur_addr = vma_end; 1032 cur_addr = vma_end;
1031 iter = std::next(iter); 1033 iter = std::next(iter);
1032 ASSERT_MSG(iter != vma_map.end(), "SizeOfUnmappablePhysicalMemoryInRange iter != end"); 1034 ASSERT(iter != vma_map.end());
1033 } 1035 }
1034 1036
1035 return MakeResult(mapped_size); 1037 return MakeResult(mapped_size);
diff --git a/src/core/hle/kernel/vm_manager.h b/src/core/hle/kernel/vm_manager.h
index b18cde619..850a7ebc3 100644
--- a/src/core/hle/kernel/vm_manager.h
+++ b/src/core/hle/kernel/vm_manager.h
@@ -454,8 +454,8 @@ public:
454 454
455 /// Maps memory at a given address. 455 /// Maps memory at a given address.
456 /// 456 ///
457 /// @param addr The virtual address to map memory at. 457 /// @param target The virtual address to map memory at.
458 /// @param size The amount of memory to map. 458 /// @param size The amount of memory to map.
459 /// 459 ///
460 /// @note The destination address must lie within the Map region. 460 /// @note The destination address must lie within the Map region.
461 /// 461 ///
@@ -468,8 +468,8 @@ public:
468 468
469 /// Unmaps memory at a given address. 469 /// Unmaps memory at a given address.
470 /// 470 ///
471 /// @param addr The virtual address to unmap memory at. 471 /// @param target The virtual address to unmap memory at.
472 /// @param size The amount of memory to unmap. 472 /// @param size The amount of memory to unmap.
473 /// 473 ///
474 /// @note The destination address must lie within the Map region. 474 /// @note The destination address must lie within the Map region.
475 /// 475 ///
diff --git a/src/core/hle/service/am/am.cpp b/src/core/hle/service/am/am.cpp
index a192a1f5f..aa2c83937 100644
--- a/src/core/hle/service/am/am.cpp
+++ b/src/core/hle/service/am/am.cpp
@@ -56,7 +56,8 @@ struct LaunchParameters {
56}; 56};
57static_assert(sizeof(LaunchParameters) == 0x88); 57static_assert(sizeof(LaunchParameters) == 0x88);
58 58
59IWindowController::IWindowController() : ServiceFramework("IWindowController") { 59IWindowController::IWindowController(Core::System& system_)
60 : ServiceFramework("IWindowController"), system{system_} {
60 // clang-format off 61 // clang-format off
61 static const FunctionInfo functions[] = { 62 static const FunctionInfo functions[] = {
62 {0, nullptr, "CreateWindow"}, 63 {0, nullptr, "CreateWindow"},
@@ -75,7 +76,7 @@ IWindowController::IWindowController() : ServiceFramework("IWindowController") {
75IWindowController::~IWindowController() = default; 76IWindowController::~IWindowController() = default;
76 77
77void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) { 78void IWindowController::GetAppletResourceUserId(Kernel::HLERequestContext& ctx) {
78 const u64 process_id = Core::System::GetInstance().Kernel().CurrentProcess()->GetProcessID(); 79 const u64 process_id = system.CurrentProcess()->GetProcessID();
79 80
80 LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id); 81 LOG_DEBUG(Service_AM, "called. Process ID=0x{:016X}", process_id);
81 82
@@ -231,8 +232,9 @@ IDebugFunctions::IDebugFunctions() : ServiceFramework{"IDebugFunctions"} {
231 232
232IDebugFunctions::~IDebugFunctions() = default; 233IDebugFunctions::~IDebugFunctions() = default;
233 234
234ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger) 235ISelfController::ISelfController(Core::System& system_,
235 : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger)) { 236 std::shared_ptr<NVFlinger::NVFlinger> nvflinger_)
237 : ServiceFramework("ISelfController"), nvflinger(std::move(nvflinger_)) {
236 // clang-format off 238 // clang-format off
237 static const FunctionInfo functions[] = { 239 static const FunctionInfo functions[] = {
238 {0, nullptr, "Exit"}, 240 {0, nullptr, "Exit"},
@@ -280,7 +282,7 @@ ISelfController::ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger
280 282
281 RegisterHandlers(functions); 283 RegisterHandlers(functions);
282 284
283 auto& kernel = Core::System::GetInstance().Kernel(); 285 auto& kernel = system_.Kernel();
284 launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, 286 launchable_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
285 "ISelfController:LaunchableEvent"); 287 "ISelfController:LaunchableEvent");
286 288
@@ -501,8 +503,7 @@ void ISelfController::GetAccumulatedSuspendedTickChangedEvent(Kernel::HLERequest
501 rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable); 503 rb.PushCopyObjects(accumulated_suspended_tick_changed_event.readable);
502} 504}
503 505
504AppletMessageQueue::AppletMessageQueue() { 506AppletMessageQueue::AppletMessageQueue(Kernel::KernelCore& kernel) {
505 auto& kernel = Core::System::GetInstance().Kernel();
506 on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual, 507 on_new_message = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Manual,
507 "AMMessageQueue:OnMessageRecieved"); 508 "AMMessageQueue:OnMessageRecieved");
508 on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair( 509 on_operation_mode_changed = Kernel::WritableEvent::CreateEventPair(
@@ -937,9 +938,8 @@ void IStorageAccessor::Read(Kernel::HLERequestContext& ctx) {
937 rb.Push(RESULT_SUCCESS); 938 rb.Push(RESULT_SUCCESS);
938} 939}
939 940
940ILibraryAppletCreator::ILibraryAppletCreator(u64 current_process_title_id) 941ILibraryAppletCreator::ILibraryAppletCreator(Core::System& system_)
941 : ServiceFramework("ILibraryAppletCreator"), 942 : ServiceFramework("ILibraryAppletCreator"), system{system_} {
942 current_process_title_id(current_process_title_id) {
943 static const FunctionInfo functions[] = { 943 static const FunctionInfo functions[] = {
944 {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"}, 944 {0, &ILibraryAppletCreator::CreateLibraryApplet, "CreateLibraryApplet"},
945 {1, nullptr, "TerminateAllLibraryApplets"}, 945 {1, nullptr, "TerminateAllLibraryApplets"},
@@ -961,8 +961,8 @@ void ILibraryAppletCreator::CreateLibraryApplet(Kernel::HLERequestContext& ctx)
961 LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}", 961 LOG_DEBUG(Service_AM, "called with applet_id={:08X}, applet_mode={:08X}",
962 static_cast<u32>(applet_id), applet_mode); 962 static_cast<u32>(applet_id), applet_mode);
963 963
964 const auto& applet_manager{Core::System::GetInstance().GetAppletManager()}; 964 const auto& applet_manager{system.GetAppletManager()};
965 const auto applet = applet_manager.GetApplet(applet_id, current_process_title_id); 965 const auto applet = applet_manager.GetApplet(applet_id);
966 966
967 if (applet == nullptr) { 967 if (applet == nullptr) {
968 LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id)); 968 LOG_ERROR(Service_AM, "Applet doesn't exist! applet_id={}", static_cast<u32>(applet_id));
@@ -999,8 +999,7 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex
999 const auto handle{rp.Pop<Kernel::Handle>()}; 999 const auto handle{rp.Pop<Kernel::Handle>()};
1000 1000
1001 const auto transfer_mem = 1001 const auto transfer_mem =
1002 Core::System::GetInstance().CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>( 1002 system.CurrentProcess()->GetHandleTable().Get<Kernel::TransferMemory>(handle);
1003 handle);
1004 1003
1005 if (transfer_mem == nullptr) { 1004 if (transfer_mem == nullptr) {
1006 LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle); 1005 LOG_ERROR(Service_AM, "shared_mem is a nullpr for handle={:08X}", handle);
@@ -1018,7 +1017,8 @@ void ILibraryAppletCreator::CreateTransferMemoryStorage(Kernel::HLERequestContex
1018 rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory))); 1017 rb.PushIpcInterface(std::make_shared<IStorage>(std::move(memory)));
1019} 1018}
1020 1019
1021IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationFunctions") { 1020IApplicationFunctions::IApplicationFunctions(Core::System& system_)
1021 : ServiceFramework("IApplicationFunctions"), system{system_} {
1022 // clang-format off 1022 // clang-format off
1023 static const FunctionInfo functions[] = { 1023 static const FunctionInfo functions[] = {
1024 {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"}, 1024 {1, &IApplicationFunctions::PopLaunchParameter, "PopLaunchParameter"},
@@ -1057,6 +1057,7 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
1057 {120, nullptr, "ExecuteProgram"}, 1057 {120, nullptr, "ExecuteProgram"},
1058 {121, nullptr, "ClearUserChannel"}, 1058 {121, nullptr, "ClearUserChannel"},
1059 {122, nullptr, "UnpopToUserChannel"}, 1059 {122, nullptr, "UnpopToUserChannel"},
1060 {130, &IApplicationFunctions::GetGpuErrorDetectedSystemEvent, "GetGpuErrorDetectedSystemEvent"},
1060 {500, nullptr, "StartContinuousRecordingFlushForDebug"}, 1061 {500, nullptr, "StartContinuousRecordingFlushForDebug"},
1061 {1000, nullptr, "CreateMovieMaker"}, 1062 {1000, nullptr, "CreateMovieMaker"},
1062 {1001, nullptr, "PrepareForJit"}, 1063 {1001, nullptr, "PrepareForJit"},
@@ -1064,6 +1065,10 @@ IApplicationFunctions::IApplicationFunctions() : ServiceFramework("IApplicationF
1064 // clang-format on 1065 // clang-format on
1065 1066
1066 RegisterHandlers(functions); 1067 RegisterHandlers(functions);
1068
1069 auto& kernel = Core::System::GetInstance().Kernel();
1070 gpu_error_detected_event = Kernel::WritableEvent::CreateEventPair(
1071 kernel, Kernel::ResetType::Manual, "IApplicationFunctions:GpuErrorDetectedSystemEvent");
1067} 1072}
1068 1073
1069IApplicationFunctions::~IApplicationFunctions() = default; 1074IApplicationFunctions::~IApplicationFunctions() = default;
@@ -1175,7 +1180,7 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
1175 // Get supported languages from NACP, if possible 1180 // Get supported languages from NACP, if possible
1176 // Default to 0 (all languages supported) 1181 // Default to 0 (all languages supported)
1177 u32 supported_languages = 0; 1182 u32 supported_languages = 0;
1178 FileSys::PatchManager pm{Core::System::GetInstance().CurrentProcess()->GetTitleID()}; 1183 FileSys::PatchManager pm{system.CurrentProcess()->GetTitleID()};
1179 1184
1180 const auto res = pm.GetControlMetadata(); 1185 const auto res = pm.GetControlMetadata();
1181 if (res.first != nullptr) { 1186 if (res.first != nullptr) {
@@ -1183,8 +1188,8 @@ void IApplicationFunctions::GetDesiredLanguage(Kernel::HLERequestContext& ctx) {
1183 } 1188 }
1184 1189
1185 // Call IApplicationManagerInterface implementation. 1190 // Call IApplicationManagerInterface implementation.
1186 auto& service_manager = Core::System::GetInstance().ServiceManager(); 1191 auto& service_manager = system.ServiceManager();
1187 auto ns_am2 = service_manager.GetService<Service::NS::NS>("ns:am2"); 1192 auto ns_am2 = service_manager.GetService<NS::NS>("ns:am2");
1188 auto app_man = ns_am2->GetApplicationManagerInterface(); 1193 auto app_man = ns_am2->GetApplicationManagerInterface();
1189 1194
1190 // Get desired application language 1195 // Get desired application language
@@ -1256,8 +1261,8 @@ void IApplicationFunctions::ExtendSaveData(Kernel::HLERequestContext& ctx) {
1256 "new_journal={:016X}", 1261 "new_journal={:016X}",
1257 static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size); 1262 static_cast<u8>(type), user_id[1], user_id[0], new_normal_size, new_journal_size);
1258 1263
1259 FileSystem::WriteSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id, 1264 const auto title_id = system.CurrentProcess()->GetTitleID();
1260 {new_normal_size, new_journal_size}); 1265 FileSystem::WriteSaveDataSize(type, title_id, user_id, {new_normal_size, new_journal_size});
1261 1266
1262 IPC::ResponseBuilder rb{ctx, 4}; 1267 IPC::ResponseBuilder rb{ctx, 4};
1263 rb.Push(RESULT_SUCCESS); 1268 rb.Push(RESULT_SUCCESS);
@@ -1276,8 +1281,8 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
1276 LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type), 1281 LOG_DEBUG(Service_AM, "called with type={:02X}, user_id={:016X}{:016X}", static_cast<u8>(type),
1277 user_id[1], user_id[0]); 1282 user_id[1], user_id[0]);
1278 1283
1279 const auto size = 1284 const auto title_id = system.CurrentProcess()->GetTitleID();
1280 FileSystem::ReadSaveDataSize(type, Core::CurrentProcess()->GetTitleID(), user_id); 1285 const auto size = FileSystem::ReadSaveDataSize(type, title_id, user_id);
1281 1286
1282 IPC::ResponseBuilder rb{ctx, 6}; 1287 IPC::ResponseBuilder rb{ctx, 6};
1283 rb.Push(RESULT_SUCCESS); 1288 rb.Push(RESULT_SUCCESS);
@@ -1285,11 +1290,19 @@ void IApplicationFunctions::GetSaveDataSize(Kernel::HLERequestContext& ctx) {
1285 rb.Push(size.journal); 1290 rb.Push(size.journal);
1286} 1291}
1287 1292
1293void IApplicationFunctions::GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx) {
1294 LOG_WARNING(Service_AM, "(STUBBED) called");
1295
1296 IPC::ResponseBuilder rb{ctx, 2, 1};
1297 rb.Push(RESULT_SUCCESS);
1298 rb.PushCopyObjects(gpu_error_detected_event.readable);
1299}
1300
1288void InstallInterfaces(SM::ServiceManager& service_manager, 1301void InstallInterfaces(SM::ServiceManager& service_manager,
1289 std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) { 1302 std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system) {
1290 auto message_queue = std::make_shared<AppletMessageQueue>(); 1303 auto message_queue = std::make_shared<AppletMessageQueue>(system.Kernel());
1291 message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged); // Needed on 1304 // Needed on game boot
1292 // game boot 1305 message_queue->PushMessage(AppletMessageQueue::AppletMessage::FocusStateChanged);
1293 1306
1294 std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager); 1307 std::make_shared<AppletAE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
1295 std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager); 1308 std::make_shared<AppletOE>(nvflinger, message_queue, system)->InstallAsService(service_manager);
diff --git a/src/core/hle/service/am/am.h b/src/core/hle/service/am/am.h
index 6cb582483..28f870302 100644
--- a/src/core/hle/service/am/am.h
+++ b/src/core/hle/service/am/am.h
@@ -10,12 +10,15 @@
10#include "core/hle/kernel/writable_event.h" 10#include "core/hle/kernel/writable_event.h"
11#include "core/hle/service/service.h" 11#include "core/hle/service/service.h"
12 12
13namespace Service { 13namespace Kernel {
14namespace NVFlinger { 14class KernelCore;
15}
16
17namespace Service::NVFlinger {
15class NVFlinger; 18class NVFlinger;
16} 19}
17 20
18namespace AM { 21namespace Service::AM {
19 22
20enum SystemLanguage { 23enum SystemLanguage {
21 Japanese = 0, 24 Japanese = 0,
@@ -47,7 +50,7 @@ public:
47 PerformanceModeChanged = 31, 50 PerformanceModeChanged = 31,
48 }; 51 };
49 52
50 AppletMessageQueue(); 53 explicit AppletMessageQueue(Kernel::KernelCore& kernel);
51 ~AppletMessageQueue(); 54 ~AppletMessageQueue();
52 55
53 const Kernel::SharedPtr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const; 56 const Kernel::SharedPtr<Kernel::ReadableEvent>& GetMesssageRecieveEvent() const;
@@ -65,12 +68,14 @@ private:
65 68
66class IWindowController final : public ServiceFramework<IWindowController> { 69class IWindowController final : public ServiceFramework<IWindowController> {
67public: 70public:
68 IWindowController(); 71 explicit IWindowController(Core::System& system_);
69 ~IWindowController() override; 72 ~IWindowController() override;
70 73
71private: 74private:
72 void GetAppletResourceUserId(Kernel::HLERequestContext& ctx); 75 void GetAppletResourceUserId(Kernel::HLERequestContext& ctx);
73 void AcquireForegroundRights(Kernel::HLERequestContext& ctx); 76 void AcquireForegroundRights(Kernel::HLERequestContext& ctx);
77
78 Core::System& system;
74}; 79};
75 80
76class IAudioController final : public ServiceFramework<IAudioController> { 81class IAudioController final : public ServiceFramework<IAudioController> {
@@ -113,7 +118,8 @@ public:
113 118
114class ISelfController final : public ServiceFramework<ISelfController> { 119class ISelfController final : public ServiceFramework<ISelfController> {
115public: 120public:
116 explicit ISelfController(std::shared_ptr<NVFlinger::NVFlinger> nvflinger); 121 explicit ISelfController(Core::System& system_,
122 std::shared_ptr<NVFlinger::NVFlinger> nvflinger_);
117 ~ISelfController() override; 123 ~ISelfController() override;
118 124
119private: 125private:
@@ -208,7 +214,7 @@ private:
208 214
209class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> { 215class ILibraryAppletCreator final : public ServiceFramework<ILibraryAppletCreator> {
210public: 216public:
211 ILibraryAppletCreator(u64 current_process_title_id); 217 explicit ILibraryAppletCreator(Core::System& system_);
212 ~ILibraryAppletCreator() override; 218 ~ILibraryAppletCreator() override;
213 219
214private: 220private:
@@ -216,12 +222,12 @@ private:
216 void CreateStorage(Kernel::HLERequestContext& ctx); 222 void CreateStorage(Kernel::HLERequestContext& ctx);
217 void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx); 223 void CreateTransferMemoryStorage(Kernel::HLERequestContext& ctx);
218 224
219 u64 current_process_title_id; 225 Core::System& system;
220}; 226};
221 227
222class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> { 228class IApplicationFunctions final : public ServiceFramework<IApplicationFunctions> {
223public: 229public:
224 IApplicationFunctions(); 230 explicit IApplicationFunctions(Core::System& system_);
225 ~IApplicationFunctions() override; 231 ~IApplicationFunctions() override;
226 232
227private: 233private:
@@ -242,6 +248,10 @@ private:
242 void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx); 248 void BeginBlockingHomeButton(Kernel::HLERequestContext& ctx);
243 void EndBlockingHomeButton(Kernel::HLERequestContext& ctx); 249 void EndBlockingHomeButton(Kernel::HLERequestContext& ctx);
244 void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx); 250 void EnableApplicationCrashReport(Kernel::HLERequestContext& ctx);
251 void GetGpuErrorDetectedSystemEvent(Kernel::HLERequestContext& ctx);
252
253 Kernel::EventPair gpu_error_detected_event;
254 Core::System& system;
245}; 255};
246 256
247class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> { 257class IHomeMenuFunctions final : public ServiceFramework<IHomeMenuFunctions> {
@@ -275,5 +285,4 @@ public:
275void InstallInterfaces(SM::ServiceManager& service_manager, 285void InstallInterfaces(SM::ServiceManager& service_manager,
276 std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system); 286 std::shared_ptr<NVFlinger::NVFlinger> nvflinger, Core::System& system);
277 287
278} // namespace AM 288} // namespace Service::AM
279} // namespace Service
diff --git a/src/core/hle/service/am/applet_ae.cpp b/src/core/hle/service/am/applet_ae.cpp
index a34368c8b..e454b77d8 100644
--- a/src/core/hle/service/am/applet_ae.cpp
+++ b/src/core/hle/service/am/applet_ae.cpp
@@ -50,7 +50,7 @@ private:
50 50
51 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 51 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
52 rb.Push(RESULT_SUCCESS); 52 rb.Push(RESULT_SUCCESS);
53 rb.PushIpcInterface<ISelfController>(nvflinger); 53 rb.PushIpcInterface<ISelfController>(system, nvflinger);
54 } 54 }
55 55
56 void GetWindowController(Kernel::HLERequestContext& ctx) { 56 void GetWindowController(Kernel::HLERequestContext& ctx) {
@@ -58,7 +58,7 @@ private:
58 58
59 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 59 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
60 rb.Push(RESULT_SUCCESS); 60 rb.Push(RESULT_SUCCESS);
61 rb.PushIpcInterface<IWindowController>(); 61 rb.PushIpcInterface<IWindowController>(system);
62 } 62 }
63 63
64 void GetAudioController(Kernel::HLERequestContext& ctx) { 64 void GetAudioController(Kernel::HLERequestContext& ctx) {
@@ -98,7 +98,7 @@ private:
98 98
99 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 99 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
100 rb.Push(RESULT_SUCCESS); 100 rb.Push(RESULT_SUCCESS);
101 rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); 101 rb.PushIpcInterface<ILibraryAppletCreator>(system);
102 } 102 }
103 103
104 void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { 104 void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
@@ -106,7 +106,7 @@ private:
106 106
107 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 107 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
108 rb.Push(RESULT_SUCCESS); 108 rb.Push(RESULT_SUCCESS);
109 rb.PushIpcInterface<IApplicationFunctions>(); 109 rb.PushIpcInterface<IApplicationFunctions>(system);
110 } 110 }
111 111
112 std::shared_ptr<NVFlinger::NVFlinger> nvflinger; 112 std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
@@ -154,7 +154,7 @@ private:
154 154
155 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 155 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
156 rb.Push(RESULT_SUCCESS); 156 rb.Push(RESULT_SUCCESS);
157 rb.PushIpcInterface<ISelfController>(nvflinger); 157 rb.PushIpcInterface<ISelfController>(system, nvflinger);
158 } 158 }
159 159
160 void GetWindowController(Kernel::HLERequestContext& ctx) { 160 void GetWindowController(Kernel::HLERequestContext& ctx) {
@@ -162,7 +162,7 @@ private:
162 162
163 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 163 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
164 rb.Push(RESULT_SUCCESS); 164 rb.Push(RESULT_SUCCESS);
165 rb.PushIpcInterface<IWindowController>(); 165 rb.PushIpcInterface<IWindowController>(system);
166 } 166 }
167 167
168 void GetAudioController(Kernel::HLERequestContext& ctx) { 168 void GetAudioController(Kernel::HLERequestContext& ctx) {
@@ -194,7 +194,7 @@ private:
194 194
195 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 195 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
196 rb.Push(RESULT_SUCCESS); 196 rb.Push(RESULT_SUCCESS);
197 rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); 197 rb.PushIpcInterface<ILibraryAppletCreator>(system);
198 } 198 }
199 199
200 void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) { 200 void GetHomeMenuFunctions(Kernel::HLERequestContext& ctx) {
diff --git a/src/core/hle/service/am/applet_oe.cpp b/src/core/hle/service/am/applet_oe.cpp
index 5d53ef113..a2ffaa440 100644
--- a/src/core/hle/service/am/applet_oe.cpp
+++ b/src/core/hle/service/am/applet_oe.cpp
@@ -4,7 +4,6 @@
4 4
5#include "common/logging/log.h" 5#include "common/logging/log.h"
6#include "core/hle/ipc_helpers.h" 6#include "core/hle/ipc_helpers.h"
7#include "core/hle/kernel/process.h"
8#include "core/hle/service/am/am.h" 7#include "core/hle/service/am/am.h"
9#include "core/hle/service/am/applet_oe.h" 8#include "core/hle/service/am/applet_oe.h"
10#include "core/hle/service/nvflinger/nvflinger.h" 9#include "core/hle/service/nvflinger/nvflinger.h"
@@ -64,7 +63,7 @@ private:
64 63
65 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 64 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
66 rb.Push(RESULT_SUCCESS); 65 rb.Push(RESULT_SUCCESS);
67 rb.PushIpcInterface<IWindowController>(); 66 rb.PushIpcInterface<IWindowController>(system);
68 } 67 }
69 68
70 void GetSelfController(Kernel::HLERequestContext& ctx) { 69 void GetSelfController(Kernel::HLERequestContext& ctx) {
@@ -72,7 +71,7 @@ private:
72 71
73 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 72 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
74 rb.Push(RESULT_SUCCESS); 73 rb.Push(RESULT_SUCCESS);
75 rb.PushIpcInterface<ISelfController>(nvflinger); 74 rb.PushIpcInterface<ISelfController>(system, nvflinger);
76 } 75 }
77 76
78 void GetCommonStateGetter(Kernel::HLERequestContext& ctx) { 77 void GetCommonStateGetter(Kernel::HLERequestContext& ctx) {
@@ -88,7 +87,7 @@ private:
88 87
89 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 88 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
90 rb.Push(RESULT_SUCCESS); 89 rb.Push(RESULT_SUCCESS);
91 rb.PushIpcInterface<ILibraryAppletCreator>(system.CurrentProcess()->GetTitleID()); 90 rb.PushIpcInterface<ILibraryAppletCreator>(system);
92 } 91 }
93 92
94 void GetApplicationFunctions(Kernel::HLERequestContext& ctx) { 93 void GetApplicationFunctions(Kernel::HLERequestContext& ctx) {
@@ -96,7 +95,7 @@ private:
96 95
97 IPC::ResponseBuilder rb{ctx, 2, 0, 1}; 96 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
98 rb.Push(RESULT_SUCCESS); 97 rb.Push(RESULT_SUCCESS);
99 rb.PushIpcInterface<IApplicationFunctions>(); 98 rb.PushIpcInterface<IApplicationFunctions>(system);
100 } 99 }
101 100
102 std::shared_ptr<NVFlinger::NVFlinger> nvflinger; 101 std::shared_ptr<NVFlinger::NVFlinger> nvflinger;
diff --git a/src/core/hle/service/am/applets/applets.cpp b/src/core/hle/service/am/applets/applets.cpp
index 6bdba2468..d2e35362f 100644
--- a/src/core/hle/service/am/applets/applets.cpp
+++ b/src/core/hle/service/am/applets/applets.cpp
@@ -23,8 +23,7 @@
23 23
24namespace Service::AM::Applets { 24namespace Service::AM::Applets {
25 25
26AppletDataBroker::AppletDataBroker() { 26AppletDataBroker::AppletDataBroker(Kernel::KernelCore& kernel) {
27 auto& kernel = Core::System::GetInstance().Kernel();
28 state_changed_event = Kernel::WritableEvent::CreateEventPair( 27 state_changed_event = Kernel::WritableEvent::CreateEventPair(
29 kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent"); 28 kernel, Kernel::ResetType::Manual, "ILibraryAppletAccessor:StateChangedEvent");
30 pop_out_data_event = Kernel::WritableEvent::CreateEventPair( 29 pop_out_data_event = Kernel::WritableEvent::CreateEventPair(
@@ -121,7 +120,7 @@ Kernel::SharedPtr<Kernel::ReadableEvent> AppletDataBroker::GetStateChangedEvent(
121 return state_changed_event.readable; 120 return state_changed_event.readable;
122} 121}
123 122
124Applet::Applet() = default; 123Applet::Applet(Kernel::KernelCore& kernel_) : broker{kernel_} {}
125 124
126Applet::~Applet() = default; 125Applet::~Applet() = default;
127 126
@@ -154,7 +153,7 @@ AppletFrontendSet::AppletFrontendSet(AppletFrontendSet&&) noexcept = default;
154 153
155AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default; 154AppletFrontendSet& AppletFrontendSet::operator=(AppletFrontendSet&&) noexcept = default;
156 155
157AppletManager::AppletManager() = default; 156AppletManager::AppletManager(Core::System& system_) : system{system_} {}
158 157
159AppletManager::~AppletManager() = default; 158AppletManager::~AppletManager() = default;
160 159
@@ -216,28 +215,28 @@ void AppletManager::ClearAll() {
216 frontend = {}; 215 frontend = {};
217} 216}
218 217
219std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id, u64 current_process_title_id) const { 218std::shared_ptr<Applet> AppletManager::GetApplet(AppletId id) const {
220 switch (id) { 219 switch (id) {
221 case AppletId::Auth: 220 case AppletId::Auth:
222 return std::make_shared<Auth>(*frontend.parental_controls); 221 return std::make_shared<Auth>(system, *frontend.parental_controls);
223 case AppletId::Error: 222 case AppletId::Error:
224 return std::make_shared<Error>(*frontend.error); 223 return std::make_shared<Error>(system, *frontend.error);
225 case AppletId::ProfileSelect: 224 case AppletId::ProfileSelect:
226 return std::make_shared<ProfileSelect>(*frontend.profile_select); 225 return std::make_shared<ProfileSelect>(system, *frontend.profile_select);
227 case AppletId::SoftwareKeyboard: 226 case AppletId::SoftwareKeyboard:
228 return std::make_shared<SoftwareKeyboard>(*frontend.software_keyboard); 227 return std::make_shared<SoftwareKeyboard>(system, *frontend.software_keyboard);
229 case AppletId::PhotoViewer: 228 case AppletId::PhotoViewer:
230 return std::make_shared<PhotoViewer>(*frontend.photo_viewer); 229 return std::make_shared<PhotoViewer>(system, *frontend.photo_viewer);
231 case AppletId::LibAppletShop: 230 case AppletId::LibAppletShop:
232 return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id, 231 return std::make_shared<WebBrowser>(system, *frontend.web_browser,
233 frontend.e_commerce.get()); 232 frontend.e_commerce.get());
234 case AppletId::LibAppletOff: 233 case AppletId::LibAppletOff:
235 return std::make_shared<WebBrowser>(*frontend.web_browser, current_process_title_id); 234 return std::make_shared<WebBrowser>(system, *frontend.web_browser);
236 default: 235 default:
237 UNIMPLEMENTED_MSG( 236 UNIMPLEMENTED_MSG(
238 "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.", 237 "No backend implementation exists for applet_id={:02X}! Falling back to stub applet.",
239 static_cast<u8>(id)); 238 static_cast<u8>(id));
240 return std::make_shared<StubApplet>(id); 239 return std::make_shared<StubApplet>(system, id);
241 } 240 }
242} 241}
243 242
diff --git a/src/core/hle/service/am/applets/applets.h b/src/core/hle/service/am/applets/applets.h
index adc973dad..764c3418c 100644
--- a/src/core/hle/service/am/applets/applets.h
+++ b/src/core/hle/service/am/applets/applets.h
@@ -12,6 +12,10 @@
12 12
13union ResultCode; 13union ResultCode;
14 14
15namespace Core {
16class System;
17}
18
15namespace Core::Frontend { 19namespace Core::Frontend {
16class ECommerceApplet; 20class ECommerceApplet;
17class ErrorApplet; 21class ErrorApplet;
@@ -22,6 +26,10 @@ class SoftwareKeyboardApplet;
22class WebBrowserApplet; 26class WebBrowserApplet;
23} // namespace Core::Frontend 27} // namespace Core::Frontend
24 28
29namespace Kernel {
30class KernelCore;
31}
32
25namespace Service::AM { 33namespace Service::AM {
26 34
27class IStorage; 35class IStorage;
@@ -53,7 +61,7 @@ enum class AppletId : u32 {
53 61
54class AppletDataBroker final { 62class AppletDataBroker final {
55public: 63public:
56 AppletDataBroker(); 64 explicit AppletDataBroker(Kernel::KernelCore& kernel_);
57 ~AppletDataBroker(); 65 ~AppletDataBroker();
58 66
59 struct RawChannelData { 67 struct RawChannelData {
@@ -108,7 +116,7 @@ private:
108 116
109class Applet { 117class Applet {
110public: 118public:
111 Applet(); 119 explicit Applet(Kernel::KernelCore& kernel_);
112 virtual ~Applet(); 120 virtual ~Applet();
113 121
114 virtual void Initialize(); 122 virtual void Initialize();
@@ -179,7 +187,7 @@ struct AppletFrontendSet {
179 187
180class AppletManager { 188class AppletManager {
181public: 189public:
182 AppletManager(); 190 explicit AppletManager(Core::System& system_);
183 ~AppletManager(); 191 ~AppletManager();
184 192
185 void SetAppletFrontendSet(AppletFrontendSet set); 193 void SetAppletFrontendSet(AppletFrontendSet set);
@@ -187,10 +195,11 @@ public:
187 void SetDefaultAppletsIfMissing(); 195 void SetDefaultAppletsIfMissing();
188 void ClearAll(); 196 void ClearAll();
189 197
190 std::shared_ptr<Applet> GetApplet(AppletId id, u64 current_process_title_id) const; 198 std::shared_ptr<Applet> GetApplet(AppletId id) const;
191 199
192private: 200private:
193 AppletFrontendSet frontend; 201 AppletFrontendSet frontend;
202 Core::System& system;
194}; 203};
195 204
196} // namespace Applets 205} // namespace Applets
diff --git a/src/core/hle/service/am/applets/error.cpp b/src/core/hle/service/am/applets/error.cpp
index af3a900f8..a7db26725 100644
--- a/src/core/hle/service/am/applets/error.cpp
+++ b/src/core/hle/service/am/applets/error.cpp
@@ -85,7 +85,8 @@ ResultCode Decode64BitError(u64 error) {
85 85
86} // Anonymous namespace 86} // Anonymous namespace
87 87
88Error::Error(const Core::Frontend::ErrorApplet& frontend) : frontend(frontend) {} 88Error::Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_)
89 : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
89 90
90Error::~Error() = default; 91Error::~Error() = default;
91 92
@@ -145,8 +146,8 @@ void Error::Execute() {
145 } 146 }
146 147
147 const auto callback = [this] { DisplayCompleted(); }; 148 const auto callback = [this] { DisplayCompleted(); };
148 const auto title_id = Core::CurrentProcess()->GetTitleID(); 149 const auto title_id = system.CurrentProcess()->GetTitleID();
149 const auto& reporter{Core::System::GetInstance().GetReporter()}; 150 const auto& reporter{system.GetReporter()};
150 151
151 switch (mode) { 152 switch (mode) {
152 case ErrorAppletMode::ShowError: 153 case ErrorAppletMode::ShowError:
diff --git a/src/core/hle/service/am/applets/error.h b/src/core/hle/service/am/applets/error.h
index a3590d181..a105cdb0c 100644
--- a/src/core/hle/service/am/applets/error.h
+++ b/src/core/hle/service/am/applets/error.h
@@ -7,6 +7,10 @@
7#include "core/hle/result.h" 7#include "core/hle/result.h"
8#include "core/hle/service/am/applets/applets.h" 8#include "core/hle/service/am/applets/applets.h"
9 9
10namespace Core {
11class System;
12}
13
10namespace Service::AM::Applets { 14namespace Service::AM::Applets {
11 15
12enum class ErrorAppletMode : u8 { 16enum class ErrorAppletMode : u8 {
@@ -21,7 +25,7 @@ enum class ErrorAppletMode : u8 {
21 25
22class Error final : public Applet { 26class Error final : public Applet {
23public: 27public:
24 explicit Error(const Core::Frontend::ErrorApplet& frontend); 28 explicit Error(Core::System& system_, const Core::Frontend::ErrorApplet& frontend_);
25 ~Error() override; 29 ~Error() override;
26 30
27 void Initialize() override; 31 void Initialize() override;
@@ -42,6 +46,7 @@ private:
42 std::unique_ptr<ErrorArguments> args; 46 std::unique_ptr<ErrorArguments> args;
43 47
44 bool complete = false; 48 bool complete = false;
49 Core::System& system;
45}; 50};
46 51
47} // namespace Service::AM::Applets 52} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/general_backend.cpp b/src/core/hle/service/am/applets/general_backend.cpp
index e0def8dff..328438a1d 100644
--- a/src/core/hle/service/am/applets/general_backend.cpp
+++ b/src/core/hle/service/am/applets/general_backend.cpp
@@ -37,7 +37,8 @@ static void LogCurrentStorage(AppletDataBroker& broker, std::string_view prefix)
37 } 37 }
38} 38}
39 39
40Auth::Auth(Core::Frontend::ParentalControlsApplet& frontend) : frontend(frontend) {} 40Auth::Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_)
41 : Applet{system_.Kernel()}, frontend(frontend_) {}
41 42
42Auth::~Auth() = default; 43Auth::~Auth() = default;
43 44
@@ -151,7 +152,8 @@ void Auth::AuthFinished(bool successful) {
151 broker.SignalStateChanged(); 152 broker.SignalStateChanged();
152} 153}
153 154
154PhotoViewer::PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend) : frontend(frontend) {} 155PhotoViewer::PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_)
156 : Applet{system_.Kernel()}, frontend(frontend_), system{system_} {}
155 157
156PhotoViewer::~PhotoViewer() = default; 158PhotoViewer::~PhotoViewer() = default;
157 159
@@ -185,7 +187,7 @@ void PhotoViewer::Execute() {
185 const auto callback = [this] { ViewFinished(); }; 187 const auto callback = [this] { ViewFinished(); };
186 switch (mode) { 188 switch (mode) {
187 case PhotoViewerAppletMode::CurrentApp: 189 case PhotoViewerAppletMode::CurrentApp:
188 frontend.ShowPhotosForApplication(Core::CurrentProcess()->GetTitleID(), callback); 190 frontend.ShowPhotosForApplication(system.CurrentProcess()->GetTitleID(), callback);
189 break; 191 break;
190 case PhotoViewerAppletMode::AllApps: 192 case PhotoViewerAppletMode::AllApps:
191 frontend.ShowAllPhotos(callback); 193 frontend.ShowAllPhotos(callback);
@@ -200,7 +202,8 @@ void PhotoViewer::ViewFinished() {
200 broker.SignalStateChanged(); 202 broker.SignalStateChanged();
201} 203}
202 204
203StubApplet::StubApplet(AppletId id) : id(id) {} 205StubApplet::StubApplet(Core::System& system_, AppletId id_)
206 : Applet{system_.Kernel()}, id(id_), system{system_} {}
204 207
205StubApplet::~StubApplet() = default; 208StubApplet::~StubApplet() = default;
206 209
@@ -209,7 +212,7 @@ void StubApplet::Initialize() {
209 Applet::Initialize(); 212 Applet::Initialize();
210 213
211 const auto data = broker.PeekDataToAppletForDebug(); 214 const auto data = broker.PeekDataToAppletForDebug();
212 Core::System::GetInstance().GetReporter().SaveUnimplementedAppletReport( 215 system.GetReporter().SaveUnimplementedAppletReport(
213 static_cast<u32>(id), common_args.arguments_version, common_args.library_version, 216 static_cast<u32>(id), common_args.arguments_version, common_args.library_version,
214 common_args.theme_color, common_args.play_startup_sound, common_args.system_tick, 217 common_args.theme_color, common_args.play_startup_sound, common_args.system_tick,
215 data.normal, data.interactive); 218 data.normal, data.interactive);
diff --git a/src/core/hle/service/am/applets/general_backend.h b/src/core/hle/service/am/applets/general_backend.h
index 0da252044..cfa2df369 100644
--- a/src/core/hle/service/am/applets/general_backend.h
+++ b/src/core/hle/service/am/applets/general_backend.h
@@ -6,6 +6,10 @@
6 6
7#include "core/hle/service/am/applets/applets.h" 7#include "core/hle/service/am/applets/applets.h"
8 8
9namespace Core {
10class System;
11}
12
9namespace Service::AM::Applets { 13namespace Service::AM::Applets {
10 14
11enum class AuthAppletType : u32 { 15enum class AuthAppletType : u32 {
@@ -16,7 +20,7 @@ enum class AuthAppletType : u32 {
16 20
17class Auth final : public Applet { 21class Auth final : public Applet {
18public: 22public:
19 explicit Auth(Core::Frontend::ParentalControlsApplet& frontend); 23 explicit Auth(Core::System& system_, Core::Frontend::ParentalControlsApplet& frontend_);
20 ~Auth() override; 24 ~Auth() override;
21 25
22 void Initialize() override; 26 void Initialize() override;
@@ -45,7 +49,7 @@ enum class PhotoViewerAppletMode : u8 {
45 49
46class PhotoViewer final : public Applet { 50class PhotoViewer final : public Applet {
47public: 51public:
48 explicit PhotoViewer(const Core::Frontend::PhotoViewerApplet& frontend); 52 explicit PhotoViewer(Core::System& system_, const Core::Frontend::PhotoViewerApplet& frontend_);
49 ~PhotoViewer() override; 53 ~PhotoViewer() override;
50 54
51 void Initialize() override; 55 void Initialize() override;
@@ -60,11 +64,12 @@ private:
60 const Core::Frontend::PhotoViewerApplet& frontend; 64 const Core::Frontend::PhotoViewerApplet& frontend;
61 bool complete = false; 65 bool complete = false;
62 PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp; 66 PhotoViewerAppletMode mode = PhotoViewerAppletMode::CurrentApp;
67 Core::System& system;
63}; 68};
64 69
65class StubApplet final : public Applet { 70class StubApplet final : public Applet {
66public: 71public:
67 explicit StubApplet(AppletId id); 72 explicit StubApplet(Core::System& system_, AppletId id_);
68 ~StubApplet() override; 73 ~StubApplet() override;
69 74
70 void Initialize() override; 75 void Initialize() override;
@@ -76,6 +81,7 @@ public:
76 81
77private: 82private:
78 AppletId id; 83 AppletId id;
84 Core::System& system;
79}; 85};
80 86
81} // namespace Service::AM::Applets 87} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/am/applets/profile_select.cpp b/src/core/hle/service/am/applets/profile_select.cpp
index 57b5419e8..3eba696ca 100644
--- a/src/core/hle/service/am/applets/profile_select.cpp
+++ b/src/core/hle/service/am/applets/profile_select.cpp
@@ -15,8 +15,9 @@ namespace Service::AM::Applets {
15 15
16constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1}; 16constexpr ResultCode ERR_USER_CANCELLED_SELECTION{ErrorModule::Account, 1};
17 17
18ProfileSelect::ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend) 18ProfileSelect::ProfileSelect(Core::System& system_,
19 : frontend(frontend) {} 19 const Core::Frontend::ProfileSelectApplet& frontend_)
20 : Applet{system_.Kernel()}, frontend(frontend_) {}
20 21
21ProfileSelect::~ProfileSelect() = default; 22ProfileSelect::~ProfileSelect() = default;
22 23
diff --git a/src/core/hle/service/am/applets/profile_select.h b/src/core/hle/service/am/applets/profile_select.h
index 563cd744a..16364ead7 100644
--- a/src/core/hle/service/am/applets/profile_select.h
+++ b/src/core/hle/service/am/applets/profile_select.h
@@ -11,6 +11,10 @@
11#include "core/hle/result.h" 11#include "core/hle/result.h"
12#include "core/hle/service/am/applets/applets.h" 12#include "core/hle/service/am/applets/applets.h"
13 13
14namespace Core {
15class System;
16}
17
14namespace Service::AM::Applets { 18namespace Service::AM::Applets {
15 19
16struct UserSelectionConfig { 20struct UserSelectionConfig {
@@ -29,7 +33,8 @@ static_assert(sizeof(UserSelectionOutput) == 0x18, "UserSelectionOutput has inco
29 33
30class ProfileSelect final : public Applet { 34class ProfileSelect final : public Applet {
31public: 35public:
32 explicit ProfileSelect(const Core::Frontend::ProfileSelectApplet& frontend); 36 explicit ProfileSelect(Core::System& system_,
37 const Core::Frontend::ProfileSelectApplet& frontend_);
33 ~ProfileSelect() override; 38 ~ProfileSelect() override;
34 39
35 void Initialize() override; 40 void Initialize() override;
diff --git a/src/core/hle/service/am/applets/software_keyboard.cpp b/src/core/hle/service/am/applets/software_keyboard.cpp
index e197990f7..748559cd0 100644
--- a/src/core/hle/service/am/applets/software_keyboard.cpp
+++ b/src/core/hle/service/am/applets/software_keyboard.cpp
@@ -39,8 +39,9 @@ static Core::Frontend::SoftwareKeyboardParameters ConvertToFrontendParameters(
39 return params; 39 return params;
40} 40}
41 41
42SoftwareKeyboard::SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend) 42SoftwareKeyboard::SoftwareKeyboard(Core::System& system_,
43 : frontend(frontend) {} 43 const Core::Frontend::SoftwareKeyboardApplet& frontend_)
44 : Applet{system_.Kernel()}, frontend(frontend_) {}
44 45
45SoftwareKeyboard::~SoftwareKeyboard() = default; 46SoftwareKeyboard::~SoftwareKeyboard() = default;
46 47
diff --git a/src/core/hle/service/am/applets/software_keyboard.h b/src/core/hle/service/am/applets/software_keyboard.h
index 0fbc43e51..ef4801fc6 100644
--- a/src/core/hle/service/am/applets/software_keyboard.h
+++ b/src/core/hle/service/am/applets/software_keyboard.h
@@ -16,6 +16,10 @@
16 16
17union ResultCode; 17union ResultCode;
18 18
19namespace Core {
20class System;
21}
22
19namespace Service::AM::Applets { 23namespace Service::AM::Applets {
20 24
21enum class KeysetDisable : u32 { 25enum class KeysetDisable : u32 {
@@ -55,7 +59,8 @@ static_assert(sizeof(KeyboardConfig) == 0x3E0, "KeyboardConfig has incorrect siz
55 59
56class SoftwareKeyboard final : public Applet { 60class SoftwareKeyboard final : public Applet {
57public: 61public:
58 explicit SoftwareKeyboard(const Core::Frontend::SoftwareKeyboardApplet& frontend); 62 explicit SoftwareKeyboard(Core::System& system_,
63 const Core::Frontend::SoftwareKeyboardApplet& frontend_);
59 ~SoftwareKeyboard() override; 64 ~SoftwareKeyboard() override;
60 65
61 void Initialize() override; 66 void Initialize() override;
diff --git a/src/core/hle/service/am/applets/web_browser.cpp b/src/core/hle/service/am/applets/web_browser.cpp
index f3c9fef0e..32283e819 100644
--- a/src/core/hle/service/am/applets/web_browser.cpp
+++ b/src/core/hle/service/am/applets/web_browser.cpp
@@ -190,8 +190,9 @@ std::map<WebArgTLVType, std::vector<u8>> GetWebArguments(const std::vector<u8>&
190 return out; 190 return out;
191} 191}
192 192
193FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordType type) { 193FileSys::VirtualFile GetApplicationRomFS(const Core::System& system, u64 title_id,
194 const auto& installed{Core::System::GetInstance().GetContentProvider()}; 194 FileSys::ContentRecordType type) {
195 const auto& installed{system.GetContentProvider()};
195 const auto res = installed.GetEntry(title_id, type); 196 const auto res = installed.GetEntry(title_id, type);
196 197
197 if (res != nullptr) { 198 if (res != nullptr) {
@@ -207,10 +208,10 @@ FileSys::VirtualFile GetApplicationRomFS(u64 title_id, FileSys::ContentRecordTyp
207 208
208} // Anonymous namespace 209} // Anonymous namespace
209 210
210WebBrowser::WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, 211WebBrowser::WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
211 Core::Frontend::ECommerceApplet* frontend_e_commerce) 212 Core::Frontend::ECommerceApplet* frontend_e_commerce_)
212 : frontend(frontend), frontend_e_commerce(frontend_e_commerce), 213 : Applet{system_.Kernel()}, frontend(frontend_),
213 current_process_title_id(current_process_title_id) {} 214 frontend_e_commerce(frontend_e_commerce_), system{system_} {}
214 215
215WebBrowser::~WebBrowser() = default; 216WebBrowser::~WebBrowser() = default;
216 217
@@ -266,7 +267,7 @@ void WebBrowser::UnpackRomFS() {
266 ASSERT(offline_romfs != nullptr); 267 ASSERT(offline_romfs != nullptr);
267 const auto dir = 268 const auto dir =
268 FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard); 269 FileSys::ExtractRomFS(offline_romfs, FileSys::RomFSExtractionType::SingleDiscard);
269 const auto& vfs{Core::System::GetInstance().GetFilesystem()}; 270 const auto& vfs{system.GetFilesystem()};
270 const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite); 271 const auto temp_dir = vfs->CreateDirectory(temporary_dir, FileSys::Mode::ReadWrite);
271 FileSys::VfsRawCopyD(dir, temp_dir); 272 FileSys::VfsRawCopyD(dir, temp_dir);
272 273
@@ -470,10 +471,10 @@ void WebBrowser::InitializeOffline() {
470 } 471 }
471 472
472 if (title_id == 0) { 473 if (title_id == 0) {
473 title_id = current_process_title_id; 474 title_id = system.CurrentProcess()->GetTitleID();
474 } 475 }
475 476
476 offline_romfs = GetApplicationRomFS(title_id, type); 477 offline_romfs = GetApplicationRomFS(system, title_id, type);
477 if (offline_romfs == nullptr) { 478 if (offline_romfs == nullptr) {
478 status = ResultCode(-1); 479 status = ResultCode(-1);
479 LOG_ERROR(Service_AM, "Failed to find offline data for request!"); 480 LOG_ERROR(Service_AM, "Failed to find offline data for request!");
diff --git a/src/core/hle/service/am/applets/web_browser.h b/src/core/hle/service/am/applets/web_browser.h
index 870f57b64..8d4027411 100644
--- a/src/core/hle/service/am/applets/web_browser.h
+++ b/src/core/hle/service/am/applets/web_browser.h
@@ -9,6 +9,10 @@
9#include "core/hle/service/am/am.h" 9#include "core/hle/service/am/am.h"
10#include "core/hle/service/am/applets/applets.h" 10#include "core/hle/service/am/applets/applets.h"
11 11
12namespace Core {
13class System;
14}
15
12namespace Service::AM::Applets { 16namespace Service::AM::Applets {
13 17
14enum class ShimKind : u32; 18enum class ShimKind : u32;
@@ -17,8 +21,8 @@ enum class WebArgTLVType : u16;
17 21
18class WebBrowser final : public Applet { 22class WebBrowser final : public Applet {
19public: 23public:
20 WebBrowser(Core::Frontend::WebBrowserApplet& frontend, u64 current_process_title_id, 24 WebBrowser(Core::System& system_, Core::Frontend::WebBrowserApplet& frontend_,
21 Core::Frontend::ECommerceApplet* frontend_e_commerce = nullptr); 25 Core::Frontend::ECommerceApplet* frontend_e_commerce_ = nullptr);
22 26
23 ~WebBrowser() override; 27 ~WebBrowser() override;
24 28
@@ -59,8 +63,6 @@ private:
59 bool unpacked = false; 63 bool unpacked = false;
60 ResultCode status = RESULT_SUCCESS; 64 ResultCode status = RESULT_SUCCESS;
61 65
62 u64 current_process_title_id;
63
64 ShimKind kind; 66 ShimKind kind;
65 std::map<WebArgTLVType, std::vector<u8>> args; 67 std::map<WebArgTLVType, std::vector<u8>> args;
66 68
@@ -74,6 +76,8 @@ private:
74 std::optional<u128> user_id; 76 std::optional<u128> user_id;
75 std::optional<bool> shop_full_display; 77 std::optional<bool> shop_full_display;
76 std::string shop_extra_parameter; 78 std::string shop_extra_parameter;
79
80 Core::System& system;
77}; 81};
78 82
79} // namespace Service::AM::Applets 83} // namespace Service::AM::Applets
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp
index 5b0b7f17e..f162249ed 100644
--- a/src/core/hle/service/audio/audren_u.cpp
+++ b/src/core/hle/service/audio/audren_u.cpp
@@ -165,15 +165,15 @@ public:
165 static const FunctionInfo functions[] = { 165 static const FunctionInfo functions[] = {
166 {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"}, 166 {0, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceName"},
167 {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"}, 167 {1, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolume"},
168 {2, nullptr, "GetAudioDeviceOutputVolume"}, 168 {2, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolume"},
169 {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"}, 169 {3, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceName"},
170 {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"}, 170 {4, &IAudioDevice::QueryAudioDeviceSystemEvent, "QueryAudioDeviceSystemEvent"},
171 {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"}, 171 {5, &IAudioDevice::GetActiveChannelCount, "GetActiveChannelCount"},
172 {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"}, 172 {6, &IAudioDevice::ListAudioDeviceName, "ListAudioDeviceNameAuto"},
173 {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"}, 173 {7, &IAudioDevice::SetAudioDeviceOutputVolume, "SetAudioDeviceOutputVolumeAuto"},
174 {8, nullptr, "GetAudioDeviceOutputVolumeAuto"}, 174 {8, &IAudioDevice::GetAudioDeviceOutputVolume, "GetAudioDeviceOutputVolumeAuto"},
175 {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"}, 175 {10, &IAudioDevice::GetActiveAudioDeviceName, "GetActiveAudioDeviceNameAuto"},
176 {11, nullptr, "QueryAudioDeviceInputEvent"}, 176 {11, &IAudioDevice::QueryAudioDeviceInputEvent, "QueryAudioDeviceInputEvent"},
177 {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"}, 177 {12, &IAudioDevice::QueryAudioDeviceOutputEvent, "QueryAudioDeviceOutputEvent"},
178 {13, nullptr, "GetAudioSystemMasterVolumeSetting"}, 178 {13, nullptr, "GetAudioSystemMasterVolumeSetting"},
179 }; 179 };
@@ -183,6 +183,10 @@ public:
183 buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic, 183 buffer_event = Kernel::WritableEvent::CreateEventPair(kernel, Kernel::ResetType::Automatic,
184 "IAudioOutBufferReleasedEvent"); 184 "IAudioOutBufferReleasedEvent");
185 185
186 // Should be similar to audio_output_device_switch_event
187 audio_input_device_switch_event = Kernel::WritableEvent::CreateEventPair(
188 kernel, Kernel::ResetType::Automatic, "IAudioDevice:AudioInputDeviceSwitchedEvent");
189
186 // Should only be signalled when an audio output device has been changed, example: speaker 190 // Should only be signalled when an audio output device has been changed, example: speaker
187 // to headset 191 // to headset
188 audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair( 192 audio_output_device_switch_event = Kernel::WritableEvent::CreateEventPair(
@@ -246,6 +250,19 @@ private:
246 rb.Push(RESULT_SUCCESS); 250 rb.Push(RESULT_SUCCESS);
247 } 251 }
248 252
253 void GetAudioDeviceOutputVolume(Kernel::HLERequestContext& ctx) {
254 IPC::RequestParser rp{ctx};
255
256 const auto device_name_buffer = ctx.ReadBuffer();
257 const std::string name = Common::StringFromBuffer(device_name_buffer);
258
259 LOG_WARNING(Service_Audio, "(STUBBED) called. name={}", name);
260
261 IPC::ResponseBuilder rb{ctx, 3};
262 rb.Push(RESULT_SUCCESS);
263 rb.Push(1.0f);
264 }
265
249 void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) { 266 void GetActiveAudioDeviceName(Kernel::HLERequestContext& ctx) {
250 LOG_WARNING(Service_Audio, "(STUBBED) called"); 267 LOG_WARNING(Service_Audio, "(STUBBED) called");
251 268
@@ -279,6 +296,15 @@ private:
279 rb.Push<u32>(1); 296 rb.Push<u32>(1);
280 } 297 }
281 298
299 // Should be similar to QueryAudioDeviceOutputEvent
300 void QueryAudioDeviceInputEvent(Kernel::HLERequestContext& ctx) {
301 LOG_WARNING(Service_Audio, "(STUBBED) called");
302
303 IPC::ResponseBuilder rb{ctx, 2, 1};
304 rb.Push(RESULT_SUCCESS);
305 rb.PushCopyObjects(audio_input_device_switch_event.readable);
306 }
307
282 void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) { 308 void QueryAudioDeviceOutputEvent(Kernel::HLERequestContext& ctx) {
283 LOG_DEBUG(Service_Audio, "called"); 309 LOG_DEBUG(Service_Audio, "called");
284 310
@@ -289,6 +315,7 @@ private:
289 315
290 u32_le revision = 0; 316 u32_le revision = 0;
291 Kernel::EventPair buffer_event; 317 Kernel::EventPair buffer_event;
318 Kernel::EventPair audio_input_device_switch_event;
292 Kernel::EventPair audio_output_device_switch_event; 319 Kernel::EventPair audio_output_device_switch_event;
293 320
294}; // namespace Audio 321}; // namespace Audio
diff --git a/src/core/hle/service/es/es.cpp b/src/core/hle/service/es/es.cpp
index 6701cb913..af70d174d 100644
--- a/src/core/hle/service/es/es.cpp
+++ b/src/core/hle/service/es/es.cpp
@@ -2,32 +2,37 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include "core/crypto/key_manager.h"
6#include "core/hle/ipc_helpers.h"
5#include "core/hle/service/service.h" 7#include "core/hle/service/service.h"
6 8
7namespace Service::ES { 9namespace Service::ES {
8 10
11constexpr ResultCode ERROR_INVALID_ARGUMENT{ErrorModule::ETicket, 2};
12constexpr ResultCode ERROR_INVALID_RIGHTS_ID{ErrorModule::ETicket, 3};
13
9class ETicket final : public ServiceFramework<ETicket> { 14class ETicket final : public ServiceFramework<ETicket> {
10public: 15public:
11 explicit ETicket() : ServiceFramework{"es"} { 16 explicit ETicket() : ServiceFramework{"es"} {
12 // clang-format off 17 // clang-format off
13 static const FunctionInfo functions[] = { 18 static const FunctionInfo functions[] = {
14 {1, nullptr, "ImportTicket"}, 19 {1, &ETicket::ImportTicket, "ImportTicket"},
15 {2, nullptr, "ImportTicketCertificateSet"}, 20 {2, nullptr, "ImportTicketCertificateSet"},
16 {3, nullptr, "DeleteTicket"}, 21 {3, nullptr, "DeleteTicket"},
17 {4, nullptr, "DeletePersonalizedTicket"}, 22 {4, nullptr, "DeletePersonalizedTicket"},
18 {5, nullptr, "DeleteAllCommonTicket"}, 23 {5, nullptr, "DeleteAllCommonTicket"},
19 {6, nullptr, "DeleteAllPersonalizedTicket"}, 24 {6, nullptr, "DeleteAllPersonalizedTicket"},
20 {7, nullptr, "DeleteAllPersonalizedTicketEx"}, 25 {7, nullptr, "DeleteAllPersonalizedTicketEx"},
21 {8, nullptr, "GetTitleKey"}, 26 {8, &ETicket::GetTitleKey, "GetTitleKey"},
22 {9, nullptr, "CountCommonTicket"}, 27 {9, &ETicket::CountCommonTicket, "CountCommonTicket"},
23 {10, nullptr, "CountPersonalizedTicket"}, 28 {10, &ETicket::CountPersonalizedTicket, "CountPersonalizedTicket"},
24 {11, nullptr, "ListCommonTicket"}, 29 {11, &ETicket::ListCommonTicket, "ListCommonTicket"},
25 {12, nullptr, "ListPersonalizedTicket"}, 30 {12, &ETicket::ListPersonalizedTicket, "ListPersonalizedTicket"},
26 {13, nullptr, "ListMissingPersonalizedTicket"}, 31 {13, nullptr, "ListMissingPersonalizedTicket"},
27 {14, nullptr, "GetCommonTicketSize"}, 32 {14, &ETicket::GetCommonTicketSize, "GetCommonTicketSize"},
28 {15, nullptr, "GetPersonalizedTicketSize"}, 33 {15, &ETicket::GetPersonalizedTicketSize, "GetPersonalizedTicketSize"},
29 {16, nullptr, "GetCommonTicketData"}, 34 {16, &ETicket::GetCommonTicketData, "GetCommonTicketData"},
30 {17, nullptr, "GetPersonalizedTicketData"}, 35 {17, &ETicket::GetPersonalizedTicketData, "GetPersonalizedTicketData"},
31 {18, nullptr, "OwnTicket"}, 36 {18, nullptr, "OwnTicket"},
32 {19, nullptr, "GetTicketInfo"}, 37 {19, nullptr, "GetTicketInfo"},
33 {20, nullptr, "ListLightTicketInfo"}, 38 {20, nullptr, "ListLightTicketInfo"},
@@ -51,7 +56,212 @@ public:
51 }; 56 };
52 // clang-format on 57 // clang-format on
53 RegisterHandlers(functions); 58 RegisterHandlers(functions);
59
60 keys.PopulateTickets();
61 keys.SynthesizeTickets();
62 }
63
64private:
65 bool CheckRightsId(Kernel::HLERequestContext& ctx, const u128& rights_id) {
66 if (rights_id == u128{}) {
67 LOG_ERROR(Service_ETicket, "The rights ID was invalid!");
68 IPC::ResponseBuilder rb{ctx, 2};
69 rb.Push(ERROR_INVALID_RIGHTS_ID);
70 return false;
71 }
72
73 return true;
74 }
75
76 void ImportTicket(Kernel::HLERequestContext& ctx) {
77 IPC::RequestParser rp{ctx};
78 const auto ticket = ctx.ReadBuffer();
79 const auto cert = ctx.ReadBuffer(1);
80
81 if (ticket.size() < sizeof(Core::Crypto::Ticket)) {
82 LOG_ERROR(Service_ETicket, "The input buffer is not large enough!");
83 IPC::ResponseBuilder rb{ctx, 2};
84 rb.Push(ERROR_INVALID_ARGUMENT);
85 return;
86 }
87
88 Core::Crypto::Ticket raw{};
89 std::memcpy(&raw, ticket.data(), sizeof(Core::Crypto::Ticket));
90
91 if (!keys.AddTicketPersonalized(raw)) {
92 LOG_ERROR(Service_ETicket, "The ticket could not be imported!");
93 IPC::ResponseBuilder rb{ctx, 2};
94 rb.Push(ERROR_INVALID_ARGUMENT);
95 return;
96 }
97
98 IPC::ResponseBuilder rb{ctx, 2};
99 rb.Push(RESULT_SUCCESS);
100 }
101
102 void GetTitleKey(Kernel::HLERequestContext& ctx) {
103 IPC::RequestParser rp{ctx};
104 const auto rights_id = rp.PopRaw<u128>();
105
106 LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
107
108 if (!CheckRightsId(ctx, rights_id))
109 return;
110
111 const auto key =
112 keys.GetKey(Core::Crypto::S128KeyType::Titlekey, rights_id[1], rights_id[0]);
113
114 if (key == Core::Crypto::Key128{}) {
115 LOG_ERROR(Service_ETicket,
116 "The titlekey doesn't exist in the KeyManager or the rights ID was invalid!");
117 IPC::ResponseBuilder rb{ctx, 2};
118 rb.Push(ERROR_INVALID_RIGHTS_ID);
119 return;
120 }
121
122 ctx.WriteBuffer(key.data(), key.size());
123
124 IPC::ResponseBuilder rb{ctx, 2};
125 rb.Push(RESULT_SUCCESS);
126 }
127
128 void CountCommonTicket(Kernel::HLERequestContext& ctx) {
129 LOG_DEBUG(Service_ETicket, "called");
130
131 const auto count = keys.GetCommonTickets().size();
132
133 IPC::ResponseBuilder rb{ctx, 3};
134 rb.Push(RESULT_SUCCESS);
135 rb.Push<u32>(count);
136 }
137
138 void CountPersonalizedTicket(Kernel::HLERequestContext& ctx) {
139 LOG_DEBUG(Service_ETicket, "called");
140
141 const auto count = keys.GetPersonalizedTickets().size();
142
143 IPC::ResponseBuilder rb{ctx, 3};
144 rb.Push(RESULT_SUCCESS);
145 rb.Push<u32>(count);
146 }
147
148 void ListCommonTicket(Kernel::HLERequestContext& ctx) {
149 u32 out_entries;
150 if (keys.GetCommonTickets().empty())
151 out_entries = 0;
152 else
153 out_entries = ctx.GetWriteBufferSize() / sizeof(u128);
154
155 LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
156
157 keys.PopulateTickets();
158 const auto tickets = keys.GetCommonTickets();
159 std::vector<u128> ids;
160 std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
161 [](const auto& pair) { return pair.first; });
162
163 out_entries = std::min<u32>(ids.size(), out_entries);
164 ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
165
166 IPC::ResponseBuilder rb{ctx, 3};
167 rb.Push(RESULT_SUCCESS);
168 rb.Push<u32>(out_entries);
54 } 169 }
170
171 void ListPersonalizedTicket(Kernel::HLERequestContext& ctx) {
172 u32 out_entries;
173 if (keys.GetPersonalizedTickets().empty())
174 out_entries = 0;
175 else
176 out_entries = ctx.GetWriteBufferSize() / sizeof(u128);
177
178 LOG_DEBUG(Service_ETicket, "called, entries={:016X}", out_entries);
179
180 keys.PopulateTickets();
181 const auto tickets = keys.GetPersonalizedTickets();
182 std::vector<u128> ids;
183 std::transform(tickets.begin(), tickets.end(), std::back_inserter(ids),
184 [](const auto& pair) { return pair.first; });
185
186 out_entries = std::min<u32>(ids.size(), out_entries);
187 ctx.WriteBuffer(ids.data(), out_entries * sizeof(u128));
188
189 IPC::ResponseBuilder rb{ctx, 3};
190 rb.Push(RESULT_SUCCESS);
191 rb.Push<u32>(out_entries);
192 }
193
194 void GetCommonTicketSize(Kernel::HLERequestContext& ctx) {
195 IPC::RequestParser rp{ctx};
196 const auto rights_id = rp.PopRaw<u128>();
197
198 LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
199
200 if (!CheckRightsId(ctx, rights_id))
201 return;
202
203 const auto ticket = keys.GetCommonTickets().at(rights_id);
204
205 IPC::ResponseBuilder rb{ctx, 4};
206 rb.Push(RESULT_SUCCESS);
207 rb.Push<u64>(ticket.GetSize());
208 }
209
210 void GetPersonalizedTicketSize(Kernel::HLERequestContext& ctx) {
211 IPC::RequestParser rp{ctx};
212 const auto rights_id = rp.PopRaw<u128>();
213
214 LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
215
216 if (!CheckRightsId(ctx, rights_id))
217 return;
218
219 const auto ticket = keys.GetPersonalizedTickets().at(rights_id);
220
221 IPC::ResponseBuilder rb{ctx, 4};
222 rb.Push(RESULT_SUCCESS);
223 rb.Push<u64>(ticket.GetSize());
224 }
225
226 void GetCommonTicketData(Kernel::HLERequestContext& ctx) {
227 IPC::RequestParser rp{ctx};
228 const auto rights_id = rp.PopRaw<u128>();
229
230 LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
231
232 if (!CheckRightsId(ctx, rights_id))
233 return;
234
235 const auto ticket = keys.GetCommonTickets().at(rights_id);
236
237 const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
238 ctx.WriteBuffer(&ticket, write_size);
239
240 IPC::ResponseBuilder rb{ctx, 4};
241 rb.Push(RESULT_SUCCESS);
242 rb.Push<u64>(write_size);
243 }
244
245 void GetPersonalizedTicketData(Kernel::HLERequestContext& ctx) {
246 IPC::RequestParser rp{ctx};
247 const auto rights_id = rp.PopRaw<u128>();
248
249 LOG_DEBUG(Service_ETicket, "called, rights_id={:016X}{:016X}", rights_id[1], rights_id[0]);
250
251 if (!CheckRightsId(ctx, rights_id))
252 return;
253
254 const auto ticket = keys.GetPersonalizedTickets().at(rights_id);
255
256 const auto write_size = std::min<u64>(ticket.GetSize(), ctx.GetWriteBufferSize());
257 ctx.WriteBuffer(&ticket, write_size);
258
259 IPC::ResponseBuilder rb{ctx, 4};
260 rb.Push(RESULT_SUCCESS);
261 rb.Push<u64>(write_size);
262 }
263
264 Core::Crypto::KeyManager keys;
55}; 265};
56 266
57void InstallInterfaces(SM::ServiceManager& service_manager) { 267void InstallInterfaces(SM::ServiceManager& service_manager) {
diff --git a/src/core/hle/service/fatal/fatal.cpp b/src/core/hle/service/fatal/fatal.cpp
index fe49c2161..01fa06ad3 100644
--- a/src/core/hle/service/fatal/fatal.cpp
+++ b/src/core/hle/service/fatal/fatal.cpp
@@ -5,7 +5,7 @@
5#include <array> 5#include <array>
6#include <cstring> 6#include <cstring>
7#include <ctime> 7#include <ctime>
8#include <fmt/time.h> 8#include <fmt/chrono.h>
9#include "common/file_util.h" 9#include "common/file_util.h"
10#include "common/logging/log.h" 10#include "common/logging/log.h"
11#include "common/scm_rev.h" 11#include "common/scm_rev.h"
diff --git a/src/core/hle/service/hid/controllers/npad.cpp b/src/core/hle/service/hid/controllers/npad.cpp
index 1e81f776f..e47fe8188 100644
--- a/src/core/hle/service/hid/controllers/npad.cpp
+++ b/src/core/hle/service/hid/controllers/npad.cpp
@@ -636,10 +636,15 @@ Controller_NPad::LedPattern Controller_NPad::GetLedPattern(u32 npad_id) {
636 return LedPattern{0, 0, 0, 0}; 636 return LedPattern{0, 0, 0, 0};
637 }; 637 };
638} 638}
639
639void Controller_NPad::SetVibrationEnabled(bool can_vibrate) { 640void Controller_NPad::SetVibrationEnabled(bool can_vibrate) {
640 can_controllers_vibrate = can_vibrate; 641 can_controllers_vibrate = can_vibrate;
641} 642}
642 643
644bool Controller_NPad::IsVibrationEnabled() const {
645 return can_controllers_vibrate;
646}
647
643void Controller_NPad::ClearAllConnectedControllers() { 648void Controller_NPad::ClearAllConnectedControllers() {
644 for (auto& controller : connected_controllers) { 649 for (auto& controller : connected_controllers) {
645 if (controller.is_connected && controller.type != NPadControllerType::None) { 650 if (controller.is_connected && controller.type != NPadControllerType::None) {
@@ -648,6 +653,7 @@ void Controller_NPad::ClearAllConnectedControllers() {
648 } 653 }
649 } 654 }
650} 655}
656
651void Controller_NPad::DisconnectAllConnectedControllers() { 657void Controller_NPad::DisconnectAllConnectedControllers() {
652 std::for_each(connected_controllers.begin(), connected_controllers.end(), 658 std::for_each(connected_controllers.begin(), connected_controllers.end(),
653 [](ControllerHolder& controller) { controller.is_connected = false; }); 659 [](ControllerHolder& controller) { controller.is_connected = false; });
diff --git a/src/core/hle/service/hid/controllers/npad.h b/src/core/hle/service/hid/controllers/npad.h
index 4b6c1083f..f28b36806 100644
--- a/src/core/hle/service/hid/controllers/npad.h
+++ b/src/core/hle/service/hid/controllers/npad.h
@@ -119,6 +119,7 @@ public:
119 void DisconnectNPad(u32 npad_id); 119 void DisconnectNPad(u32 npad_id);
120 LedPattern GetLedPattern(u32 npad_id); 120 LedPattern GetLedPattern(u32 npad_id);
121 void SetVibrationEnabled(bool can_vibrate); 121 void SetVibrationEnabled(bool can_vibrate);
122 bool IsVibrationEnabled() const;
122 void ClearAllConnectedControllers(); 123 void ClearAllConnectedControllers();
123 void DisconnectAllConnectedControllers(); 124 void DisconnectAllConnectedControllers();
124 void ConnectAllDisconnectedControllers(); 125 void ConnectAllDisconnectedControllers();
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp
index 0bd24b8eb..f8b1ca816 100644
--- a/src/core/hle/service/hid/hid.cpp
+++ b/src/core/hle/service/hid/hid.cpp
@@ -216,8 +216,8 @@ Hid::Hid() : ServiceFramework("hid") {
216 {201, &Hid::SendVibrationValue, "SendVibrationValue"}, 216 {201, &Hid::SendVibrationValue, "SendVibrationValue"},
217 {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"}, 217 {202, &Hid::GetActualVibrationValue, "GetActualVibrationValue"},
218 {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"}, 218 {203, &Hid::CreateActiveVibrationDeviceList, "CreateActiveVibrationDeviceList"},
219 {204, nullptr, "PermitVibration"}, 219 {204, &Hid::PermitVibration, "PermitVibration"},
220 {205, nullptr, "IsVibrationPermitted"}, 220 {205, &Hid::IsVibrationPermitted, "IsVibrationPermitted"},
221 {206, &Hid::SendVibrationValues, "SendVibrationValues"}, 221 {206, &Hid::SendVibrationValues, "SendVibrationValues"},
222 {207, nullptr, "SendVibrationGcErmCommand"}, 222 {207, nullptr, "SendVibrationGcErmCommand"},
223 {208, nullptr, "GetActualVibrationGcErmCommand"}, 223 {208, nullptr, "GetActualVibrationGcErmCommand"},
@@ -679,6 +679,27 @@ void Hid::CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx) {
679 rb.PushIpcInterface<IActiveVibrationDeviceList>(); 679 rb.PushIpcInterface<IActiveVibrationDeviceList>();
680} 680}
681 681
682void Hid::PermitVibration(Kernel::HLERequestContext& ctx) {
683 IPC::RequestParser rp{ctx};
684 const auto can_vibrate{rp.Pop<bool>()};
685 applet_resource->GetController<Controller_NPad>(HidController::NPad)
686 .SetVibrationEnabled(can_vibrate);
687
688 LOG_DEBUG(Service_HID, "called, can_vibrate={}", can_vibrate);
689
690 IPC::ResponseBuilder rb{ctx, 2};
691 rb.Push(RESULT_SUCCESS);
692}
693
694void Hid::IsVibrationPermitted(Kernel::HLERequestContext& ctx) {
695 LOG_DEBUG(Service_HID, "called");
696
697 IPC::ResponseBuilder rb{ctx, 3};
698 rb.Push(RESULT_SUCCESS);
699 rb.Push(
700 applet_resource->GetController<Controller_NPad>(HidController::NPad).IsVibrationEnabled());
701}
702
682void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) { 703void Hid::ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx) {
683 IPC::RequestParser rp{ctx}; 704 IPC::RequestParser rp{ctx};
684 const auto applet_resource_user_id{rp.Pop<u64>()}; 705 const auto applet_resource_user_id{rp.Pop<u64>()};
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h
index 28260ef1b..2fd6d9fc7 100644
--- a/src/core/hle/service/hid/hid.h
+++ b/src/core/hle/service/hid/hid.h
@@ -114,6 +114,8 @@ private:
114 void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx); 114 void SetNpadHandheldActivationMode(Kernel::HLERequestContext& ctx);
115 void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx); 115 void GetVibrationDeviceInfo(Kernel::HLERequestContext& ctx);
116 void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx); 116 void CreateActiveVibrationDeviceList(Kernel::HLERequestContext& ctx);
117 void PermitVibration(Kernel::HLERequestContext& ctx);
118 void IsVibrationPermitted(Kernel::HLERequestContext& ctx);
117 void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); 119 void ActivateConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
118 void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx); 120 void StartConsoleSixAxisSensor(Kernel::HLERequestContext& ctx);
119 void StopSixAxisSensor(Kernel::HLERequestContext& ctx); 121 void StopSixAxisSensor(Kernel::HLERequestContext& ctx);
diff --git a/src/core/loader/nro.cpp b/src/core/loader/nro.cpp
index e92e2e06e..3a5361fdd 100644
--- a/src/core/loader/nro.cpp
+++ b/src/core/loader/nro.cpp
@@ -258,6 +258,15 @@ ResultStatus AppLoader_NRO::ReadTitle(std::string& title) {
258 return ResultStatus::Success; 258 return ResultStatus::Success;
259} 259}
260 260
261ResultStatus AppLoader_NRO::ReadControlData(FileSys::NACP& control) {
262 if (nacp == nullptr) {
263 return ResultStatus::ErrorNoControl;
264 }
265
266 control = *nacp;
267 return ResultStatus::Success;
268}
269
261bool AppLoader_NRO::IsRomFSUpdatable() const { 270bool AppLoader_NRO::IsRomFSUpdatable() const {
262 return false; 271 return false;
263} 272}
diff --git a/src/core/loader/nro.h b/src/core/loader/nro.h
index 1ffdae805..71811bc29 100644
--- a/src/core/loader/nro.h
+++ b/src/core/loader/nro.h
@@ -43,6 +43,7 @@ public:
43 ResultStatus ReadProgramId(u64& out_program_id) override; 43 ResultStatus ReadProgramId(u64& out_program_id) override;
44 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; 44 ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override;
45 ResultStatus ReadTitle(std::string& title) override; 45 ResultStatus ReadTitle(std::string& title) override;
46 ResultStatus ReadControlData(FileSys::NACP& control) override;
46 bool IsRomFSUpdatable() const override; 47 bool IsRomFSUpdatable() const override;
47 48
48private: 49private:
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp
index 5d4c3e6ea..cfe0771e2 100644
--- a/src/core/reporter.cpp
+++ b/src/core/reporter.cpp
@@ -5,8 +5,8 @@
5#include <ctime> 5#include <ctime>
6#include <fstream> 6#include <fstream>
7 7
8#include <fmt/chrono.h>
8#include <fmt/format.h> 9#include <fmt/format.h>
9#include <fmt/time.h>
10#include <json.hpp> 10#include <json.hpp>
11 11
12#include "common/file_util.h" 12#include "common/file_util.h"
diff --git a/src/video_core/engines/kepler_compute.cpp b/src/video_core/engines/kepler_compute.cpp
index 08586d33c..63d449135 100644
--- a/src/video_core/engines/kepler_compute.cpp
+++ b/src/video_core/engines/kepler_compute.cpp
@@ -2,6 +2,7 @@
2// Licensed under GPLv2 or any later version 2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included. 3// Refer to the license.txt file included.
4 4
5#include <bitset>
5#include "common/assert.h" 6#include "common/assert.h"
6#include "common/logging/log.h" 7#include "common/logging/log.h"
7#include "core/core.h" 8#include "core/core.h"
@@ -49,6 +50,33 @@ void KeplerCompute::CallMethod(const GPU::MethodCall& method_call) {
49 } 50 }
50} 51}
51 52
53Tegra::Texture::FullTextureInfo KeplerCompute::GetTexture(std::size_t offset) const {
54 const std::bitset<8> cbuf_mask = launch_description.const_buffer_enable_mask.Value();
55 ASSERT(cbuf_mask[regs.tex_cb_index]);
56
57 const auto& texinfo = launch_description.const_buffer_config[regs.tex_cb_index];
58 ASSERT(texinfo.Address() != 0);
59
60 const GPUVAddr address = texinfo.Address() + offset * sizeof(Texture::TextureHandle);
61 ASSERT(address < texinfo.Address() + texinfo.size);
62
63 const Texture::TextureHandle tex_handle{memory_manager.Read<u32>(address)};
64 return GetTextureInfo(tex_handle, offset);
65}
66
67Texture::FullTextureInfo KeplerCompute::GetTextureInfo(const Texture::TextureHandle tex_handle,
68 std::size_t offset) const {
69 return Texture::FullTextureInfo{static_cast<u32>(offset), GetTICEntry(tex_handle.tic_id),
70 GetTSCEntry(tex_handle.tsc_id)};
71}
72
73u32 KeplerCompute::AccessConstBuffer32(u64 const_buffer, u64 offset) const {
74 const auto& buffer = launch_description.const_buffer_config[const_buffer];
75 u32 result;
76 std::memcpy(&result, memory_manager.GetPointer(buffer.Address() + offset), sizeof(u32));
77 return result;
78}
79
52void KeplerCompute::ProcessLaunch() { 80void KeplerCompute::ProcessLaunch() {
53 const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address(); 81 const GPUVAddr launch_desc_loc = regs.launch_desc_loc.Address();
54 memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description, 82 memory_manager.ReadBlockUnsafe(launch_desc_loc, &launch_description,
@@ -60,4 +88,29 @@ void KeplerCompute::ProcessLaunch() {
60 rasterizer.DispatchCompute(code_addr); 88 rasterizer.DispatchCompute(code_addr);
61} 89}
62 90
91Texture::TICEntry KeplerCompute::GetTICEntry(u32 tic_index) const {
92 const GPUVAddr tic_address_gpu{regs.tic.Address() + tic_index * sizeof(Texture::TICEntry)};
93
94 Texture::TICEntry tic_entry;
95 memory_manager.ReadBlockUnsafe(tic_address_gpu, &tic_entry, sizeof(Texture::TICEntry));
96
97 const auto r_type{tic_entry.r_type.Value()};
98 const auto g_type{tic_entry.g_type.Value()};
99 const auto b_type{tic_entry.b_type.Value()};
100 const auto a_type{tic_entry.a_type.Value()};
101
102 // TODO(Subv): Different data types for separate components are not supported
103 DEBUG_ASSERT(r_type == g_type && r_type == b_type && r_type == a_type);
104
105 return tic_entry;
106}
107
108Texture::TSCEntry KeplerCompute::GetTSCEntry(u32 tsc_index) const {
109 const GPUVAddr tsc_address_gpu{regs.tsc.Address() + tsc_index * sizeof(Texture::TSCEntry)};
110
111 Texture::TSCEntry tsc_entry;
112 memory_manager.ReadBlockUnsafe(tsc_address_gpu, &tsc_entry, sizeof(Texture::TSCEntry));
113 return tsc_entry;
114}
115
63} // namespace Tegra::Engines 116} // namespace Tegra::Engines
diff --git a/src/video_core/engines/kepler_compute.h b/src/video_core/engines/kepler_compute.h
index 6a3309a2c..90cf650d2 100644
--- a/src/video_core/engines/kepler_compute.h
+++ b/src/video_core/engines/kepler_compute.h
@@ -12,6 +12,7 @@
12#include "common/common_types.h" 12#include "common/common_types.h"
13#include "video_core/engines/engine_upload.h" 13#include "video_core/engines/engine_upload.h"
14#include "video_core/gpu.h" 14#include "video_core/gpu.h"
15#include "video_core/textures/texture.h"
15 16
16namespace Core { 17namespace Core {
17class System; 18class System;
@@ -111,7 +112,7 @@ public:
111 112
112 INSERT_PADDING_WORDS(0x3FE); 113 INSERT_PADDING_WORDS(0x3FE);
113 114
114 u32 texture_const_buffer_index; 115 u32 tex_cb_index;
115 116
116 INSERT_PADDING_WORDS(0x374); 117 INSERT_PADDING_WORDS(0x374);
117 }; 118 };
@@ -149,7 +150,7 @@ public:
149 union { 150 union {
150 BitField<0, 8, u32> const_buffer_enable_mask; 151 BitField<0, 8, u32> const_buffer_enable_mask;
151 BitField<29, 2, u32> cache_layout; 152 BitField<29, 2, u32> cache_layout;
152 } memory_config; 153 };
153 154
154 INSERT_PADDING_WORDS(0x8); 155 INSERT_PADDING_WORDS(0x8);
155 156
@@ -194,6 +195,14 @@ public:
194 /// Write the value to the register identified by method. 195 /// Write the value to the register identified by method.
195 void CallMethod(const GPU::MethodCall& method_call); 196 void CallMethod(const GPU::MethodCall& method_call);
196 197
198 Tegra::Texture::FullTextureInfo GetTexture(std::size_t offset) const;
199
200 /// Given a Texture Handle, returns the TSC and TIC entries.
201 Texture::FullTextureInfo GetTextureInfo(const Texture::TextureHandle tex_handle,
202 std::size_t offset) const;
203
204 u32 AccessConstBuffer32(u64 const_buffer, u64 offset) const;
205
197private: 206private:
198 Core::System& system; 207 Core::System& system;
199 VideoCore::RasterizerInterface& rasterizer; 208 VideoCore::RasterizerInterface& rasterizer;
@@ -201,6 +210,12 @@ private:
201 Upload::State upload_state; 210 Upload::State upload_state;
202 211
203 void ProcessLaunch(); 212 void ProcessLaunch();
213
214 /// Retrieves information about a specific TIC entry from the TIC buffer.
215 Texture::TICEntry GetTICEntry(u32 tic_index) const;
216
217 /// Retrieves information about a specific TSC entry from the TSC buffer.
218 Texture::TSCEntry GetTSCEntry(u32 tsc_index) const;
204}; 219};
205 220
206#define ASSERT_REG_POSITION(field_name, position) \ 221#define ASSERT_REG_POSITION(field_name, position) \
@@ -218,12 +233,12 @@ ASSERT_REG_POSITION(launch, 0xAF);
218ASSERT_REG_POSITION(tsc, 0x557); 233ASSERT_REG_POSITION(tsc, 0x557);
219ASSERT_REG_POSITION(tic, 0x55D); 234ASSERT_REG_POSITION(tic, 0x55D);
220ASSERT_REG_POSITION(code_loc, 0x582); 235ASSERT_REG_POSITION(code_loc, 0x582);
221ASSERT_REG_POSITION(texture_const_buffer_index, 0x982); 236ASSERT_REG_POSITION(tex_cb_index, 0x982);
222ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8); 237ASSERT_LAUNCH_PARAM_POSITION(program_start, 0x8);
223ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC); 238ASSERT_LAUNCH_PARAM_POSITION(grid_dim_x, 0xC);
224ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11); 239ASSERT_LAUNCH_PARAM_POSITION(shared_alloc, 0x11);
225ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12); 240ASSERT_LAUNCH_PARAM_POSITION(block_dim_x, 0x12);
226ASSERT_LAUNCH_PARAM_POSITION(memory_config, 0x14); 241ASSERT_LAUNCH_PARAM_POSITION(const_buffer_enable_mask, 0x14);
227ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D); 242ASSERT_LAUNCH_PARAM_POSITION(const_buffer_config, 0x1D);
228 243
229#undef ASSERT_REG_POSITION 244#undef ASSERT_REG_POSITION
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp
index f5158d219..c8c92757a 100644
--- a/src/video_core/engines/maxwell_3d.cpp
+++ b/src/video_core/engines/maxwell_3d.cpp
@@ -244,7 +244,7 @@ void Maxwell3D::InitDirtySettings() {
244 dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_clamp)] = polygon_offset_dirty_reg; 244 dirty_pointers[MAXWELL3D_REG_INDEX(polygon_offset_clamp)] = polygon_offset_dirty_reg;
245} 245}
246 246
247void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) { 247void Maxwell3D::CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters) {
248 // Reset the current macro. 248 // Reset the current macro.
249 executing_macro = 0; 249 executing_macro = 0;
250 250
@@ -252,7 +252,7 @@ void Maxwell3D::CallMacroMethod(u32 method, std::vector<u32> parameters) {
252 const u32 entry = ((method - MacroRegistersStart) >> 1) % macro_positions.size(); 252 const u32 entry = ((method - MacroRegistersStart) >> 1) % macro_positions.size();
253 253
254 // Execute the current macro. 254 // Execute the current macro.
255 macro_interpreter.Execute(macro_positions[entry], std::move(parameters)); 255 macro_interpreter.Execute(macro_positions[entry], num_parameters, parameters);
256} 256}
257 257
258void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) { 258void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
@@ -289,7 +289,8 @@ void Maxwell3D::CallMethod(const GPU::MethodCall& method_call) {
289 289
290 // Call the macro when there are no more parameters in the command buffer 290 // Call the macro when there are no more parameters in the command buffer
291 if (method_call.IsLastCall()) { 291 if (method_call.IsLastCall()) {
292 CallMacroMethod(executing_macro, std::move(macro_params)); 292 CallMacroMethod(executing_macro, macro_params.size(), macro_params.data());
293 macro_params.clear();
293 } 294 }
294 return; 295 return;
295 } 296 }
diff --git a/src/video_core/engines/maxwell_3d.h b/src/video_core/engines/maxwell_3d.h
index 0184342a0..f67a5389f 100644
--- a/src/video_core/engines/maxwell_3d.h
+++ b/src/video_core/engines/maxwell_3d.h
@@ -62,6 +62,7 @@ public:
62 static constexpr std::size_t NumVertexAttributes = 32; 62 static constexpr std::size_t NumVertexAttributes = 32;
63 static constexpr std::size_t NumVaryings = 31; 63 static constexpr std::size_t NumVaryings = 31;
64 static constexpr std::size_t NumTextureSamplers = 32; 64 static constexpr std::size_t NumTextureSamplers = 32;
65 static constexpr std::size_t NumImages = 8; // TODO(Rodrigo): Investigate this number
65 static constexpr std::size_t NumClipDistances = 8; 66 static constexpr std::size_t NumClipDistances = 8;
66 static constexpr std::size_t MaxShaderProgram = 6; 67 static constexpr std::size_t MaxShaderProgram = 6;
67 static constexpr std::size_t MaxShaderStage = 5; 68 static constexpr std::size_t MaxShaderStage = 5;
@@ -1307,9 +1308,10 @@ private:
1307 /** 1308 /**
1308 * Call a macro on this engine. 1309 * Call a macro on this engine.
1309 * @param method Method to call 1310 * @param method Method to call
1311 * @param num_parameters Number of arguments
1310 * @param parameters Arguments to the method call 1312 * @param parameters Arguments to the method call
1311 */ 1313 */
1312 void CallMacroMethod(u32 method, std::vector<u32> parameters); 1314 void CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters);
1313 1315
1314 /// Handles writes to the macro uploading register. 1316 /// Handles writes to the macro uploading register.
1315 void ProcessMacroUpload(u32 data); 1317 void ProcessMacroUpload(u32 data);
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
index 9f59a2dc1..4e1cb98db 100644
--- a/src/video_core/macro_interpreter.cpp
+++ b/src/video_core/macro_interpreter.cpp
@@ -14,11 +14,18 @@ namespace Tegra {
14 14
15MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {} 15MacroInterpreter::MacroInterpreter(Engines::Maxwell3D& maxwell3d) : maxwell3d(maxwell3d) {}
16 16
17void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) { 17void MacroInterpreter::Execute(u32 offset, std::size_t num_parameters, const u32* parameters) {
18 MICROPROFILE_SCOPE(MacroInterp); 18 MICROPROFILE_SCOPE(MacroInterp);
19 Reset(); 19 Reset();
20
20 registers[1] = parameters[0]; 21 registers[1] = parameters[0];
21 this->parameters = std::move(parameters); 22
23 if (num_parameters > parameters_capacity) {
24 parameters_capacity = num_parameters;
25 this->parameters = std::make_unique<u32[]>(num_parameters);
26 }
27 std::memcpy(this->parameters.get(), parameters, num_parameters * sizeof(u32));
28 this->num_parameters = num_parameters;
22 29
23 // Execute the code until we hit an exit condition. 30 // Execute the code until we hit an exit condition.
24 bool keep_executing = true; 31 bool keep_executing = true;
@@ -27,7 +34,7 @@ void MacroInterpreter::Execute(u32 offset, std::vector<u32> parameters) {
27 } 34 }
28 35
29 // Assert the the macro used all the input parameters 36 // Assert the the macro used all the input parameters
30 ASSERT(next_parameter_index == this->parameters.size()); 37 ASSERT(next_parameter_index == num_parameters);
31} 38}
32 39
33void MacroInterpreter::Reset() { 40void MacroInterpreter::Reset() {
@@ -35,7 +42,7 @@ void MacroInterpreter::Reset() {
35 pc = 0; 42 pc = 0;
36 delayed_pc = {}; 43 delayed_pc = {};
37 method_address.raw = 0; 44 method_address.raw = 0;
38 parameters.clear(); 45 num_parameters = 0;
39 // The next parameter index starts at 1, because $r1 already has the value of the first 46 // The next parameter index starts at 1, because $r1 already has the value of the first
40 // parameter. 47 // parameter.
41 next_parameter_index = 1; 48 next_parameter_index = 1;
@@ -229,7 +236,8 @@ void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 res
229} 236}
230 237
231u32 MacroInterpreter::FetchParameter() { 238u32 MacroInterpreter::FetchParameter() {
232 return parameters.at(next_parameter_index++); 239 ASSERT(next_parameter_index < num_parameters);
240 return parameters[next_parameter_index++];
233} 241}
234 242
235u32 MacroInterpreter::GetRegister(u32 register_id) const { 243u32 MacroInterpreter::GetRegister(u32 register_id) const {
diff --git a/src/video_core/macro_interpreter.h b/src/video_core/macro_interpreter.h
index cde360288..76b6a895b 100644
--- a/src/video_core/macro_interpreter.h
+++ b/src/video_core/macro_interpreter.h
@@ -25,7 +25,7 @@ public:
25 * @param offset Offset to start execution at. 25 * @param offset Offset to start execution at.
26 * @param parameters The parameters of the macro. 26 * @param parameters The parameters of the macro.
27 */ 27 */
28 void Execute(u32 offset, std::vector<u32> parameters); 28 void Execute(u32 offset, std::size_t num_parameters, const u32* parameters);
29 29
30private: 30private:
31 enum class Operation : u32 { 31 enum class Operation : u32 {
@@ -162,10 +162,12 @@ private:
162 MethodAddress method_address = {}; 162 MethodAddress method_address = {};
163 163
164 /// Input parameters of the current macro. 164 /// Input parameters of the current macro.
165 std::vector<u32> parameters; 165 std::unique_ptr<u32[]> parameters;
166 std::size_t num_parameters = 0;
167 std::size_t parameters_capacity = 0;
166 /// Index of the next parameter that will be fetched by the 'parm' instruction. 168 /// Index of the next parameter that will be fetched by the 'parm' instruction.
167 u32 next_parameter_index = 0; 169 u32 next_parameter_index = 0;
168 170
169 bool carry_flag{}; 171 bool carry_flag = false;
170}; 172};
171} // namespace Tegra 173} // namespace Tegra
diff --git a/src/video_core/renderer_opengl/gl_device.cpp b/src/video_core/renderer_opengl/gl_device.cpp
index 03d434b28..4f59a87b4 100644
--- a/src/video_core/renderer_opengl/gl_device.cpp
+++ b/src/video_core/renderer_opengl/gl_device.cpp
@@ -14,12 +14,22 @@
14namespace OpenGL { 14namespace OpenGL {
15 15
16namespace { 16namespace {
17
17template <typename T> 18template <typename T>
18T GetInteger(GLenum pname) { 19T GetInteger(GLenum pname) {
19 GLint temporary; 20 GLint temporary;
20 glGetIntegerv(pname, &temporary); 21 glGetIntegerv(pname, &temporary);
21 return static_cast<T>(temporary); 22 return static_cast<T>(temporary);
22} 23}
24
25bool TestProgram(const GLchar* glsl) {
26 const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &glsl)};
27 GLint link_status;
28 glGetProgramiv(shader, GL_LINK_STATUS, &link_status);
29 glDeleteProgram(shader);
30 return link_status == GL_TRUE;
31}
32
23} // Anonymous namespace 33} // Anonymous namespace
24 34
25Device::Device() { 35Device::Device() {
@@ -32,6 +42,11 @@ Device::Device() {
32 has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array; 42 has_vertex_viewport_layer = GLAD_GL_ARB_shader_viewport_layer_array;
33 has_variable_aoffi = TestVariableAoffi(); 43 has_variable_aoffi = TestVariableAoffi();
34 has_component_indexing_bug = TestComponentIndexingBug(); 44 has_component_indexing_bug = TestComponentIndexingBug();
45 has_precise_bug = TestPreciseBug();
46
47 LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", has_variable_aoffi);
48 LOG_INFO(Render_OpenGL, "Renderer_ComponentIndexingBug: {}", has_component_indexing_bug);
49 LOG_INFO(Render_OpenGL, "Renderer_PreciseBug: {}", has_precise_bug);
35} 50}
36 51
37Device::Device(std::nullptr_t) { 52Device::Device(std::nullptr_t) {
@@ -42,30 +57,21 @@ Device::Device(std::nullptr_t) {
42 has_vertex_viewport_layer = true; 57 has_vertex_viewport_layer = true;
43 has_variable_aoffi = true; 58 has_variable_aoffi = true;
44 has_component_indexing_bug = false; 59 has_component_indexing_bug = false;
60 has_precise_bug = false;
45} 61}
46 62
47bool Device::TestVariableAoffi() { 63bool Device::TestVariableAoffi() {
48 const GLchar* AOFFI_TEST = R"(#version 430 core 64 return TestProgram(R"(#version 430 core
49// This is a unit test, please ignore me on apitrace bug reports. 65// This is a unit test, please ignore me on apitrace bug reports.
50uniform sampler2D tex; 66uniform sampler2D tex;
51uniform ivec2 variable_offset; 67uniform ivec2 variable_offset;
52out vec4 output_attribute; 68out vec4 output_attribute;
53void main() { 69void main() {
54 output_attribute = textureOffset(tex, vec2(0), variable_offset); 70 output_attribute = textureOffset(tex, vec2(0), variable_offset);
55} 71})");
56)";
57 const GLuint shader{glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &AOFFI_TEST)};
58 GLint link_status{};
59 glGetProgramiv(shader, GL_LINK_STATUS, &link_status);
60 glDeleteProgram(shader);
61
62 const bool supported{link_status == GL_TRUE};
63 LOG_INFO(Render_OpenGL, "Renderer_VariableAOFFI: {}", supported);
64 return supported;
65} 72}
66 73
67bool Device::TestComponentIndexingBug() { 74bool Device::TestComponentIndexingBug() {
68 constexpr char log_message[] = "Renderer_ComponentIndexingBug: {}";
69 const GLchar* COMPONENT_TEST = R"(#version 430 core 75 const GLchar* COMPONENT_TEST = R"(#version 430 core
70layout (std430, binding = 0) buffer OutputBuffer { 76layout (std430, binding = 0) buffer OutputBuffer {
71 uint output_value; 77 uint output_value;
@@ -105,12 +111,21 @@ void main() {
105 GLuint result; 111 GLuint result;
106 glGetNamedBufferSubData(ssbo.handle, 0, sizeof(result), &result); 112 glGetNamedBufferSubData(ssbo.handle, 0, sizeof(result), &result);
107 if (result != values.at(index)) { 113 if (result != values.at(index)) {
108 LOG_INFO(Render_OpenGL, log_message, true);
109 return true; 114 return true;
110 } 115 }
111 } 116 }
112 LOG_INFO(Render_OpenGL, log_message, false);
113 return false; 117 return false;
114} 118}
115 119
120bool Device::TestPreciseBug() {
121 return !TestProgram(R"(#version 430 core
122in vec3 coords;
123out float out_value;
124uniform sampler2DShadow tex;
125void main() {
126 precise float tmp_value = vec4(texture(tex, coords)).x;
127 out_value = tmp_value;
128})");
129}
130
116} // namespace OpenGL 131} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_device.h b/src/video_core/renderer_opengl/gl_device.h
index 3ef7c6dd8..ba6dcd3be 100644
--- a/src/video_core/renderer_opengl/gl_device.h
+++ b/src/video_core/renderer_opengl/gl_device.h
@@ -46,9 +46,14 @@ public:
46 return has_component_indexing_bug; 46 return has_component_indexing_bug;
47 } 47 }
48 48
49 bool HasPreciseBug() const {
50 return has_precise_bug;
51 }
52
49private: 53private:
50 static bool TestVariableAoffi(); 54 static bool TestVariableAoffi();
51 static bool TestComponentIndexingBug(); 55 static bool TestComponentIndexingBug();
56 static bool TestPreciseBug();
52 57
53 std::size_t uniform_buffer_alignment{}; 58 std::size_t uniform_buffer_alignment{};
54 std::size_t shader_storage_alignment{}; 59 std::size_t shader_storage_alignment{};
@@ -58,6 +63,7 @@ private:
58 bool has_vertex_viewport_layer{}; 63 bool has_vertex_viewport_layer{};
59 bool has_variable_aoffi{}; 64 bool has_variable_aoffi{};
60 bool has_component_indexing_bug{}; 65 bool has_component_indexing_bug{};
66 bool has_precise_bug{};
61}; 67};
62 68
63} // namespace OpenGL 69} // namespace OpenGL
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index bb09ecd52..4e266cdad 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -331,7 +331,7 @@ void RasterizerOpenGL::SetupShaders(GLenum primitive_mode) {
331 const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage); 331 const auto stage_enum = static_cast<Maxwell::ShaderStage>(stage);
332 SetupDrawConstBuffers(stage_enum, shader); 332 SetupDrawConstBuffers(stage_enum, shader);
333 SetupDrawGlobalMemory(stage_enum, shader); 333 SetupDrawGlobalMemory(stage_enum, shader);
334 const auto texture_buffer_usage{SetupTextures(stage_enum, shader, base_bindings)}; 334 const auto texture_buffer_usage{SetupDrawTextures(stage_enum, shader, base_bindings)};
335 335
336 const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage}; 336 const ProgramVariant variant{base_bindings, primitive_mode, texture_buffer_usage};
337 const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant); 337 const auto [program_handle, next_bindings] = shader->GetProgramHandle(variant);
@@ -537,8 +537,7 @@ std::pair<bool, bool> RasterizerOpenGL::ConfigureFramebuffers(
537 texture_cache.MarkDepthBufferInUse(); 537 texture_cache.MarkDepthBufferInUse();
538 538
539 fbkey.zeta = depth_surface; 539 fbkey.zeta = depth_surface;
540 fbkey.stencil_enable = regs.stencil_enable && 540 fbkey.stencil_enable = depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil;
541 depth_surface->GetSurfaceParams().type == SurfaceType::DepthStencil;
542 } 541 }
543 542
544 texture_cache.GuardRenderTargets(false); 543 texture_cache.GuardRenderTargets(false);
@@ -577,16 +576,15 @@ void RasterizerOpenGL::ConfigureClearFramebuffer(OpenGLState& current_state, boo
577 if (depth_surface) { 576 if (depth_surface) {
578 const auto& params = depth_surface->GetSurfaceParams(); 577 const auto& params = depth_surface->GetSurfaceParams();
579 switch (params.type) { 578 switch (params.type) {
580 case VideoCore::Surface::SurfaceType::Depth: { 579 case VideoCore::Surface::SurfaceType::Depth:
581 depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER); 580 depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER);
582 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0); 581 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 0);
583 break; 582 break;
584 } 583 case VideoCore::Surface::SurfaceType::DepthStencil:
585 case VideoCore::Surface::SurfaceType::DepthStencil: { 584 depth_surface->Attach(GL_DEPTH_STENCIL_ATTACHMENT, GL_DRAW_FRAMEBUFFER);
586 depth_surface->Attach(GL_DEPTH_ATTACHMENT, GL_DRAW_FRAMEBUFFER);
587 break; 585 break;
588 } 586 default:
589 default: { UNIMPLEMENTED(); } 587 UNIMPLEMENTED();
590 } 588 }
591 } else { 589 } else {
592 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0, 590 glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_TEXTURE_2D, 0,
@@ -639,6 +637,7 @@ void RasterizerOpenGL::Clear() {
639 ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!"); 637 ASSERT_MSG(regs.zeta_enable != 0, "Tried to clear stencil but buffer is not enabled!");
640 use_stencil = true; 638 use_stencil = true;
641 clear_state.stencil.test_enabled = true; 639 clear_state.stencil.test_enabled = true;
640
642 if (regs.clear_flags.stencil) { 641 if (regs.clear_flags.stencil) {
643 // Stencil affects the clear so fill it with the used masks 642 // Stencil affects the clear so fill it with the used masks
644 clear_state.stencil.front.test_func = GL_ALWAYS; 643 clear_state.stencil.front.test_func = GL_ALWAYS;
@@ -802,7 +801,11 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
802 } 801 }
803 802
804 auto kernel = shader_cache.GetComputeKernel(code_addr); 803 auto kernel = shader_cache.GetComputeKernel(code_addr);
805 const auto [program, next_bindings] = kernel->GetProgramHandle({}); 804 ProgramVariant variant;
805 variant.texture_buffer_usage = SetupComputeTextures(kernel);
806 SetupComputeImages(kernel);
807
808 const auto [program, next_bindings] = kernel->GetProgramHandle(variant);
806 state.draw.shader_program = program; 809 state.draw.shader_program = program;
807 state.draw.program_pipeline = 0; 810 state.draw.program_pipeline = 0;
808 811
@@ -817,13 +820,13 @@ void RasterizerOpenGL::DispatchCompute(GPUVAddr code_addr) {
817 SetupComputeConstBuffers(kernel); 820 SetupComputeConstBuffers(kernel);
818 SetupComputeGlobalMemory(kernel); 821 SetupComputeGlobalMemory(kernel);
819 822
820 // TODO(Rodrigo): Bind images and samplers
821
822 buffer_cache.Unmap(); 823 buffer_cache.Unmap();
823 824
824 bind_ubo_pushbuffer.Bind(); 825 bind_ubo_pushbuffer.Bind();
825 bind_ssbo_pushbuffer.Bind(); 826 bind_ssbo_pushbuffer.Bind();
826 827
828 state.ApplyTextures();
829 state.ApplyImages();
827 state.ApplyShaderProgram(); 830 state.ApplyShaderProgram();
828 state.ApplyProgramPipeline(); 831 state.ApplyProgramPipeline();
829 832
@@ -923,7 +926,7 @@ void RasterizerOpenGL::SetupComputeConstBuffers(const Shader& kernel) {
923 const auto& launch_desc = system.GPU().KeplerCompute().launch_description; 926 const auto& launch_desc = system.GPU().KeplerCompute().launch_description;
924 for (const auto& entry : kernel->GetShaderEntries().const_buffers) { 927 for (const auto& entry : kernel->GetShaderEntries().const_buffers) {
925 const auto& config = launch_desc.const_buffer_config[entry.GetIndex()]; 928 const auto& config = launch_desc.const_buffer_config[entry.GetIndex()];
926 const std::bitset<8> mask = launch_desc.memory_config.const_buffer_enable_mask.Value(); 929 const std::bitset<8> mask = launch_desc.const_buffer_enable_mask.Value();
927 Tegra::Engines::ConstBufferInfo buffer; 930 Tegra::Engines::ConstBufferInfo buffer;
928 buffer.address = config.Address(); 931 buffer.address = config.Address();
929 buffer.size = config.size; 932 buffer.size = config.size;
@@ -982,53 +985,125 @@ void RasterizerOpenGL::SetupGlobalMemory(const GLShader::GlobalMemoryEntry& entr
982 bind_ssbo_pushbuffer.Push(ssbo, buffer_offset, static_cast<GLsizeiptr>(size)); 985 bind_ssbo_pushbuffer.Push(ssbo, buffer_offset, static_cast<GLsizeiptr>(size));
983} 986}
984 987
985TextureBufferUsage RasterizerOpenGL::SetupTextures(Maxwell::ShaderStage stage, const Shader& shader, 988TextureBufferUsage RasterizerOpenGL::SetupDrawTextures(Maxwell::ShaderStage stage,
986 BaseBindings base_bindings) { 989 const Shader& shader,
990 BaseBindings base_bindings) {
987 MICROPROFILE_SCOPE(OpenGL_Texture); 991 MICROPROFILE_SCOPE(OpenGL_Texture);
988 const auto& gpu = system.GPU(); 992 const auto& gpu = system.GPU();
989 const auto& maxwell3d = gpu.Maxwell3D(); 993 const auto& maxwell3d = gpu.Maxwell3D();
990 const auto& entries = shader->GetShaderEntries().samplers; 994 const auto& entries = shader->GetShaderEntries().samplers;
991 995
992 ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.texture_units), 996 ASSERT_MSG(base_bindings.sampler + entries.size() <= std::size(state.textures),
993 "Exceeded the number of active textures."); 997 "Exceeded the number of active textures.");
994 998
995 TextureBufferUsage texture_buffer_usage{0}; 999 TextureBufferUsage texture_buffer_usage{0};
996 1000
997 for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) { 1001 for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
998 const auto& entry = entries[bindpoint]; 1002 const auto& entry = entries[bindpoint];
999 Tegra::Texture::FullTextureInfo texture; 1003 const auto texture = [&]() {
1000 if (entry.IsBindless()) { 1004 if (!entry.IsBindless()) {
1005 return maxwell3d.GetStageTexture(stage, entry.GetOffset());
1006 }
1001 const auto cbuf = entry.GetBindlessCBuf(); 1007 const auto cbuf = entry.GetBindlessCBuf();
1002 Tegra::Texture::TextureHandle tex_handle; 1008 Tegra::Texture::TextureHandle tex_handle;
1003 tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second); 1009 tex_handle.raw = maxwell3d.AccessConstBuffer32(stage, cbuf.first, cbuf.second);
1004 texture = maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset()); 1010 return maxwell3d.GetTextureInfo(tex_handle, entry.GetOffset());
1005 } else { 1011 }();
1006 texture = maxwell3d.GetStageTexture(stage, entry.GetOffset()); 1012
1013 if (SetupTexture(base_bindings.sampler + bindpoint, texture, entry)) {
1014 texture_buffer_usage.set(bindpoint);
1007 } 1015 }
1008 const u32 current_bindpoint = base_bindings.sampler + bindpoint; 1016 }
1009 1017
1010 auto& unit{state.texture_units[current_bindpoint]}; 1018 return texture_buffer_usage;
1011 unit.sampler = sampler_cache.GetSampler(texture.tsc); 1019}
1012 1020
1013 if (const auto view{texture_cache.GetTextureSurface(texture, entry)}; view) { 1021TextureBufferUsage RasterizerOpenGL::SetupComputeTextures(const Shader& kernel) {
1014 if (view->GetSurfaceParams().IsBuffer()) { 1022 MICROPROFILE_SCOPE(OpenGL_Texture);
1015 // Record that this texture is a texture buffer. 1023 const auto& compute = system.GPU().KeplerCompute();
1016 texture_buffer_usage.set(bindpoint); 1024 const auto& entries = kernel->GetShaderEntries().samplers;
1017 } else { 1025
1018 // Apply swizzle to textures that are not buffers. 1026 ASSERT_MSG(entries.size() <= std::size(state.textures),
1019 view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source, 1027 "Exceeded the number of active textures.");
1020 texture.tic.w_source); 1028
1029 TextureBufferUsage texture_buffer_usage{0};
1030
1031 for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
1032 const auto& entry = entries[bindpoint];
1033 const auto texture = [&]() {
1034 if (!entry.IsBindless()) {
1035 return compute.GetTexture(entry.GetOffset());
1021 } 1036 }
1022 state.texture_units[current_bindpoint].texture = view->GetTexture(); 1037 const auto cbuf = entry.GetBindlessCBuf();
1023 } else { 1038 Tegra::Texture::TextureHandle tex_handle;
1024 // Can occur when texture addr is null or its memory is unmapped/invalid 1039 tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second);
1025 unit.texture = 0; 1040 return compute.GetTextureInfo(tex_handle, entry.GetOffset());
1041 }();
1042
1043 if (SetupTexture(bindpoint, texture, entry)) {
1044 texture_buffer_usage.set(bindpoint);
1026 } 1045 }
1027 } 1046 }
1028 1047
1029 return texture_buffer_usage; 1048 return texture_buffer_usage;
1030} 1049}
1031 1050
1051bool RasterizerOpenGL::SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture,
1052 const GLShader::SamplerEntry& entry) {
1053 state.samplers[binding] = sampler_cache.GetSampler(texture.tsc);
1054
1055 const auto view = texture_cache.GetTextureSurface(texture.tic, entry);
1056 if (!view) {
1057 // Can occur when texture addr is null or its memory is unmapped/invalid
1058 state.textures[binding] = 0;
1059 return false;
1060 }
1061 state.textures[binding] = view->GetTexture();
1062
1063 if (view->GetSurfaceParams().IsBuffer()) {
1064 return true;
1065 }
1066
1067 // Apply swizzle to textures that are not buffers.
1068 view->ApplySwizzle(texture.tic.x_source, texture.tic.y_source, texture.tic.z_source,
1069 texture.tic.w_source);
1070 return false;
1071}
1072
1073void RasterizerOpenGL::SetupComputeImages(const Shader& shader) {
1074 const auto& compute = system.GPU().KeplerCompute();
1075 const auto& entries = shader->GetShaderEntries().images;
1076 for (u32 bindpoint = 0; bindpoint < entries.size(); ++bindpoint) {
1077 const auto& entry = entries[bindpoint];
1078 const auto tic = [&]() {
1079 if (!entry.IsBindless()) {
1080 return compute.GetTexture(entry.GetOffset()).tic;
1081 }
1082 const auto cbuf = entry.GetBindlessCBuf();
1083 Tegra::Texture::TextureHandle tex_handle;
1084 tex_handle.raw = compute.AccessConstBuffer32(cbuf.first, cbuf.second);
1085 return compute.GetTextureInfo(tex_handle, entry.GetOffset()).tic;
1086 }();
1087 SetupImage(bindpoint, tic, entry);
1088 }
1089}
1090
1091void RasterizerOpenGL::SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic,
1092 const GLShader::ImageEntry& entry) {
1093 const auto view = texture_cache.GetImageSurface(tic, entry);
1094 if (!view) {
1095 state.images[binding] = 0;
1096 return;
1097 }
1098 if (!tic.IsBuffer()) {
1099 view->ApplySwizzle(tic.x_source, tic.y_source, tic.z_source, tic.w_source);
1100 }
1101 if (entry.IsWritten()) {
1102 view->MarkAsModified(texture_cache.Tick());
1103 }
1104 state.images[binding] = view->GetTexture();
1105}
1106
1032void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) { 1107void RasterizerOpenGL::SyncViewport(OpenGLState& current_state) {
1033 const auto& regs = system.GPU().Maxwell3D().regs; 1108 const auto& regs = system.GPU().Maxwell3D().regs;
1034 const bool geometry_shaders_enabled = 1109 const bool geometry_shaders_enabled =
@@ -1119,9 +1194,12 @@ void RasterizerOpenGL::SyncStencilTestState() {
1119 if (!maxwell3d.dirty.stencil_test) { 1194 if (!maxwell3d.dirty.stencil_test) {
1120 return; 1195 return;
1121 } 1196 }
1122 const auto& regs = maxwell3d.regs; 1197 maxwell3d.dirty.stencil_test = false;
1123 1198
1199 const auto& regs = maxwell3d.regs;
1124 state.stencil.test_enabled = regs.stencil_enable != 0; 1200 state.stencil.test_enabled = regs.stencil_enable != 0;
1201 state.MarkDirtyStencilState();
1202
1125 if (!regs.stencil_enable) { 1203 if (!regs.stencil_enable) {
1126 return; 1204 return;
1127 } 1205 }
@@ -1150,8 +1228,6 @@ void RasterizerOpenGL::SyncStencilTestState() {
1150 state.stencil.back.action_depth_fail = GL_KEEP; 1228 state.stencil.back.action_depth_fail = GL_KEEP;
1151 state.stencil.back.action_depth_pass = GL_KEEP; 1229 state.stencil.back.action_depth_pass = GL_KEEP;
1152 } 1230 }
1153 state.MarkDirtyStencilState();
1154 maxwell3d.dirty.stencil_test = false;
1155} 1231}
1156 1232
1157void RasterizerOpenGL::SyncColorMask() { 1233void RasterizerOpenGL::SyncColorMask() {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index 9d20a4fbf..eada752e0 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -32,6 +32,7 @@
32#include "video_core/renderer_opengl/gl_state.h" 32#include "video_core/renderer_opengl/gl_state.h"
33#include "video_core/renderer_opengl/gl_texture_cache.h" 33#include "video_core/renderer_opengl/gl_texture_cache.h"
34#include "video_core/renderer_opengl/utils.h" 34#include "video_core/renderer_opengl/utils.h"
35#include "video_core/textures/texture.h"
35 36
36namespace Core { 37namespace Core {
37class System; 38class System;
@@ -137,8 +138,22 @@ private:
137 138
138 /// Configures the current textures to use for the draw command. Returns shaders texture buffer 139 /// Configures the current textures to use for the draw command. Returns shaders texture buffer
139 /// usage. 140 /// usage.
140 TextureBufferUsage SetupTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage, 141 TextureBufferUsage SetupDrawTextures(Tegra::Engines::Maxwell3D::Regs::ShaderStage stage,
141 const Shader& shader, BaseBindings base_bindings); 142 const Shader& shader, BaseBindings base_bindings);
143
144 /// Configures the textures used in a compute shader. Returns texture buffer usage.
145 TextureBufferUsage SetupComputeTextures(const Shader& kernel);
146
147 /// Configures a texture. Returns true when the texture is a texture buffer.
148 bool SetupTexture(u32 binding, const Tegra::Texture::FullTextureInfo& texture,
149 const GLShader::SamplerEntry& entry);
150
151 /// Configures images in a compute shader.
152 void SetupComputeImages(const Shader& shader);
153
154 /// Configures an image.
155 void SetupImage(u32 binding, const Tegra::Texture::TICEntry& tic,
156 const GLShader::ImageEntry& entry);
142 157
143 /// Syncs the viewport and depth range to match the guest state 158 /// Syncs the viewport and depth range to match the guest state
144 void SyncViewport(OpenGLState& current_state); 159 void SyncViewport(OpenGLState& current_state);
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp
index cf6a5cddf..909ccb82c 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp
@@ -348,23 +348,16 @@ Shader CachedShader::CreateKernelFromCache(const ShaderParameters& params,
348} 348}
349 349
350std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) { 350std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVariant& variant) {
351 GLuint handle{}; 351 const auto [entry, is_cache_miss] = programs.try_emplace(variant);
352 if (program_type == ProgramType::Geometry) { 352 auto& program = entry->second;
353 handle = GetGeometryShader(variant); 353 if (is_cache_miss) {
354 } else { 354 program = TryLoadProgram(variant);
355 const auto [entry, is_cache_miss] = programs.try_emplace(variant); 355 if (!program) {
356 auto& program = entry->second; 356 program = SpecializeShader(code, entries, program_type, variant);
357 if (is_cache_miss) { 357 disk_cache.SaveUsage(GetUsage(variant));
358 program = TryLoadProgram(variant);
359 if (!program) {
360 program = SpecializeShader(code, entries, program_type, variant);
361 disk_cache.SaveUsage(GetUsage(variant));
362 }
363
364 LabelGLObject(GL_PROGRAM, program->handle, cpu_addr);
365 } 358 }
366 359
367 handle = program->handle; 360 LabelGLObject(GL_PROGRAM, program->handle, cpu_addr);
368 } 361 }
369 362
370 auto base_bindings = variant.base_bindings; 363 auto base_bindings = variant.base_bindings;
@@ -375,52 +368,9 @@ std::tuple<GLuint, BaseBindings> CachedShader::GetProgramHandle(const ProgramVar
375 base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size()); 368 base_bindings.gmem += static_cast<u32>(entries.global_memory_entries.size());
376 base_bindings.sampler += static_cast<u32>(entries.samplers.size()); 369 base_bindings.sampler += static_cast<u32>(entries.samplers.size());
377 370
378 return {handle, base_bindings}; 371 return {program->handle, base_bindings};
379} 372}
380 373
381GLuint CachedShader::GetGeometryShader(const ProgramVariant& variant) {
382 const auto [entry, is_cache_miss] = geometry_programs.try_emplace(variant);
383 auto& programs = entry->second;
384
385 switch (variant.primitive_mode) {
386 case GL_POINTS:
387 return LazyGeometryProgram(programs.points, variant);
388 case GL_LINES:
389 case GL_LINE_STRIP:
390 return LazyGeometryProgram(programs.lines, variant);
391 case GL_LINES_ADJACENCY:
392 case GL_LINE_STRIP_ADJACENCY:
393 return LazyGeometryProgram(programs.lines_adjacency, variant);
394 case GL_TRIANGLES:
395 case GL_TRIANGLE_STRIP:
396 case GL_TRIANGLE_FAN:
397 return LazyGeometryProgram(programs.triangles, variant);
398 case GL_TRIANGLES_ADJACENCY:
399 case GL_TRIANGLE_STRIP_ADJACENCY:
400 return LazyGeometryProgram(programs.triangles_adjacency, variant);
401 default:
402 UNREACHABLE_MSG("Unknown primitive mode.");
403 return LazyGeometryProgram(programs.points, variant);
404 }
405}
406
407GLuint CachedShader::LazyGeometryProgram(CachedProgram& target_program,
408 const ProgramVariant& variant) {
409 if (target_program) {
410 return target_program->handle;
411 }
412 const auto [glsl_name, debug_name, vertices] = GetPrimitiveDescription(variant.primitive_mode);
413 target_program = TryLoadProgram(variant);
414 if (!target_program) {
415 target_program = SpecializeShader(code, entries, program_type, variant);
416 disk_cache.SaveUsage(GetUsage(variant));
417 }
418
419 LabelGLObject(GL_PROGRAM, target_program->handle, cpu_addr, debug_name);
420
421 return target_program->handle;
422};
423
424CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const { 374CachedProgram CachedShader::TryLoadProgram(const ProgramVariant& variant) const {
425 const auto found = precompiled_programs.find(GetUsage(variant)); 375 const auto found = precompiled_programs.find(GetUsage(variant));
426 if (found == precompiled_programs.end()) { 376 if (found == precompiled_programs.end()) {
diff --git a/src/video_core/renderer_opengl/gl_shader_cache.h b/src/video_core/renderer_opengl/gl_shader_cache.h
index 2c8faf855..de195cc5d 100644
--- a/src/video_core/renderer_opengl/gl_shader_cache.h
+++ b/src/video_core/renderer_opengl/gl_shader_cache.h
@@ -86,22 +86,6 @@ private:
86 explicit CachedShader(const ShaderParameters& params, ProgramType program_type, 86 explicit CachedShader(const ShaderParameters& params, ProgramType program_type,
87 GLShader::ProgramResult result); 87 GLShader::ProgramResult result);
88 88
89 // Geometry programs. These are needed because GLSL needs an input topology but it's not
90 // declared by the hardware. Workaround this issue by generating a different shader per input
91 // topology class.
92 struct GeometryPrograms {
93 CachedProgram points;
94 CachedProgram lines;
95 CachedProgram lines_adjacency;
96 CachedProgram triangles;
97 CachedProgram triangles_adjacency;
98 };
99
100 GLuint GetGeometryShader(const ProgramVariant& variant);
101
102 /// Generates a geometry shader or returns one that already exists.
103 GLuint LazyGeometryProgram(CachedProgram& target_program, const ProgramVariant& variant);
104
105 CachedProgram TryLoadProgram(const ProgramVariant& variant) const; 89 CachedProgram TryLoadProgram(const ProgramVariant& variant) const;
106 90
107 ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const; 91 ShaderDiskCacheUsage GetUsage(const ProgramVariant& variant) const;
@@ -117,11 +101,6 @@ private:
117 std::size_t shader_length{}; 101 std::size_t shader_length{};
118 102
119 std::unordered_map<ProgramVariant, CachedProgram> programs; 103 std::unordered_map<ProgramVariant, CachedProgram> programs;
120 std::unordered_map<ProgramVariant, GeometryPrograms> geometry_programs;
121
122 std::unordered_map<u32, GLuint> cbuf_resource_cache;
123 std::unordered_map<u32, GLuint> gmem_resource_cache;
124 std::unordered_map<u32, GLint> uniform_cache;
125}; 104};
126 105
127class ShaderCacheOpenGL final : public RasterizerCache<Shader> { 106class ShaderCacheOpenGL final : public RasterizerCache<Shader> {
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 359d58cbe..6edb2ca38 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -39,7 +39,7 @@ using namespace VideoCommon::Shader;
39using Maxwell = Tegra::Engines::Maxwell3D::Regs; 39using Maxwell = Tegra::Engines::Maxwell3D::Regs;
40using Operation = const OperationNode&; 40using Operation = const OperationNode&;
41 41
42enum class Type { Bool, Bool2, Float, Int, Uint, HalfFloat }; 42enum class Type { Void, Bool, Bool2, Float, Int, Uint, HalfFloat };
43 43
44struct TextureAoffi {}; 44struct TextureAoffi {};
45using TextureArgument = std::pair<Type, Node>; 45using TextureArgument = std::pair<Type, Node>;
@@ -48,7 +48,7 @@ using TextureIR = std::variant<TextureAoffi, TextureArgument>;
48constexpr u32 MAX_CONSTBUFFER_ELEMENTS = 48constexpr u32 MAX_CONSTBUFFER_ELEMENTS =
49 static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float)); 49 static_cast<u32>(Maxwell::MaxConstBufferSize) / (4 * sizeof(float));
50 50
51class ShaderWriter { 51class ShaderWriter final {
52public: 52public:
53 void AddExpression(std::string_view text) { 53 void AddExpression(std::string_view text) {
54 DEBUG_ASSERT(scope >= 0); 54 DEBUG_ASSERT(scope >= 0);
@@ -93,9 +93,157 @@ private:
93 u32 temporary_index = 1; 93 u32 temporary_index = 1;
94}; 94};
95 95
96class Expression final {
97public:
98 Expression(std::string code, Type type) : code{std::move(code)}, type{type} {
99 ASSERT(type != Type::Void);
100 }
101 Expression() : type{Type::Void} {}
102
103 Type GetType() const {
104 return type;
105 }
106
107 std::string GetCode() const {
108 return code;
109 }
110
111 void CheckVoid() const {
112 ASSERT(type == Type::Void);
113 }
114
115 std::string As(Type type) const {
116 switch (type) {
117 case Type::Bool:
118 return AsBool();
119 case Type::Bool2:
120 return AsBool2();
121 case Type::Float:
122 return AsFloat();
123 case Type::Int:
124 return AsInt();
125 case Type::Uint:
126 return AsUint();
127 case Type::HalfFloat:
128 return AsHalfFloat();
129 default:
130 UNREACHABLE_MSG("Invalid type");
131 return code;
132 }
133 }
134
135 std::string AsBool() const {
136 switch (type) {
137 case Type::Bool:
138 return code;
139 default:
140 UNREACHABLE_MSG("Incompatible types");
141 return code;
142 }
143 }
144
145 std::string AsBool2() const {
146 switch (type) {
147 case Type::Bool2:
148 return code;
149 default:
150 UNREACHABLE_MSG("Incompatible types");
151 return code;
152 }
153 }
154
155 std::string AsFloat() const {
156 switch (type) {
157 case Type::Float:
158 return code;
159 case Type::Uint:
160 return fmt::format("utof({})", code);
161 case Type::Int:
162 return fmt::format("itof({})", code);
163 case Type::HalfFloat:
164 return fmt::format("utof(packHalf2x16({}))", code);
165 default:
166 UNREACHABLE_MSG("Incompatible types");
167 return code;
168 }
169 }
170
171 std::string AsInt() const {
172 switch (type) {
173 case Type::Float:
174 return fmt::format("ftoi({})", code);
175 case Type::Uint:
176 return fmt::format("int({})", code);
177 case Type::Int:
178 return code;
179 case Type::HalfFloat:
180 return fmt::format("int(packHalf2x16({}))", code);
181 default:
182 UNREACHABLE_MSG("Incompatible types");
183 return code;
184 }
185 }
186
187 std::string AsUint() const {
188 switch (type) {
189 case Type::Float:
190 return fmt::format("ftou({})", code);
191 case Type::Uint:
192 return code;
193 case Type::Int:
194 return fmt::format("uint({})", code);
195 case Type::HalfFloat:
196 return fmt::format("packHalf2x16({})", code);
197 default:
198 UNREACHABLE_MSG("Incompatible types");
199 return code;
200 }
201 }
202
203 std::string AsHalfFloat() const {
204 switch (type) {
205 case Type::Float:
206 return fmt::format("unpackHalf2x16(ftou({}))", code);
207 case Type::Uint:
208 return fmt::format("unpackHalf2x16({})", code);
209 case Type::Int:
210 return fmt::format("unpackHalf2x16(int({}))", code);
211 case Type::HalfFloat:
212 return code;
213 default:
214 UNREACHABLE_MSG("Incompatible types");
215 return code;
216 }
217 }
218
219private:
220 std::string code;
221 Type type{};
222};
223
224constexpr const char* GetTypeString(Type type) {
225 switch (type) {
226 case Type::Bool:
227 return "bool";
228 case Type::Bool2:
229 return "bvec2";
230 case Type::Float:
231 return "float";
232 case Type::Int:
233 return "int";
234 case Type::Uint:
235 return "uint";
236 case Type::HalfFloat:
237 return "vec2";
238 default:
239 UNREACHABLE_MSG("Invalid type");
240 return "<invalid type>";
241 }
242}
243
96/// Generates code to use for a swizzle operation. 244/// Generates code to use for a swizzle operation.
97constexpr const char* GetSwizzle(u32 element) { 245constexpr const char* GetSwizzle(u32 element) {
98 constexpr std::array<const char*, 4> swizzle = {".x", ".y", ".z", ".w"}; 246 constexpr std::array swizzle = {".x", ".y", ".z", ".w"};
99 return swizzle.at(element); 247 return swizzle.at(element);
100} 248}
101 249
@@ -134,8 +282,8 @@ constexpr bool IsGenericAttribute(Attribute::Index index) {
134 return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31; 282 return index >= Attribute::Index::Attribute_0 && index <= Attribute::Index::Attribute_31;
135} 283}
136 284
137constexpr Attribute::Index ToGenericAttribute(u32 value) { 285constexpr Attribute::Index ToGenericAttribute(u64 value) {
138 return static_cast<Attribute::Index>(value + static_cast<u32>(Attribute::Index::Attribute_0)); 286 return static_cast<Attribute::Index>(value + static_cast<u64>(Attribute::Index::Attribute_0));
139} 287}
140 288
141u32 GetGenericAttributeIndex(Attribute::Index index) { 289u32 GetGenericAttributeIndex(Attribute::Index index) {
@@ -191,7 +339,7 @@ public:
191 339
192 // VM's program counter 340 // VM's program counter
193 const auto first_address = ir.GetBasicBlocks().begin()->first; 341 const auto first_address = ir.GetBasicBlocks().begin()->first;
194 code.AddLine("uint jmp_to = {}u;", first_address); 342 code.AddLine("uint jmp_to = {}U;", first_address);
195 343
196 // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems 344 // TODO(Subv): Figure out the actual depth of the flow stack, for now it seems
197 // unlikely that shaders will use 20 nested SSYs and PBKs. 345 // unlikely that shaders will use 20 nested SSYs and PBKs.
@@ -199,7 +347,7 @@ public:
199 constexpr u32 FLOW_STACK_SIZE = 20; 347 constexpr u32 FLOW_STACK_SIZE = 20;
200 for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) { 348 for (const auto stack : std::array{MetaStackClass::Ssy, MetaStackClass::Pbk}) {
201 code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE); 349 code.AddLine("uint {}[{}];", FlowStackName(stack), FLOW_STACK_SIZE);
202 code.AddLine("uint {} = 0u;", FlowStackTopName(stack)); 350 code.AddLine("uint {} = 0U;", FlowStackTopName(stack));
203 } 351 }
204 } 352 }
205 353
@@ -210,7 +358,7 @@ public:
210 358
211 for (const auto& pair : ir.GetBasicBlocks()) { 359 for (const auto& pair : ir.GetBasicBlocks()) {
212 const auto [address, bb] = pair; 360 const auto [address, bb] = pair;
213 code.AddLine("case 0x{:x}u: {{", address); 361 code.AddLine("case 0x{:X}U: {{", address);
214 ++code.scope; 362 ++code.scope;
215 363
216 VisitBlock(bb); 364 VisitBlock(bb);
@@ -241,11 +389,10 @@ public:
241 for (const auto& sampler : ir.GetSamplers()) { 389 for (const auto& sampler : ir.GetSamplers()) {
242 entries.samplers.emplace_back(sampler); 390 entries.samplers.emplace_back(sampler);
243 } 391 }
244 for (const auto& image : ir.GetImages()) { 392 for (const auto& [offset, image] : ir.GetImages()) {
245 entries.images.emplace_back(image); 393 entries.images.emplace_back(image);
246 } 394 }
247 for (const auto& gmem_pair : ir.GetGlobalMemory()) { 395 for (const auto& [base, usage] : ir.GetGlobalMemory()) {
248 const auto& [base, usage] = gmem_pair;
249 entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset, 396 entries.global_memory_entries.emplace_back(base.cbuf_index, base.cbuf_offset,
250 usage.is_read, usage.is_written); 397 usage.is_read, usage.is_written);
251 } 398 }
@@ -322,7 +469,7 @@ private:
322 void DeclareRegisters() { 469 void DeclareRegisters() {
323 const auto& registers = ir.GetRegisters(); 470 const auto& registers = ir.GetRegisters();
324 for (const u32 gpr : registers) { 471 for (const u32 gpr : registers) {
325 code.AddLine("float {} = 0;", GetRegister(gpr)); 472 code.AddLine("float {} = 0.0f;", GetRegister(gpr));
326 } 473 }
327 if (!registers.empty()) { 474 if (!registers.empty()) {
328 code.AddNewLine(); 475 code.AddNewLine();
@@ -348,7 +495,7 @@ private:
348 return; 495 return;
349 } 496 }
350 const auto element_count = Common::AlignUp(local_memory_size, 4) / 4; 497 const auto element_count = Common::AlignUp(local_memory_size, 4) / 4;
351 code.AddLine("float {}[{}];", GetLocalMemory(), element_count); 498 code.AddLine("uint {}[{}];", GetLocalMemory(), element_count);
352 code.AddNewLine(); 499 code.AddNewLine();
353 } 500 }
354 501
@@ -371,8 +518,6 @@ private:
371 return "noperspective "; 518 return "noperspective ";
372 default: 519 default:
373 case AttributeUse::Unused: 520 case AttributeUse::Unused:
374 UNREACHABLE_MSG("Unused attribute being fetched");
375 return {};
376 UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute)); 521 UNIMPLEMENTED_MSG("Unknown attribute usage index={}", static_cast<u32>(attribute));
377 return {}; 522 return {};
378 } 523 }
@@ -449,7 +594,7 @@ private:
449 const auto [index, size] = entry; 594 const auto [index, size] = entry;
450 code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index, 595 code.AddLine("layout (std140, binding = CBUF_BINDING_{}) uniform {} {{", index,
451 GetConstBufferBlock(index)); 596 GetConstBufferBlock(index));
452 code.AddLine(" vec4 {}[MAX_CONSTBUFFER_ELEMENTS];", GetConstBuffer(index)); 597 code.AddLine(" uvec4 {}[{}];", GetConstBuffer(index), MAX_CONSTBUFFER_ELEMENTS);
453 code.AddLine("}};"); 598 code.AddLine("}};");
454 code.AddNewLine(); 599 code.AddNewLine();
455 } 600 }
@@ -470,7 +615,7 @@ private:
470 615
471 code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{", 616 code.AddLine("layout (std430, binding = GMEM_BINDING_{}_{}) {} buffer {} {{",
472 base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base)); 617 base.cbuf_index, base.cbuf_offset, qualifier, GetGlobalMemoryBlock(base));
473 code.AddLine(" float {}[];", GetGlobalMemory(base)); 618 code.AddLine(" uint {}[];", GetGlobalMemory(base));
474 code.AddLine("}};"); 619 code.AddLine("}};");
475 code.AddNewLine(); 620 code.AddNewLine();
476 } 621 }
@@ -528,7 +673,7 @@ private:
528 if (!ir.HasPhysicalAttributes()) { 673 if (!ir.HasPhysicalAttributes()) {
529 return; 674 return;
530 } 675 }
531 code.AddLine("float readPhysicalAttribute(uint physical_address) {{"); 676 code.AddLine("float ReadPhysicalAttribute(uint physical_address) {{");
532 ++code.scope; 677 ++code.scope;
533 code.AddLine("switch (physical_address) {{"); 678 code.AddLine("switch (physical_address) {{");
534 679
@@ -537,15 +682,16 @@ private:
537 for (u32 index = 0; index < num_attributes; ++index) { 682 for (u32 index = 0; index < num_attributes; ++index) {
538 const auto attribute{ToGenericAttribute(index)}; 683 const auto attribute{ToGenericAttribute(index)};
539 for (u32 element = 0; element < 4; ++element) { 684 for (u32 element = 0; element < 4; ++element) {
540 constexpr u32 generic_base{0x80}; 685 constexpr u32 generic_base = 0x80;
541 constexpr u32 generic_stride{16}; 686 constexpr u32 generic_stride = 16;
542 constexpr u32 element_stride{4}; 687 constexpr u32 element_stride = 4;
543 const u32 address{generic_base + index * generic_stride + element * element_stride}; 688 const u32 address{generic_base + index * generic_stride + element * element_stride};
544 689
545 const bool declared{stage != ProgramType::Fragment || 690 const bool declared = stage != ProgramType::Fragment ||
546 header.ps.GetAttributeUse(index) != AttributeUse::Unused}; 691 header.ps.GetAttributeUse(index) != AttributeUse::Unused;
547 const std::string value{declared ? ReadAttribute(attribute, element) : "0"}; 692 const std::string value =
548 code.AddLine("case 0x{:x}: return {};", address, value); 693 declared ? ReadAttribute(attribute, element).AsFloat() : "0.0f";
694 code.AddLine("case 0x{:X}U: return {};", address, value);
549 } 695 }
550 } 696 }
551 697
@@ -559,7 +705,7 @@ private:
559 705
560 void DeclareImages() { 706 void DeclareImages() {
561 const auto& images{ir.GetImages()}; 707 const auto& images{ir.GetImages()};
562 for (const auto& image : images) { 708 for (const auto& [offset, image] : images) {
563 const std::string image_type = [&]() { 709 const std::string image_type = [&]() {
564 switch (image.GetType()) { 710 switch (image.GetType()) {
565 case Tegra::Shader::ImageType::Texture1D: 711 case Tegra::Shader::ImageType::Texture1D:
@@ -579,9 +725,16 @@ private:
579 return "image1D"; 725 return "image1D";
580 } 726 }
581 }(); 727 }();
582 code.AddLine("layout (binding = IMAGE_BINDING_{}) coherent volatile writeonly uniform " 728 std::string qualifier = "coherent volatile";
729 if (image.IsRead() && !image.IsWritten()) {
730 qualifier += " readonly";
731 } else if (image.IsWritten() && !image.IsRead()) {
732 qualifier += " writeonly";
733 }
734
735 code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform "
583 "{} {};", 736 "{} {};",
584 image.GetIndex(), image_type, GetImage(image)); 737 image.GetIndex(), qualifier, image_type, GetImage(image));
585 } 738 }
586 if (!images.empty()) { 739 if (!images.empty()) {
587 code.AddNewLine(); 740 code.AddNewLine();
@@ -590,13 +743,11 @@ private:
590 743
591 void VisitBlock(const NodeBlock& bb) { 744 void VisitBlock(const NodeBlock& bb) {
592 for (const auto& node : bb) { 745 for (const auto& node : bb) {
593 if (const std::string expr = Visit(node); !expr.empty()) { 746 Visit(node).CheckVoid();
594 code.AddLine(expr);
595 }
596 } 747 }
597 } 748 }
598 749
599 std::string Visit(const Node& node) { 750 Expression Visit(const Node& node) {
600 if (const auto operation = std::get_if<OperationNode>(&*node)) { 751 if (const auto operation = std::get_if<OperationNode>(&*node)) {
601 const auto operation_index = static_cast<std::size_t>(operation->GetCode()); 752 const auto operation_index = static_cast<std::size_t>(operation->GetCode());
602 if (operation_index >= operation_decompilers.size()) { 753 if (operation_index >= operation_decompilers.size()) {
@@ -614,18 +765,18 @@ private:
614 if (const auto gpr = std::get_if<GprNode>(&*node)) { 765 if (const auto gpr = std::get_if<GprNode>(&*node)) {
615 const u32 index = gpr->GetIndex(); 766 const u32 index = gpr->GetIndex();
616 if (index == Register::ZeroIndex) { 767 if (index == Register::ZeroIndex) {
617 return "0"; 768 return {"0U", Type::Uint};
618 } 769 }
619 return GetRegister(index); 770 return {GetRegister(index), Type::Float};
620 } 771 }
621 772
622 if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { 773 if (const auto immediate = std::get_if<ImmediateNode>(&*node)) {
623 const u32 value = immediate->GetValue(); 774 const u32 value = immediate->GetValue();
624 if (value < 10) { 775 if (value < 10) {
625 // For eyecandy avoid using hex numbers on single digits 776 // For eyecandy avoid using hex numbers on single digits
626 return fmt::format("utof({}u)", immediate->GetValue()); 777 return {fmt::format("{}U", immediate->GetValue()), Type::Uint};
627 } 778 }
628 return fmt::format("utof(0x{:x}u)", immediate->GetValue()); 779 return {fmt::format("0x{:X}U", immediate->GetValue()), Type::Uint};
629 } 780 }
630 781
631 if (const auto predicate = std::get_if<PredicateNode>(&*node)) { 782 if (const auto predicate = std::get_if<PredicateNode>(&*node)) {
@@ -640,17 +791,18 @@ private:
640 } 791 }
641 }(); 792 }();
642 if (predicate->IsNegated()) { 793 if (predicate->IsNegated()) {
643 return fmt::format("!({})", value); 794 return {fmt::format("!({})", value), Type::Bool};
644 } 795 }
645 return value; 796 return {value, Type::Bool};
646 } 797 }
647 798
648 if (const auto abuf = std::get_if<AbufNode>(&*node)) { 799 if (const auto abuf = std::get_if<AbufNode>(&*node)) {
649 UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry, 800 UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry,
650 "Physical attributes in geometry shaders are not implemented"); 801 "Physical attributes in geometry shaders are not implemented");
651 if (abuf->IsPhysicalBuffer()) { 802 if (abuf->IsPhysicalBuffer()) {
652 return fmt::format("readPhysicalAttribute(ftou({}))", 803 return {fmt::format("ReadPhysicalAttribute({})",
653 Visit(abuf->GetPhysicalAddress())); 804 Visit(abuf->GetPhysicalAddress()).AsUint()),
805 Type::Float};
654 } 806 }
655 return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer()); 807 return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer());
656 } 808 }
@@ -661,59 +813,64 @@ private:
661 // Direct access 813 // Direct access
662 const u32 offset_imm = immediate->GetValue(); 814 const u32 offset_imm = immediate->GetValue();
663 ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); 815 ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access");
664 return fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), 816 return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()),
665 offset_imm / (4 * 4), (offset_imm / 4) % 4); 817 offset_imm / (4 * 4), (offset_imm / 4) % 4),
818 Type::Uint};
666 } 819 }
667 820
668 if (std::holds_alternative<OperationNode>(*offset)) { 821 if (std::holds_alternative<OperationNode>(*offset)) {
669 // Indirect access 822 // Indirect access
670 const std::string final_offset = code.GenerateTemporary(); 823 const std::string final_offset = code.GenerateTemporary();
671 code.AddLine("uint {} = ftou({}) >> 2;", final_offset, Visit(offset)); 824 code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint());
672 825
673 if (!device.HasComponentIndexingBug()) { 826 if (!device.HasComponentIndexingBug()) {
674 return fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), 827 return {fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()),
675 final_offset, final_offset); 828 final_offset, final_offset),
829 Type::Uint};
676 } 830 }
677 831
678 // AMD's proprietary GLSL compiler emits ill code for variable component access. 832 // AMD's proprietary GLSL compiler emits ill code for variable component access.
679 // To bypass this driver bug generate 4 ifs, one per each component. 833 // To bypass this driver bug generate 4 ifs, one per each component.
680 const std::string pack = code.GenerateTemporary(); 834 const std::string pack = code.GenerateTemporary();
681 code.AddLine("vec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), 835 code.AddLine("uvec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()),
682 final_offset); 836 final_offset);
683 837
684 const std::string result = code.GenerateTemporary(); 838 const std::string result = code.GenerateTemporary();
685 code.AddLine("float {};", result); 839 code.AddLine("uint {};", result);
686 for (u32 swizzle = 0; swizzle < 4; ++swizzle) { 840 for (u32 swizzle = 0; swizzle < 4; ++swizzle) {
687 code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result, 841 code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result,
688 pack, GetSwizzle(swizzle)); 842 pack, GetSwizzle(swizzle));
689 } 843 }
690 return result; 844 return {result, Type::Uint};
691 } 845 }
692 846
693 UNREACHABLE_MSG("Unmanaged offset node type"); 847 UNREACHABLE_MSG("Unmanaged offset node type");
694 } 848 }
695 849
696 if (const auto gmem = std::get_if<GmemNode>(&*node)) { 850 if (const auto gmem = std::get_if<GmemNode>(&*node)) {
697 const std::string real = Visit(gmem->GetRealAddress()); 851 const std::string real = Visit(gmem->GetRealAddress()).AsUint();
698 const std::string base = Visit(gmem->GetBaseAddress()); 852 const std::string base = Visit(gmem->GetBaseAddress()).AsUint();
699 const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); 853 const std::string final_offset = fmt::format("({} - {}) >> 2", real, base);
700 return fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); 854 return {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset),
855 Type::Uint};
701 } 856 }
702 857
703 if (const auto lmem = std::get_if<LmemNode>(&*node)) { 858 if (const auto lmem = std::get_if<LmemNode>(&*node)) {
704 if (stage == ProgramType::Compute) { 859 if (stage == ProgramType::Compute) {
705 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); 860 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders");
706 } 861 }
707 return fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); 862 return {
863 fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()),
864 Type::Uint};
708 } 865 }
709 866
710 if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { 867 if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) {
711 return GetInternalFlag(internal_flag->GetFlag()); 868 return {GetInternalFlag(internal_flag->GetFlag()), Type::Bool};
712 } 869 }
713 870
714 if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { 871 if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
715 // It's invalid to call conditional on nested nodes, use an operation instead 872 // It's invalid to call conditional on nested nodes, use an operation instead
716 code.AddLine("if ({}) {{", Visit(conditional->GetCondition())); 873 code.AddLine("if ({}) {{", Visit(conditional->GetCondition()).AsBool());
717 ++code.scope; 874 ++code.scope;
718 875
719 VisitBlock(conditional->GetCode()); 876 VisitBlock(conditional->GetCode());
@@ -724,20 +881,21 @@ private:
724 } 881 }
725 882
726 if (const auto comment = std::get_if<CommentNode>(&*node)) { 883 if (const auto comment = std::get_if<CommentNode>(&*node)) {
727 return "// " + comment->GetText(); 884 code.AddLine("// " + comment->GetText());
885 return {};
728 } 886 }
729 887
730 UNREACHABLE(); 888 UNREACHABLE();
731 return {}; 889 return {};
732 } 890 }
733 891
734 std::string ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { 892 Expression ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) {
735 const auto GeometryPass = [&](std::string_view name) { 893 const auto GeometryPass = [&](std::string_view name) {
736 if (stage == ProgramType::Geometry && buffer) { 894 if (stage == ProgramType::Geometry && buffer) {
737 // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games 895 // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games
738 // set an 0x80000000 index for those and the shader fails to build. Find out why 896 // set an 0x80000000 index for those and the shader fails to build. Find out why
739 // this happens and what's its intent. 897 // this happens and what's its intent.
740 return fmt::format("gs_{}[ftou({}) % MAX_VERTEX_INPUT]", name, Visit(buffer)); 898 return fmt::format("gs_{}[{} % MAX_VERTEX_INPUT]", name, Visit(buffer).AsUint());
741 } 899 }
742 return std::string(name); 900 return std::string(name);
743 }; 901 };
@@ -746,25 +904,27 @@ private:
746 case Attribute::Index::Position: 904 case Attribute::Index::Position:
747 switch (stage) { 905 switch (stage) {
748 case ProgramType::Geometry: 906 case ProgramType::Geometry:
749 return fmt::format("gl_in[ftou({})].gl_Position{}", Visit(buffer), 907 return {fmt::format("gl_in[{}].gl_Position{}", Visit(buffer).AsUint(),
750 GetSwizzle(element)); 908 GetSwizzle(element)),
909 Type::Float};
751 case ProgramType::Fragment: 910 case ProgramType::Fragment:
752 return element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)); 911 return {element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)),
912 Type::Float};
753 default: 913 default:
754 UNREACHABLE(); 914 UNREACHABLE();
755 } 915 }
756 case Attribute::Index::PointCoord: 916 case Attribute::Index::PointCoord:
757 switch (element) { 917 switch (element) {
758 case 0: 918 case 0:
759 return "gl_PointCoord.x"; 919 return {"gl_PointCoord.x", Type::Float};
760 case 1: 920 case 1:
761 return "gl_PointCoord.y"; 921 return {"gl_PointCoord.y", Type::Float};
762 case 2: 922 case 2:
763 case 3: 923 case 3:
764 return "0"; 924 return {"0.0f", Type::Float};
765 } 925 }
766 UNREACHABLE(); 926 UNREACHABLE();
767 return "0"; 927 return {"0", Type::Int};
768 case Attribute::Index::TessCoordInstanceIDVertexID: 928 case Attribute::Index::TessCoordInstanceIDVertexID:
769 // TODO(Subv): Find out what the values are for the first two elements when inside a 929 // TODO(Subv): Find out what the values are for the first two elements when inside a
770 // vertex shader, and what's the value of the fourth element when inside a Tess Eval 930 // vertex shader, and what's the value of the fourth element when inside a Tess Eval
@@ -773,44 +933,49 @@ private:
773 switch (element) { 933 switch (element) {
774 case 2: 934 case 2:
775 // Config pack's first value is instance_id. 935 // Config pack's first value is instance_id.
776 return "uintBitsToFloat(config_pack[0])"; 936 return {"config_pack[0]", Type::Uint};
777 case 3: 937 case 3:
778 return "uintBitsToFloat(gl_VertexID)"; 938 return {"gl_VertexID", Type::Int};
779 } 939 }
780 UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element); 940 UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
781 return "0"; 941 return {"0", Type::Int};
782 case Attribute::Index::FrontFacing: 942 case Attribute::Index::FrontFacing:
783 // TODO(Subv): Find out what the values are for the other elements. 943 // TODO(Subv): Find out what the values are for the other elements.
784 ASSERT(stage == ProgramType::Fragment); 944 ASSERT(stage == ProgramType::Fragment);
785 switch (element) { 945 switch (element) {
786 case 3: 946 case 3:
787 return "itof(gl_FrontFacing ? -1 : 0)"; 947 return {"(gl_FrontFacing ? -1 : 0)", Type::Int};
788 } 948 }
789 UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element); 949 UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element);
790 return "0"; 950 return {"0", Type::Int};
791 default: 951 default:
792 if (IsGenericAttribute(attribute)) { 952 if (IsGenericAttribute(attribute)) {
793 return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element); 953 return {GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element),
954 Type::Float};
794 } 955 }
795 break; 956 break;
796 } 957 }
797 UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); 958 UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
798 return "0"; 959 return {"0", Type::Int};
799 } 960 }
800 961
801 std::string ApplyPrecise(Operation operation, const std::string& value) { 962 Expression ApplyPrecise(Operation operation, std::string value, Type type) {
802 if (!IsPrecise(operation)) { 963 if (!IsPrecise(operation)) {
803 return value; 964 return {std::move(value), type};
804 } 965 }
805 // There's a bug in NVidia's proprietary drivers that makes precise fail on fragment shaders 966 // Old Nvidia drivers have a bug with precise and texture sampling. These are more likely to
806 const std::string precise = stage != ProgramType::Fragment ? "precise " : ""; 967 // be found in fragment shaders, so we disable precise there. There are vertex shaders that
968 // also fail to build but nobody seems to care about those.
969 // Note: Only bugged drivers will skip precise.
970 const bool disable_precise = device.HasPreciseBug() && stage == ProgramType::Fragment;
807 971
808 const std::string temporary = code.GenerateTemporary(); 972 std::string temporary = code.GenerateTemporary();
809 code.AddLine("{}float {} = {};", precise, temporary, value); 973 code.AddLine("{}{} {} = {};", disable_precise ? "" : "precise ", GetTypeString(type),
810 return temporary; 974 temporary, value);
975 return {std::move(temporary), type};
811 } 976 }
812 977
813 std::string VisitOperand(Operation operation, std::size_t operand_index) { 978 Expression VisitOperand(Operation operation, std::size_t operand_index) {
814 const auto& operand = operation[operand_index]; 979 const auto& operand = operation[operand_index];
815 const bool parent_precise = IsPrecise(operation); 980 const bool parent_precise = IsPrecise(operation);
816 const bool child_precise = IsPrecise(operand); 981 const bool child_precise = IsPrecise(operand);
@@ -819,19 +984,16 @@ private:
819 return Visit(operand); 984 return Visit(operand);
820 } 985 }
821 986
822 const std::string temporary = code.GenerateTemporary(); 987 Expression value = Visit(operand);
823 code.AddLine("float {} = {};", temporary, Visit(operand)); 988 std::string temporary = code.GenerateTemporary();
824 return temporary; 989 code.AddLine("{} {} = {};", GetTypeString(value.GetType()), temporary, value.GetCode());
825 } 990 return {std::move(temporary), value.GetType()};
826
827 std::string VisitOperand(Operation operation, std::size_t operand_index, Type type) {
828 return CastOperand(VisitOperand(operation, operand_index), type);
829 } 991 }
830 992
831 std::optional<std::pair<std::string, bool>> GetOutputAttribute(const AbufNode* abuf) { 993 Expression GetOutputAttribute(const AbufNode* abuf) {
832 switch (const auto attribute = abuf->GetIndex()) { 994 switch (const auto attribute = abuf->GetIndex()) {
833 case Attribute::Index::Position: 995 case Attribute::Index::Position:
834 return std::make_pair("gl_Position"s + GetSwizzle(abuf->GetElement()), false); 996 return {"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float};
835 case Attribute::Index::LayerViewportPointSize: 997 case Attribute::Index::LayerViewportPointSize:
836 switch (abuf->GetElement()) { 998 switch (abuf->GetElement()) {
837 case 0: 999 case 0:
@@ -841,119 +1003,79 @@ private:
841 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { 1003 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) {
842 return {}; 1004 return {};
843 } 1005 }
844 return std::make_pair("gl_Layer", true); 1006 return {"gl_Layer", Type::Int};
845 case 2: 1007 case 2:
846 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { 1008 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) {
847 return {}; 1009 return {};
848 } 1010 }
849 return std::make_pair("gl_ViewportIndex", true); 1011 return {"gl_ViewportIndex", Type::Int};
850 case 3: 1012 case 3:
851 UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader"); 1013 UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader");
852 return std::make_pair("gl_PointSize", false); 1014 return {"gl_PointSize", Type::Float};
853 } 1015 }
854 return {}; 1016 return {};
855 case Attribute::Index::ClipDistances0123: 1017 case Attribute::Index::ClipDistances0123:
856 return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), false); 1018 return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float};
857 case Attribute::Index::ClipDistances4567: 1019 case Attribute::Index::ClipDistances4567:
858 return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), 1020 return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float};
859 false);
860 default: 1021 default:
861 if (IsGenericAttribute(attribute)) { 1022 if (IsGenericAttribute(attribute)) {
862 return std::make_pair( 1023 return {GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()),
863 GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), false); 1024 Type::Float};
864 } 1025 }
865 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); 1026 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute));
866 return {}; 1027 return {};
867 } 1028 }
868 } 1029 }
869 1030
870 std::string CastOperand(const std::string& value, Type type) const { 1031 Expression GenerateUnary(Operation operation, std::string_view func, Type result_type,
871 switch (type) { 1032 Type type_a) {
872 case Type::Bool: 1033 std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0).As(type_a));
873 case Type::Bool2: 1034 return ApplyPrecise(operation, std::move(op_str), result_type);
874 case Type::Float:
875 return value;
876 case Type::Int:
877 return fmt::format("ftoi({})", value);
878 case Type::Uint:
879 return fmt::format("ftou({})", value);
880 case Type::HalfFloat:
881 return fmt::format("toHalf2({})", value);
882 }
883 UNREACHABLE();
884 return value;
885 } 1035 }
886 1036
887 std::string BitwiseCastResult(const std::string& value, Type type, 1037 Expression GenerateBinaryInfix(Operation operation, std::string_view func, Type result_type,
888 bool needs_parenthesis = false) { 1038 Type type_a, Type type_b) {
889 switch (type) { 1039 const std::string op_a = VisitOperand(operation, 0).As(type_a);
890 case Type::Bool: 1040 const std::string op_b = VisitOperand(operation, 1).As(type_b);
891 case Type::Bool2: 1041 std::string op_str = fmt::format("({} {} {})", op_a, func, op_b);
892 case Type::Float:
893 if (needs_parenthesis) {
894 return fmt::format("({})", value);
895 }
896 return value;
897 case Type::Int:
898 return fmt::format("itof({})", value);
899 case Type::Uint:
900 return fmt::format("utof({})", value);
901 case Type::HalfFloat:
902 return fmt::format("fromHalf2({})", value);
903 }
904 UNREACHABLE();
905 return value;
906 }
907
908 std::string GenerateUnary(Operation operation, const std::string& func, Type result_type,
909 Type type_a, bool needs_parenthesis = true) {
910 const std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0, type_a));
911
912 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type, needs_parenthesis));
913 }
914
915 std::string GenerateBinaryInfix(Operation operation, const std::string& func, Type result_type,
916 Type type_a, Type type_b) {
917 const std::string op_a = VisitOperand(operation, 0, type_a);
918 const std::string op_b = VisitOperand(operation, 1, type_b);
919 const std::string op_str = fmt::format("({} {} {})", op_a, func, op_b);
920 1042
921 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1043 return ApplyPrecise(operation, std::move(op_str), result_type);
922 } 1044 }
923 1045
924 std::string GenerateBinaryCall(Operation operation, const std::string& func, Type result_type, 1046 Expression GenerateBinaryCall(Operation operation, std::string_view func, Type result_type,
925 Type type_a, Type type_b) { 1047 Type type_a, Type type_b) {
926 const std::string op_a = VisitOperand(operation, 0, type_a); 1048 const std::string op_a = VisitOperand(operation, 0).As(type_a);
927 const std::string op_b = VisitOperand(operation, 1, type_b); 1049 const std::string op_b = VisitOperand(operation, 1).As(type_b);
928 const std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); 1050 std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b);
929 1051
930 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1052 return ApplyPrecise(operation, std::move(op_str), result_type);
931 } 1053 }
932 1054
933 std::string GenerateTernary(Operation operation, const std::string& func, Type result_type, 1055 Expression GenerateTernary(Operation operation, std::string_view func, Type result_type,
934 Type type_a, Type type_b, Type type_c) { 1056 Type type_a, Type type_b, Type type_c) {
935 const std::string op_a = VisitOperand(operation, 0, type_a); 1057 const std::string op_a = VisitOperand(operation, 0).As(type_a);
936 const std::string op_b = VisitOperand(operation, 1, type_b); 1058 const std::string op_b = VisitOperand(operation, 1).As(type_b);
937 const std::string op_c = VisitOperand(operation, 2, type_c); 1059 const std::string op_c = VisitOperand(operation, 2).As(type_c);
938 const std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c); 1060 std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c);
939 1061
940 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1062 return ApplyPrecise(operation, std::move(op_str), result_type);
941 } 1063 }
942 1064
943 std::string GenerateQuaternary(Operation operation, const std::string& func, Type result_type, 1065 Expression GenerateQuaternary(Operation operation, const std::string& func, Type result_type,
944 Type type_a, Type type_b, Type type_c, Type type_d) { 1066 Type type_a, Type type_b, Type type_c, Type type_d) {
945 const std::string op_a = VisitOperand(operation, 0, type_a); 1067 const std::string op_a = VisitOperand(operation, 0).As(type_a);
946 const std::string op_b = VisitOperand(operation, 1, type_b); 1068 const std::string op_b = VisitOperand(operation, 1).As(type_b);
947 const std::string op_c = VisitOperand(operation, 2, type_c); 1069 const std::string op_c = VisitOperand(operation, 2).As(type_c);
948 const std::string op_d = VisitOperand(operation, 3, type_d); 1070 const std::string op_d = VisitOperand(operation, 3).As(type_d);
949 const std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d); 1071 std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d);
950 1072
951 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1073 return ApplyPrecise(operation, std::move(op_str), result_type);
952 } 1074 }
953 1075
954 std::string GenerateTexture(Operation operation, const std::string& function_suffix, 1076 std::string GenerateTexture(Operation operation, const std::string& function_suffix,
955 const std::vector<TextureIR>& extras) { 1077 const std::vector<TextureIR>& extras) {
956 constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"}; 1078 constexpr std::array coord_constructors = {"float", "vec2", "vec3", "vec4"};
957 1079
958 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1080 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
959 ASSERT(meta); 1081 ASSERT(meta);
@@ -970,17 +1092,17 @@ private:
970 expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1); 1092 expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1);
971 expr += '('; 1093 expr += '(';
972 for (std::size_t i = 0; i < count; ++i) { 1094 for (std::size_t i = 0; i < count; ++i) {
973 expr += Visit(operation[i]); 1095 expr += Visit(operation[i]).AsFloat();
974 1096
975 const std::size_t next = i + 1; 1097 const std::size_t next = i + 1;
976 if (next < count) 1098 if (next < count)
977 expr += ", "; 1099 expr += ", ";
978 } 1100 }
979 if (has_array) { 1101 if (has_array) {
980 expr += ", float(ftoi(" + Visit(meta->array) + "))"; 1102 expr += ", float(" + Visit(meta->array).AsInt() + ')';
981 } 1103 }
982 if (has_shadow) { 1104 if (has_shadow) {
983 expr += ", " + Visit(meta->depth_compare); 1105 expr += ", " + Visit(meta->depth_compare).AsFloat();
984 } 1106 }
985 expr += ')'; 1107 expr += ')';
986 1108
@@ -1011,11 +1133,11 @@ private:
1011 // required to be constant) 1133 // required to be constant)
1012 expr += std::to_string(static_cast<s32>(immediate->GetValue())); 1134 expr += std::to_string(static_cast<s32>(immediate->GetValue()));
1013 } else { 1135 } else {
1014 expr += fmt::format("ftoi({})", Visit(operand)); 1136 expr += Visit(operand).AsInt();
1015 } 1137 }
1016 break; 1138 break;
1017 case Type::Float: 1139 case Type::Float:
1018 expr += Visit(operand); 1140 expr += Visit(operand).AsFloat();
1019 break; 1141 break;
1020 default: { 1142 default: {
1021 const auto type_int = static_cast<u32>(type); 1143 const auto type_int = static_cast<u32>(type);
@@ -1031,7 +1153,7 @@ private:
1031 if (aoffi.empty()) { 1153 if (aoffi.empty()) {
1032 return {}; 1154 return {};
1033 } 1155 }
1034 constexpr std::array<const char*, 3> coord_constructors = {"int", "ivec2", "ivec3"}; 1156 constexpr std::array coord_constructors = {"int", "ivec2", "ivec3"};
1035 std::string expr = ", "; 1157 std::string expr = ", ";
1036 expr += coord_constructors.at(aoffi.size() - 1); 1158 expr += coord_constructors.at(aoffi.size() - 1);
1037 expr += '('; 1159 expr += '(';
@@ -1044,7 +1166,7 @@ private:
1044 expr += std::to_string(static_cast<s32>(immediate->GetValue())); 1166 expr += std::to_string(static_cast<s32>(immediate->GetValue()));
1045 } else if (device.HasVariableAoffi()) { 1167 } else if (device.HasVariableAoffi()) {
1046 // Avoid using variable AOFFI on unsupported devices. 1168 // Avoid using variable AOFFI on unsupported devices.
1047 expr += fmt::format("ftoi({})", Visit(operand)); 1169 expr += Visit(operand).AsInt();
1048 } else { 1170 } else {
1049 // Insert 0 on devices not supporting variable AOFFI. 1171 // Insert 0 on devices not supporting variable AOFFI.
1050 expr += '0'; 1172 expr += '0';
@@ -1058,328 +1180,314 @@ private:
1058 return expr; 1180 return expr;
1059 } 1181 }
1060 1182
1061 std::string Assign(Operation operation) { 1183 Expression Assign(Operation operation) {
1062 const Node& dest = operation[0]; 1184 const Node& dest = operation[0];
1063 const Node& src = operation[1]; 1185 const Node& src = operation[1];
1064 1186
1065 std::string target; 1187 Expression target;
1066 bool is_integer = false;
1067
1068 if (const auto gpr = std::get_if<GprNode>(&*dest)) { 1188 if (const auto gpr = std::get_if<GprNode>(&*dest)) {
1069 if (gpr->GetIndex() == Register::ZeroIndex) { 1189 if (gpr->GetIndex() == Register::ZeroIndex) {
1070 // Writing to Register::ZeroIndex is a no op 1190 // Writing to Register::ZeroIndex is a no op
1071 return {}; 1191 return {};
1072 } 1192 }
1073 target = GetRegister(gpr->GetIndex()); 1193 target = {GetRegister(gpr->GetIndex()), Type::Float};
1074 } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) { 1194 } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) {
1075 UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer()); 1195 UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer());
1076 const auto result = GetOutputAttribute(abuf); 1196 target = GetOutputAttribute(abuf);
1077 if (!result) {
1078 return {};
1079 }
1080 target = result->first;
1081 is_integer = result->second;
1082 } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) { 1197 } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) {
1083 if (stage == ProgramType::Compute) { 1198 if (stage == ProgramType::Compute) {
1084 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); 1199 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders");
1085 } 1200 }
1086 target = fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); 1201 target = {
1202 fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()),
1203 Type::Uint};
1087 } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { 1204 } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) {
1088 const std::string real = Visit(gmem->GetRealAddress()); 1205 const std::string real = Visit(gmem->GetRealAddress()).AsUint();
1089 const std::string base = Visit(gmem->GetBaseAddress()); 1206 const std::string base = Visit(gmem->GetBaseAddress()).AsUint();
1090 const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); 1207 const std::string final_offset = fmt::format("({} - {}) >> 2", real, base);
1091 target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); 1208 target = {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset),
1209 Type::Uint};
1092 } else { 1210 } else {
1093 UNREACHABLE_MSG("Assign called without a proper target"); 1211 UNREACHABLE_MSG("Assign called without a proper target");
1094 } 1212 }
1095 1213
1096 if (is_integer) { 1214 code.AddLine("{} = {};", target.GetCode(), Visit(src).As(target.GetType()));
1097 code.AddLine("{} = ftoi({});", target, Visit(src));
1098 } else {
1099 code.AddLine("{} = {};", target, Visit(src));
1100 }
1101 return {}; 1215 return {};
1102 } 1216 }
1103 1217
1104 template <Type type> 1218 template <Type type>
1105 std::string Add(Operation operation) { 1219 Expression Add(Operation operation) {
1106 return GenerateBinaryInfix(operation, "+", type, type, type); 1220 return GenerateBinaryInfix(operation, "+", type, type, type);
1107 } 1221 }
1108 1222
1109 template <Type type> 1223 template <Type type>
1110 std::string Mul(Operation operation) { 1224 Expression Mul(Operation operation) {
1111 return GenerateBinaryInfix(operation, "*", type, type, type); 1225 return GenerateBinaryInfix(operation, "*", type, type, type);
1112 } 1226 }
1113 1227
1114 template <Type type> 1228 template <Type type>
1115 std::string Div(Operation operation) { 1229 Expression Div(Operation operation) {
1116 return GenerateBinaryInfix(operation, "/", type, type, type); 1230 return GenerateBinaryInfix(operation, "/", type, type, type);
1117 } 1231 }
1118 1232
1119 template <Type type> 1233 template <Type type>
1120 std::string Fma(Operation operation) { 1234 Expression Fma(Operation operation) {
1121 return GenerateTernary(operation, "fma", type, type, type, type); 1235 return GenerateTernary(operation, "fma", type, type, type, type);
1122 } 1236 }
1123 1237
1124 template <Type type> 1238 template <Type type>
1125 std::string Negate(Operation operation) { 1239 Expression Negate(Operation operation) {
1126 return GenerateUnary(operation, "-", type, type, true); 1240 return GenerateUnary(operation, "-", type, type);
1127 } 1241 }
1128 1242
1129 template <Type type> 1243 template <Type type>
1130 std::string Absolute(Operation operation) { 1244 Expression Absolute(Operation operation) {
1131 return GenerateUnary(operation, "abs", type, type, false); 1245 return GenerateUnary(operation, "abs", type, type);
1132 } 1246 }
1133 1247
1134 std::string FClamp(Operation operation) { 1248 Expression FClamp(Operation operation) {
1135 return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float, 1249 return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float,
1136 Type::Float); 1250 Type::Float);
1137 } 1251 }
1138 1252
1139 std::string FCastHalf0(Operation operation) { 1253 Expression FCastHalf0(Operation operation) {
1140 const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); 1254 return {fmt::format("({})[0]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float};
1141 return fmt::format("({})[0]", op_a);
1142 } 1255 }
1143 1256
1144 std::string FCastHalf1(Operation operation) { 1257 Expression FCastHalf1(Operation operation) {
1145 const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); 1258 return {fmt::format("({})[1]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float};
1146 return fmt::format("({})[1]", op_a);
1147 } 1259 }
1148 1260
1149 template <Type type> 1261 template <Type type>
1150 std::string Min(Operation operation) { 1262 Expression Min(Operation operation) {
1151 return GenerateBinaryCall(operation, "min", type, type, type); 1263 return GenerateBinaryCall(operation, "min", type, type, type);
1152 } 1264 }
1153 1265
1154 template <Type type> 1266 template <Type type>
1155 std::string Max(Operation operation) { 1267 Expression Max(Operation operation) {
1156 return GenerateBinaryCall(operation, "max", type, type, type); 1268 return GenerateBinaryCall(operation, "max", type, type, type);
1157 } 1269 }
1158 1270
1159 std::string Select(Operation operation) { 1271 Expression Select(Operation operation) {
1160 const std::string condition = Visit(operation[0]); 1272 const std::string condition = Visit(operation[0]).AsBool();
1161 const std::string true_case = Visit(operation[1]); 1273 const std::string true_case = Visit(operation[1]).AsUint();
1162 const std::string false_case = Visit(operation[2]); 1274 const std::string false_case = Visit(operation[2]).AsUint();
1163 const std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); 1275 std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case);
1164 1276
1165 return ApplyPrecise(operation, op_str); 1277 return ApplyPrecise(operation, std::move(op_str), Type::Uint);
1166 } 1278 }
1167 1279
1168 std::string FCos(Operation operation) { 1280 Expression FCos(Operation operation) {
1169 return GenerateUnary(operation, "cos", Type::Float, Type::Float, false); 1281 return GenerateUnary(operation, "cos", Type::Float, Type::Float);
1170 } 1282 }
1171 1283
1172 std::string FSin(Operation operation) { 1284 Expression FSin(Operation operation) {
1173 return GenerateUnary(operation, "sin", Type::Float, Type::Float, false); 1285 return GenerateUnary(operation, "sin", Type::Float, Type::Float);
1174 } 1286 }
1175 1287
1176 std::string FExp2(Operation operation) { 1288 Expression FExp2(Operation operation) {
1177 return GenerateUnary(operation, "exp2", Type::Float, Type::Float, false); 1289 return GenerateUnary(operation, "exp2", Type::Float, Type::Float);
1178 } 1290 }
1179 1291
1180 std::string FLog2(Operation operation) { 1292 Expression FLog2(Operation operation) {
1181 return GenerateUnary(operation, "log2", Type::Float, Type::Float, false); 1293 return GenerateUnary(operation, "log2", Type::Float, Type::Float);
1182 } 1294 }
1183 1295
1184 std::string FInverseSqrt(Operation operation) { 1296 Expression FInverseSqrt(Operation operation) {
1185 return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float, false); 1297 return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float);
1186 } 1298 }
1187 1299
1188 std::string FSqrt(Operation operation) { 1300 Expression FSqrt(Operation operation) {
1189 return GenerateUnary(operation, "sqrt", Type::Float, Type::Float, false); 1301 return GenerateUnary(operation, "sqrt", Type::Float, Type::Float);
1190 } 1302 }
1191 1303
1192 std::string FRoundEven(Operation operation) { 1304 Expression FRoundEven(Operation operation) {
1193 return GenerateUnary(operation, "roundEven", Type::Float, Type::Float, false); 1305 return GenerateUnary(operation, "roundEven", Type::Float, Type::Float);
1194 } 1306 }
1195 1307
1196 std::string FFloor(Operation operation) { 1308 Expression FFloor(Operation operation) {
1197 return GenerateUnary(operation, "floor", Type::Float, Type::Float, false); 1309 return GenerateUnary(operation, "floor", Type::Float, Type::Float);
1198 } 1310 }
1199 1311
1200 std::string FCeil(Operation operation) { 1312 Expression FCeil(Operation operation) {
1201 return GenerateUnary(operation, "ceil", Type::Float, Type::Float, false); 1313 return GenerateUnary(operation, "ceil", Type::Float, Type::Float);
1202 } 1314 }
1203 1315
1204 std::string FTrunc(Operation operation) { 1316 Expression FTrunc(Operation operation) {
1205 return GenerateUnary(operation, "trunc", Type::Float, Type::Float, false); 1317 return GenerateUnary(operation, "trunc", Type::Float, Type::Float);
1206 } 1318 }
1207 1319
1208 template <Type type> 1320 template <Type type>
1209 std::string FCastInteger(Operation operation) { 1321 Expression FCastInteger(Operation operation) {
1210 return GenerateUnary(operation, "float", Type::Float, type, false); 1322 return GenerateUnary(operation, "float", Type::Float, type);
1211 } 1323 }
1212 1324
1213 std::string ICastFloat(Operation operation) { 1325 Expression ICastFloat(Operation operation) {
1214 return GenerateUnary(operation, "int", Type::Int, Type::Float, false); 1326 return GenerateUnary(operation, "int", Type::Int, Type::Float);
1215 } 1327 }
1216 1328
1217 std::string ICastUnsigned(Operation operation) { 1329 Expression ICastUnsigned(Operation operation) {
1218 return GenerateUnary(operation, "int", Type::Int, Type::Uint, false); 1330 return GenerateUnary(operation, "int", Type::Int, Type::Uint);
1219 } 1331 }
1220 1332
1221 template <Type type> 1333 template <Type type>
1222 std::string LogicalShiftLeft(Operation operation) { 1334 Expression LogicalShiftLeft(Operation operation) {
1223 return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint); 1335 return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint);
1224 } 1336 }
1225 1337
1226 std::string ILogicalShiftRight(Operation operation) { 1338 Expression ILogicalShiftRight(Operation operation) {
1227 const std::string op_a = VisitOperand(operation, 0, Type::Uint); 1339 const std::string op_a = VisitOperand(operation, 0).AsUint();
1228 const std::string op_b = VisitOperand(operation, 1, Type::Uint); 1340 const std::string op_b = VisitOperand(operation, 1).AsUint();
1229 const std::string op_str = fmt::format("int({} >> {})", op_a, op_b); 1341 std::string op_str = fmt::format("int({} >> {})", op_a, op_b);
1230 1342
1231 return ApplyPrecise(operation, BitwiseCastResult(op_str, Type::Int)); 1343 return ApplyPrecise(operation, std::move(op_str), Type::Int);
1232 } 1344 }
1233 1345
1234 std::string IArithmeticShiftRight(Operation operation) { 1346 Expression IArithmeticShiftRight(Operation operation) {
1235 return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint); 1347 return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint);
1236 } 1348 }
1237 1349
1238 template <Type type> 1350 template <Type type>
1239 std::string BitwiseAnd(Operation operation) { 1351 Expression BitwiseAnd(Operation operation) {
1240 return GenerateBinaryInfix(operation, "&", type, type, type); 1352 return GenerateBinaryInfix(operation, "&", type, type, type);
1241 } 1353 }
1242 1354
1243 template <Type type> 1355 template <Type type>
1244 std::string BitwiseOr(Operation operation) { 1356 Expression BitwiseOr(Operation operation) {
1245 return GenerateBinaryInfix(operation, "|", type, type, type); 1357 return GenerateBinaryInfix(operation, "|", type, type, type);
1246 } 1358 }
1247 1359
1248 template <Type type> 1360 template <Type type>
1249 std::string BitwiseXor(Operation operation) { 1361 Expression BitwiseXor(Operation operation) {
1250 return GenerateBinaryInfix(operation, "^", type, type, type); 1362 return GenerateBinaryInfix(operation, "^", type, type, type);
1251 } 1363 }
1252 1364
1253 template <Type type> 1365 template <Type type>
1254 std::string BitwiseNot(Operation operation) { 1366 Expression BitwiseNot(Operation operation) {
1255 return GenerateUnary(operation, "~", type, type, false); 1367 return GenerateUnary(operation, "~", type, type);
1256 } 1368 }
1257 1369
1258 std::string UCastFloat(Operation operation) { 1370 Expression UCastFloat(Operation operation) {
1259 return GenerateUnary(operation, "uint", Type::Uint, Type::Float, false); 1371 return GenerateUnary(operation, "uint", Type::Uint, Type::Float);
1260 } 1372 }
1261 1373
1262 std::string UCastSigned(Operation operation) { 1374 Expression UCastSigned(Operation operation) {
1263 return GenerateUnary(operation, "uint", Type::Uint, Type::Int, false); 1375 return GenerateUnary(operation, "uint", Type::Uint, Type::Int);
1264 } 1376 }
1265 1377
1266 std::string UShiftRight(Operation operation) { 1378 Expression UShiftRight(Operation operation) {
1267 return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint); 1379 return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint);
1268 } 1380 }
1269 1381
1270 template <Type type> 1382 template <Type type>
1271 std::string BitfieldInsert(Operation operation) { 1383 Expression BitfieldInsert(Operation operation) {
1272 return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int, 1384 return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int,
1273 Type::Int); 1385 Type::Int);
1274 } 1386 }
1275 1387
1276 template <Type type> 1388 template <Type type>
1277 std::string BitfieldExtract(Operation operation) { 1389 Expression BitfieldExtract(Operation operation) {
1278 return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int); 1390 return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int);
1279 } 1391 }
1280 1392
1281 template <Type type> 1393 template <Type type>
1282 std::string BitCount(Operation operation) { 1394 Expression BitCount(Operation operation) {
1283 return GenerateUnary(operation, "bitCount", type, type, false); 1395 return GenerateUnary(operation, "bitCount", type, type);
1284 } 1396 }
1285 1397
1286 std::string HNegate(Operation operation) { 1398 Expression HNegate(Operation operation) {
1287 const auto GetNegate = [&](std::size_t index) { 1399 const auto GetNegate = [&](std::size_t index) {
1288 return VisitOperand(operation, index, Type::Bool) + " ? -1 : 1"; 1400 return VisitOperand(operation, index).AsBool() + " ? -1 : 1";
1289 }; 1401 };
1290 const std::string value = 1402 return {fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0).AsHalfFloat(),
1291 fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0, Type::HalfFloat), 1403 GetNegate(1), GetNegate(2)),
1292 GetNegate(1), GetNegate(2)); 1404 Type::HalfFloat};
1293 return BitwiseCastResult(value, Type::HalfFloat); 1405 }
1294 } 1406
1295 1407 Expression HClamp(Operation operation) {
1296 std::string HClamp(Operation operation) { 1408 const std::string value = VisitOperand(operation, 0).AsHalfFloat();
1297 const std::string value = VisitOperand(operation, 0, Type::HalfFloat); 1409 const std::string min = VisitOperand(operation, 1).AsFloat();
1298 const std::string min = VisitOperand(operation, 1, Type::Float); 1410 const std::string max = VisitOperand(operation, 2).AsFloat();
1299 const std::string max = VisitOperand(operation, 2, Type::Float); 1411 std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max);
1300 const std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); 1412
1301 1413 return ApplyPrecise(operation, std::move(clamped), Type::HalfFloat);
1302 return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat)); 1414 }
1303 } 1415
1304 1416 Expression HCastFloat(Operation operation) {
1305 std::string HCastFloat(Operation operation) { 1417 return {fmt::format("vec2({})", VisitOperand(operation, 0).AsFloat()), Type::HalfFloat};
1306 const std::string op_a = VisitOperand(operation, 0, Type::Float); 1418 }
1307 return fmt::format("fromHalf2(vec2({}, 0.0f))", op_a); 1419
1308 } 1420 Expression HUnpack(Operation operation) {
1309 1421 Expression operand = VisitOperand(operation, 0);
1310 std::string HUnpack(Operation operation) { 1422 switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) {
1311 const std::string operand{VisitOperand(operation, 0, Type::HalfFloat)}; 1423 case Tegra::Shader::HalfType::H0_H1:
1312 const auto value = [&]() -> std::string { 1424 return operand;
1313 switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) { 1425 case Tegra::Shader::HalfType::F32:
1314 case Tegra::Shader::HalfType::H0_H1: 1426 return {fmt::format("vec2({})", operand.AsFloat()), Type::HalfFloat};
1315 return operand; 1427 case Tegra::Shader::HalfType::H0_H0:
1316 case Tegra::Shader::HalfType::F32: 1428 return {fmt::format("vec2({}[0])", operand.AsHalfFloat()), Type::HalfFloat};
1317 return fmt::format("vec2(fromHalf2({}))", operand); 1429 case Tegra::Shader::HalfType::H1_H1:
1318 case Tegra::Shader::HalfType::H0_H0: 1430 return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat};
1319 return fmt::format("vec2({}[0])", operand); 1431 }
1320 case Tegra::Shader::HalfType::H1_H1:
1321 return fmt::format("vec2({}[1])", operand);
1322 }
1323 UNREACHABLE();
1324 return "0";
1325 }();
1326 return fmt::format("fromHalf2({})", value);
1327 } 1432 }
1328 1433
1329 std::string HMergeF32(Operation operation) { 1434 Expression HMergeF32(Operation operation) {
1330 return fmt::format("float(toHalf2({})[0])", Visit(operation[0])); 1435 return {fmt::format("float({}[0])", VisitOperand(operation, 0).AsHalfFloat()), Type::Float};
1331 } 1436 }
1332 1437
1333 std::string HMergeH0(Operation operation) { 1438 Expression HMergeH0(Operation operation) {
1334 return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[1]), 1439 std::string dest = VisitOperand(operation, 0).AsUint();
1335 Visit(operation[0])); 1440 std::string src = VisitOperand(operation, 1).AsUint();
1441 return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", src, dest), Type::Uint};
1336 } 1442 }
1337 1443
1338 std::string HMergeH1(Operation operation) { 1444 Expression HMergeH1(Operation operation) {
1339 return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[0]), 1445 std::string dest = VisitOperand(operation, 0).AsUint();
1340 Visit(operation[1])); 1446 std::string src = VisitOperand(operation, 1).AsUint();
1447 return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", dest, src), Type::Uint};
1341 } 1448 }
1342 1449
1343 std::string HPack2(Operation operation) { 1450 Expression HPack2(Operation operation) {
1344 return fmt::format("utof(packHalf2x16(vec2({}, {})))", Visit(operation[0]), 1451 return {fmt::format("vec2({}, {})", VisitOperand(operation, 0).AsFloat(),
1345 Visit(operation[1])); 1452 VisitOperand(operation, 1).AsFloat()),
1453 Type::HalfFloat};
1346 } 1454 }
1347 1455
1348 template <Type type> 1456 template <Type type>
1349 std::string LogicalLessThan(Operation operation) { 1457 Expression LogicalLessThan(Operation operation) {
1350 return GenerateBinaryInfix(operation, "<", Type::Bool, type, type); 1458 return GenerateBinaryInfix(operation, "<", Type::Bool, type, type);
1351 } 1459 }
1352 1460
1353 template <Type type> 1461 template <Type type>
1354 std::string LogicalEqual(Operation operation) { 1462 Expression LogicalEqual(Operation operation) {
1355 return GenerateBinaryInfix(operation, "==", Type::Bool, type, type); 1463 return GenerateBinaryInfix(operation, "==", Type::Bool, type, type);
1356 } 1464 }
1357 1465
1358 template <Type type> 1466 template <Type type>
1359 std::string LogicalLessEqual(Operation operation) { 1467 Expression LogicalLessEqual(Operation operation) {
1360 return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type); 1468 return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type);
1361 } 1469 }
1362 1470
1363 template <Type type> 1471 template <Type type>
1364 std::string LogicalGreaterThan(Operation operation) { 1472 Expression LogicalGreaterThan(Operation operation) {
1365 return GenerateBinaryInfix(operation, ">", Type::Bool, type, type); 1473 return GenerateBinaryInfix(operation, ">", Type::Bool, type, type);
1366 } 1474 }
1367 1475
1368 template <Type type> 1476 template <Type type>
1369 std::string LogicalNotEqual(Operation operation) { 1477 Expression LogicalNotEqual(Operation operation) {
1370 return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type); 1478 return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type);
1371 } 1479 }
1372 1480
1373 template <Type type> 1481 template <Type type>
1374 std::string LogicalGreaterEqual(Operation operation) { 1482 Expression LogicalGreaterEqual(Operation operation) {
1375 return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type); 1483 return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type);
1376 } 1484 }
1377 1485
1378 std::string LogicalFIsNan(Operation operation) { 1486 Expression LogicalFIsNan(Operation operation) {
1379 return GenerateUnary(operation, "isnan", Type::Bool, Type::Float, false); 1487 return GenerateUnary(operation, "isnan", Type::Bool, Type::Float);
1380 } 1488 }
1381 1489
1382 std::string LogicalAssign(Operation operation) { 1490 Expression LogicalAssign(Operation operation) {
1383 const Node& dest = operation[0]; 1491 const Node& dest = operation[0];
1384 const Node& src = operation[1]; 1492 const Node& src = operation[1];
1385 1493
@@ -1400,78 +1508,80 @@ private:
1400 target = GetInternalFlag(flag->GetFlag()); 1508 target = GetInternalFlag(flag->GetFlag());
1401 } 1509 }
1402 1510
1403 code.AddLine("{} = {};", target, Visit(src)); 1511 code.AddLine("{} = {};", target, Visit(src).AsBool());
1404 return {}; 1512 return {};
1405 } 1513 }
1406 1514
1407 std::string LogicalAnd(Operation operation) { 1515 Expression LogicalAnd(Operation operation) {
1408 return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool); 1516 return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool);
1409 } 1517 }
1410 1518
1411 std::string LogicalOr(Operation operation) { 1519 Expression LogicalOr(Operation operation) {
1412 return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool); 1520 return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool);
1413 } 1521 }
1414 1522
1415 std::string LogicalXor(Operation operation) { 1523 Expression LogicalXor(Operation operation) {
1416 return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool); 1524 return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool);
1417 } 1525 }
1418 1526
1419 std::string LogicalNegate(Operation operation) { 1527 Expression LogicalNegate(Operation operation) {
1420 return GenerateUnary(operation, "!", Type::Bool, Type::Bool, false); 1528 return GenerateUnary(operation, "!", Type::Bool, Type::Bool);
1421 } 1529 }
1422 1530
1423 std::string LogicalPick2(Operation operation) { 1531 Expression LogicalPick2(Operation operation) {
1424 const std::string pair = VisitOperand(operation, 0, Type::Bool2); 1532 return {fmt::format("{}[{}]", VisitOperand(operation, 0).AsBool2(),
1425 return fmt::format("{}[{}]", pair, VisitOperand(operation, 1, Type::Uint)); 1533 VisitOperand(operation, 1).AsUint()),
1534 Type::Bool};
1426 } 1535 }
1427 1536
1428 std::string LogicalAnd2(Operation operation) { 1537 Expression LogicalAnd2(Operation operation) {
1429 return GenerateUnary(operation, "all", Type::Bool, Type::Bool2); 1538 return GenerateUnary(operation, "all", Type::Bool, Type::Bool2);
1430 } 1539 }
1431 1540
1432 template <bool with_nan> 1541 template <bool with_nan>
1433 std::string GenerateHalfComparison(Operation operation, const std::string& compare_op) { 1542 Expression GenerateHalfComparison(Operation operation, std::string_view compare_op) {
1434 const std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2, 1543 Expression comparison = GenerateBinaryCall(operation, compare_op, Type::Bool2,
1435 Type::HalfFloat, Type::HalfFloat)}; 1544 Type::HalfFloat, Type::HalfFloat);
1436 if constexpr (!with_nan) { 1545 if constexpr (!with_nan) {
1437 return comparison; 1546 return comparison;
1438 } 1547 }
1439 return fmt::format("halfFloatNanComparison({}, {}, {})", comparison, 1548 return {fmt::format("HalfFloatNanComparison({}, {}, {})", comparison.AsBool2(),
1440 VisitOperand(operation, 0, Type::HalfFloat), 1549 VisitOperand(operation, 0).AsHalfFloat(),
1441 VisitOperand(operation, 1, Type::HalfFloat)); 1550 VisitOperand(operation, 1).AsHalfFloat()),
1551 Type::Bool2};
1442 } 1552 }
1443 1553
1444 template <bool with_nan> 1554 template <bool with_nan>
1445 std::string Logical2HLessThan(Operation operation) { 1555 Expression Logical2HLessThan(Operation operation) {
1446 return GenerateHalfComparison<with_nan>(operation, "lessThan"); 1556 return GenerateHalfComparison<with_nan>(operation, "lessThan");
1447 } 1557 }
1448 1558
1449 template <bool with_nan> 1559 template <bool with_nan>
1450 std::string Logical2HEqual(Operation operation) { 1560 Expression Logical2HEqual(Operation operation) {
1451 return GenerateHalfComparison<with_nan>(operation, "equal"); 1561 return GenerateHalfComparison<with_nan>(operation, "equal");
1452 } 1562 }
1453 1563
1454 template <bool with_nan> 1564 template <bool with_nan>
1455 std::string Logical2HLessEqual(Operation operation) { 1565 Expression Logical2HLessEqual(Operation operation) {
1456 return GenerateHalfComparison<with_nan>(operation, "lessThanEqual"); 1566 return GenerateHalfComparison<with_nan>(operation, "lessThanEqual");
1457 } 1567 }
1458 1568
1459 template <bool with_nan> 1569 template <bool with_nan>
1460 std::string Logical2HGreaterThan(Operation operation) { 1570 Expression Logical2HGreaterThan(Operation operation) {
1461 return GenerateHalfComparison<with_nan>(operation, "greaterThan"); 1571 return GenerateHalfComparison<with_nan>(operation, "greaterThan");
1462 } 1572 }
1463 1573
1464 template <bool with_nan> 1574 template <bool with_nan>
1465 std::string Logical2HNotEqual(Operation operation) { 1575 Expression Logical2HNotEqual(Operation operation) {
1466 return GenerateHalfComparison<with_nan>(operation, "notEqual"); 1576 return GenerateHalfComparison<with_nan>(operation, "notEqual");
1467 } 1577 }
1468 1578
1469 template <bool with_nan> 1579 template <bool with_nan>
1470 std::string Logical2HGreaterEqual(Operation operation) { 1580 Expression Logical2HGreaterEqual(Operation operation) {
1471 return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual"); 1581 return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual");
1472 } 1582 }
1473 1583
1474 std::string Texture(Operation operation) { 1584 Expression Texture(Operation operation) {
1475 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1585 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1476 ASSERT(meta); 1586 ASSERT(meta);
1477 1587
@@ -1480,10 +1590,10 @@ private:
1480 if (meta->sampler.IsShadow()) { 1590 if (meta->sampler.IsShadow()) {
1481 expr = "vec4(" + expr + ')'; 1591 expr = "vec4(" + expr + ')';
1482 } 1592 }
1483 return expr + GetSwizzle(meta->element); 1593 return {expr + GetSwizzle(meta->element), Type::Float};
1484 } 1594 }
1485 1595
1486 std::string TextureLod(Operation operation) { 1596 Expression TextureLod(Operation operation) {
1487 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1597 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1488 ASSERT(meta); 1598 ASSERT(meta);
1489 1599
@@ -1492,54 +1602,54 @@ private:
1492 if (meta->sampler.IsShadow()) { 1602 if (meta->sampler.IsShadow()) {
1493 expr = "vec4(" + expr + ')'; 1603 expr = "vec4(" + expr + ')';
1494 } 1604 }
1495 return expr + GetSwizzle(meta->element); 1605 return {expr + GetSwizzle(meta->element), Type::Float};
1496 } 1606 }
1497 1607
1498 std::string TextureGather(Operation operation) { 1608 Expression TextureGather(Operation operation) {
1499 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1609 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1500 ASSERT(meta); 1610 ASSERT(meta);
1501 1611
1502 const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; 1612 const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int;
1503 return GenerateTexture(operation, "Gather", 1613 return {GenerateTexture(operation, "Gather",
1504 {TextureArgument{type, meta->component}, TextureAoffi{}}) + 1614 {TextureArgument{type, meta->component}, TextureAoffi{}}) +
1505 GetSwizzle(meta->element); 1615 GetSwizzle(meta->element),
1616 Type::Float};
1506 } 1617 }
1507 1618
1508 std::string TextureQueryDimensions(Operation operation) { 1619 Expression TextureQueryDimensions(Operation operation) {
1509 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1620 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1510 ASSERT(meta); 1621 ASSERT(meta);
1511 1622
1512 const std::string sampler = GetSampler(meta->sampler); 1623 const std::string sampler = GetSampler(meta->sampler);
1513 const std::string lod = VisitOperand(operation, 0, Type::Int); 1624 const std::string lod = VisitOperand(operation, 0).AsInt();
1514 1625
1515 switch (meta->element) { 1626 switch (meta->element) {
1516 case 0: 1627 case 0:
1517 case 1: 1628 case 1:
1518 return fmt::format("itof(int(textureSize({}, {}){}))", sampler, lod, 1629 return {fmt::format("textureSize({}, {}){}", sampler, lod, GetSwizzle(meta->element)),
1519 GetSwizzle(meta->element)); 1630 Type::Int};
1520 case 2:
1521 return "0";
1522 case 3: 1631 case 3:
1523 return fmt::format("itof(textureQueryLevels({}))", sampler); 1632 return {fmt::format("textureQueryLevels({})", sampler), Type::Int};
1524 } 1633 }
1525 UNREACHABLE(); 1634 UNREACHABLE();
1526 return "0"; 1635 return {"0", Type::Int};
1527 } 1636 }
1528 1637
1529 std::string TextureQueryLod(Operation operation) { 1638 Expression TextureQueryLod(Operation operation) {
1530 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1639 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1531 ASSERT(meta); 1640 ASSERT(meta);
1532 1641
1533 if (meta->element < 2) { 1642 if (meta->element < 2) {
1534 return fmt::format("itof(int(({} * vec2(256)){}))", 1643 return {fmt::format("int(({} * vec2(256)){})",
1535 GenerateTexture(operation, "QueryLod", {}), 1644 GenerateTexture(operation, "QueryLod", {}),
1536 GetSwizzle(meta->element)); 1645 GetSwizzle(meta->element)),
1646 Type::Int};
1537 } 1647 }
1538 return "0"; 1648 return {"0", Type::Int};
1539 } 1649 }
1540 1650
1541 std::string TexelFetch(Operation operation) { 1651 Expression TexelFetch(Operation operation) {
1542 constexpr std::array<const char*, 4> constructors = {"int", "ivec2", "ivec3", "ivec4"}; 1652 constexpr std::array constructors = {"int", "ivec2", "ivec3", "ivec4"};
1543 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1653 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1544 ASSERT(meta); 1654 ASSERT(meta);
1545 UNIMPLEMENTED_IF(meta->sampler.IsArray()); 1655 UNIMPLEMENTED_IF(meta->sampler.IsArray());
@@ -1552,7 +1662,7 @@ private:
1552 expr += constructors.at(operation.GetOperandsCount() - 1); 1662 expr += constructors.at(operation.GetOperandsCount() - 1);
1553 expr += '('; 1663 expr += '(';
1554 for (std::size_t i = 0; i < count; ++i) { 1664 for (std::size_t i = 0; i < count; ++i) {
1555 expr += VisitOperand(operation, i, Type::Int); 1665 expr += VisitOperand(operation, i).AsInt();
1556 const std::size_t next = i + 1; 1666 const std::size_t next = i + 1;
1557 if (next == count) 1667 if (next == count)
1558 expr += ')'; 1668 expr += ')';
@@ -1565,7 +1675,7 @@ private:
1565 1675
1566 if (meta->lod) { 1676 if (meta->lod) {
1567 expr += ", "; 1677 expr += ", ";
1568 expr += CastOperand(Visit(meta->lod), Type::Int); 1678 expr += Visit(meta->lod).AsInt();
1569 } 1679 }
1570 expr += ')'; 1680 expr += ')';
1571 expr += GetSwizzle(meta->element); 1681 expr += GetSwizzle(meta->element);
@@ -1580,11 +1690,11 @@ private:
1580 code.AddLine("float {} = {};", tmp, expr); 1690 code.AddLine("float {} = {};", tmp, expr);
1581 code.AddLine("#endif"); 1691 code.AddLine("#endif");
1582 1692
1583 return tmp; 1693 return {tmp, Type::Float};
1584 } 1694 }
1585 1695
1586 std::string ImageStore(Operation operation) { 1696 Expression ImageStore(Operation operation) {
1587 constexpr std::array<const char*, 4> constructors{"int(", "ivec2(", "ivec3(", "ivec4("}; 1697 constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("};
1588 const auto meta{std::get<MetaImage>(operation.GetMeta())}; 1698 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1589 1699
1590 std::string expr = "imageStore("; 1700 std::string expr = "imageStore(";
@@ -1594,7 +1704,7 @@ private:
1594 const std::size_t coords_count{operation.GetOperandsCount()}; 1704 const std::size_t coords_count{operation.GetOperandsCount()};
1595 expr += constructors.at(coords_count - 1); 1705 expr += constructors.at(coords_count - 1);
1596 for (std::size_t i = 0; i < coords_count; ++i) { 1706 for (std::size_t i = 0; i < coords_count; ++i) {
1597 expr += VisitOperand(operation, i, Type::Int); 1707 expr += VisitOperand(operation, i).AsInt();
1598 if (i + 1 < coords_count) { 1708 if (i + 1 < coords_count) {
1599 expr += ", "; 1709 expr += ", ";
1600 } 1710 }
@@ -1605,7 +1715,7 @@ private:
1605 UNIMPLEMENTED_IF(values_count != 4); 1715 UNIMPLEMENTED_IF(values_count != 4);
1606 expr += "vec4("; 1716 expr += "vec4(";
1607 for (std::size_t i = 0; i < values_count; ++i) { 1717 for (std::size_t i = 0; i < values_count; ++i) {
1608 expr += Visit(meta.values.at(i)); 1718 expr += Visit(meta.values.at(i)).AsFloat();
1609 if (i + 1 < values_count) { 1719 if (i + 1 < values_count) {
1610 expr += ", "; 1720 expr += ", ";
1611 } 1721 }
@@ -1616,52 +1726,52 @@ private:
1616 return {}; 1726 return {};
1617 } 1727 }
1618 1728
1619 std::string Branch(Operation operation) { 1729 Expression Branch(Operation operation) {
1620 const auto target = std::get_if<ImmediateNode>(&*operation[0]); 1730 const auto target = std::get_if<ImmediateNode>(&*operation[0]);
1621 UNIMPLEMENTED_IF(!target); 1731 UNIMPLEMENTED_IF(!target);
1622 1732
1623 code.AddLine("jmp_to = 0x{:x}u;", target->GetValue()); 1733 code.AddLine("jmp_to = 0x{:X}U;", target->GetValue());
1624 code.AddLine("break;"); 1734 code.AddLine("break;");
1625 return {}; 1735 return {};
1626 } 1736 }
1627 1737
1628 std::string BranchIndirect(Operation operation) { 1738 Expression BranchIndirect(Operation operation) {
1629 const std::string op_a = VisitOperand(operation, 0, Type::Uint); 1739 const std::string op_a = VisitOperand(operation, 0).AsUint();
1630 1740
1631 code.AddLine("jmp_to = {};", op_a); 1741 code.AddLine("jmp_to = {};", op_a);
1632 code.AddLine("break;"); 1742 code.AddLine("break;");
1633 return {}; 1743 return {};
1634 } 1744 }
1635 1745
1636 std::string PushFlowStack(Operation operation) { 1746 Expression PushFlowStack(Operation operation) {
1637 const auto stack = std::get<MetaStackClass>(operation.GetMeta()); 1747 const auto stack = std::get<MetaStackClass>(operation.GetMeta());
1638 const auto target = std::get_if<ImmediateNode>(&*operation[0]); 1748 const auto target = std::get_if<ImmediateNode>(&*operation[0]);
1639 UNIMPLEMENTED_IF(!target); 1749 UNIMPLEMENTED_IF(!target);
1640 1750
1641 code.AddLine("{}[{}++] = 0x{:x}u;", FlowStackName(stack), FlowStackTopName(stack), 1751 code.AddLine("{}[{}++] = 0x{:X}U;", FlowStackName(stack), FlowStackTopName(stack),
1642 target->GetValue()); 1752 target->GetValue());
1643 return {}; 1753 return {};
1644 } 1754 }
1645 1755
1646 std::string PopFlowStack(Operation operation) { 1756 Expression PopFlowStack(Operation operation) {
1647 const auto stack = std::get<MetaStackClass>(operation.GetMeta()); 1757 const auto stack = std::get<MetaStackClass>(operation.GetMeta());
1648 code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack)); 1758 code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack));
1649 code.AddLine("break;"); 1759 code.AddLine("break;");
1650 return {}; 1760 return {};
1651 } 1761 }
1652 1762
1653 std::string Exit(Operation operation) { 1763 Expression Exit(Operation operation) {
1654 if (stage != ProgramType::Fragment) { 1764 if (stage != ProgramType::Fragment) {
1655 code.AddLine("return;"); 1765 code.AddLine("return;");
1656 return {}; 1766 return {};
1657 } 1767 }
1658 const auto& used_registers = ir.GetRegisters(); 1768 const auto& used_registers = ir.GetRegisters();
1659 const auto SafeGetRegister = [&](u32 reg) -> std::string { 1769 const auto SafeGetRegister = [&](u32 reg) -> Expression {
1660 // TODO(Rodrigo): Replace with contains once C++20 releases 1770 // TODO(Rodrigo): Replace with contains once C++20 releases
1661 if (used_registers.find(reg) != used_registers.end()) { 1771 if (used_registers.find(reg) != used_registers.end()) {
1662 return GetRegister(reg); 1772 return {GetRegister(reg), Type::Float};
1663 } 1773 }
1664 return "0.0f"; 1774 return {"0.0f", Type::Float};
1665 }; 1775 };
1666 1776
1667 UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); 1777 UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented");
@@ -1674,7 +1784,7 @@ private:
1674 for (u32 component = 0; component < 4; ++component) { 1784 for (u32 component = 0; component < 4; ++component) {
1675 if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { 1785 if (header.ps.IsColorComponentOutputEnabled(render_target, component)) {
1676 code.AddLine("FragColor{}[{}] = {};", render_target, component, 1786 code.AddLine("FragColor{}[{}] = {};", render_target, component,
1677 SafeGetRegister(current_reg)); 1787 SafeGetRegister(current_reg).AsFloat());
1678 ++current_reg; 1788 ++current_reg;
1679 } 1789 }
1680 } 1790 }
@@ -1683,14 +1793,14 @@ private:
1683 if (header.ps.omap.depth) { 1793 if (header.ps.omap.depth) {
1684 // The depth output is always 2 registers after the last color output, and current_reg 1794 // The depth output is always 2 registers after the last color output, and current_reg
1685 // already contains one past the last color register. 1795 // already contains one past the last color register.
1686 code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1)); 1796 code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat());
1687 } 1797 }
1688 1798
1689 code.AddLine("return;"); 1799 code.AddLine("return;");
1690 return {}; 1800 return {};
1691 } 1801 }
1692 1802
1693 std::string Discard(Operation operation) { 1803 Expression Discard(Operation operation) {
1694 // Enclose "discard" in a conditional, so that GLSL compilation does not complain 1804 // Enclose "discard" in a conditional, so that GLSL compilation does not complain
1695 // about unexecuted instructions that may follow this. 1805 // about unexecuted instructions that may follow this.
1696 code.AddLine("if (true) {{"); 1806 code.AddLine("if (true) {{");
@@ -1701,7 +1811,7 @@ private:
1701 return {}; 1811 return {};
1702 } 1812 }
1703 1813
1704 std::string EmitVertex(Operation operation) { 1814 Expression EmitVertex(Operation operation) {
1705 ASSERT_MSG(stage == ProgramType::Geometry, 1815 ASSERT_MSG(stage == ProgramType::Geometry,
1706 "EmitVertex is expected to be used in a geometry shader."); 1816 "EmitVertex is expected to be used in a geometry shader.");
1707 1817
@@ -1712,7 +1822,7 @@ private:
1712 return {}; 1822 return {};
1713 } 1823 }
1714 1824
1715 std::string EndPrimitive(Operation operation) { 1825 Expression EndPrimitive(Operation operation) {
1716 ASSERT_MSG(stage == ProgramType::Geometry, 1826 ASSERT_MSG(stage == ProgramType::Geometry,
1717 "EndPrimitive is expected to be used in a geometry shader."); 1827 "EndPrimitive is expected to be used in a geometry shader.");
1718 1828
@@ -1720,59 +1830,59 @@ private:
1720 return {}; 1830 return {};
1721 } 1831 }
1722 1832
1723 std::string YNegate(Operation operation) { 1833 Expression YNegate(Operation operation) {
1724 // Config pack's third value is Y_NEGATE's state. 1834 // Config pack's third value is Y_NEGATE's state.
1725 return "uintBitsToFloat(config_pack[2])"; 1835 return {"config_pack[2]", Type::Uint};
1726 } 1836 }
1727 1837
1728 template <u32 element> 1838 template <u32 element>
1729 std::string LocalInvocationId(Operation) { 1839 Expression LocalInvocationId(Operation) {
1730 return "utof(gl_LocalInvocationID"s + GetSwizzle(element) + ')'; 1840 return {"gl_LocalInvocationID"s + GetSwizzle(element), Type::Uint};
1731 } 1841 }
1732 1842
1733 template <u32 element> 1843 template <u32 element>
1734 std::string WorkGroupId(Operation) { 1844 Expression WorkGroupId(Operation) {
1735 return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')'; 1845 return {"gl_WorkGroupID"s + GetSwizzle(element), Type::Uint};
1736 } 1846 }
1737 1847
1738 std::string BallotThread(Operation operation) { 1848 Expression BallotThread(Operation operation) {
1739 const std::string value = VisitOperand(operation, 0, Type::Bool); 1849 const std::string value = VisitOperand(operation, 0).AsBool();
1740 if (!device.HasWarpIntrinsics()) { 1850 if (!device.HasWarpIntrinsics()) {
1741 LOG_ERROR(Render_OpenGL, 1851 LOG_ERROR(Render_OpenGL,
1742 "Nvidia warp intrinsics are not available and its required by a shader"); 1852 "Nvidia warp intrinsics are not available and its required by a shader");
1743 // Stub on non-Nvidia devices by simulating all threads voting the same as the active 1853 // Stub on non-Nvidia devices by simulating all threads voting the same as the active
1744 // one. 1854 // one.
1745 return fmt::format("utof({} ? 0xFFFFFFFFU : 0U)", value); 1855 return {fmt::format("({} ? 0xFFFFFFFFU : 0U)", value), Type::Uint};
1746 } 1856 }
1747 return fmt::format("utof(ballotThreadNV({}))", value); 1857 return {fmt::format("ballotThreadNV({})", value), Type::Uint};
1748 } 1858 }
1749 1859
1750 std::string Vote(Operation operation, const char* func) { 1860 Expression Vote(Operation operation, const char* func) {
1751 const std::string value = VisitOperand(operation, 0, Type::Bool); 1861 const std::string value = VisitOperand(operation, 0).AsBool();
1752 if (!device.HasWarpIntrinsics()) { 1862 if (!device.HasWarpIntrinsics()) {
1753 LOG_ERROR(Render_OpenGL, 1863 LOG_ERROR(Render_OpenGL,
1754 "Nvidia vote intrinsics are not available and its required by a shader"); 1864 "Nvidia vote intrinsics are not available and its required by a shader");
1755 // Stub with a warp size of one. 1865 // Stub with a warp size of one.
1756 return value; 1866 return {value, Type::Bool};
1757 } 1867 }
1758 return fmt::format("{}({})", func, value); 1868 return {fmt::format("{}({})", func, value), Type::Bool};
1759 } 1869 }
1760 1870
1761 std::string VoteAll(Operation operation) { 1871 Expression VoteAll(Operation operation) {
1762 return Vote(operation, "allThreadsNV"); 1872 return Vote(operation, "allThreadsNV");
1763 } 1873 }
1764 1874
1765 std::string VoteAny(Operation operation) { 1875 Expression VoteAny(Operation operation) {
1766 return Vote(operation, "anyThreadNV"); 1876 return Vote(operation, "anyThreadNV");
1767 } 1877 }
1768 1878
1769 std::string VoteEqual(Operation operation) { 1879 Expression VoteEqual(Operation operation) {
1770 if (!device.HasWarpIntrinsics()) { 1880 if (!device.HasWarpIntrinsics()) {
1771 LOG_ERROR(Render_OpenGL, 1881 LOG_ERROR(Render_OpenGL,
1772 "Nvidia vote intrinsics are not available and its required by a shader"); 1882 "Nvidia vote intrinsics are not available and its required by a shader");
1773 // We must return true here since a stub for a theoretical warp size of 1 will always 1883 // We must return true here since a stub for a theoretical warp size of 1 will always
1774 // return an equal result for all its votes. 1884 // return an equal result for all its votes.
1775 return "true"; 1885 return {"true", Type::Bool};
1776 } 1886 }
1777 return Vote(operation, "allThreadsEqualNV"); 1887 return Vote(operation, "allThreadsEqualNV");
1778 } 1888 }
@@ -1973,8 +2083,8 @@ private:
1973 } 2083 }
1974 2084
1975 std::string GetInternalFlag(InternalFlag flag) const { 2085 std::string GetInternalFlag(InternalFlag flag) const {
1976 constexpr std::array<const char*, 4> InternalFlagNames = {"zero_flag", "sign_flag", 2086 constexpr std::array InternalFlagNames = {"zero_flag", "sign_flag", "carry_flag",
1977 "carry_flag", "overflow_flag"}; 2087 "overflow_flag"};
1978 const auto index = static_cast<u32>(flag); 2088 const auto index = static_cast<u32>(flag);
1979 ASSERT(index < static_cast<u32>(InternalFlag::Amount)); 2089 ASSERT(index < static_cast<u32>(InternalFlag::Amount));
1980 2090
@@ -2022,24 +2132,16 @@ private:
2022 2132
2023std::string GetCommonDeclarations() { 2133std::string GetCommonDeclarations() {
2024 return fmt::format( 2134 return fmt::format(
2025 "#define MAX_CONSTBUFFER_ELEMENTS {}\n"
2026 "#define ftoi floatBitsToInt\n" 2135 "#define ftoi floatBitsToInt\n"
2027 "#define ftou floatBitsToUint\n" 2136 "#define ftou floatBitsToUint\n"
2028 "#define itof intBitsToFloat\n" 2137 "#define itof intBitsToFloat\n"
2029 "#define utof uintBitsToFloat\n\n" 2138 "#define utof uintBitsToFloat\n\n"
2030 "float fromHalf2(vec2 pair) {{\n" 2139 "bvec2 HalfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n"
2031 " return utof(packHalf2x16(pair));\n"
2032 "}}\n\n"
2033 "vec2 toHalf2(float value) {{\n"
2034 " return unpackHalf2x16(ftou(value));\n"
2035 "}}\n\n"
2036 "bvec2 halfFloatNanComparison(bvec2 comparison, vec2 pair1, vec2 pair2) {{\n"
2037 " bvec2 is_nan1 = isnan(pair1);\n" 2140 " bvec2 is_nan1 = isnan(pair1);\n"
2038 " bvec2 is_nan2 = isnan(pair2);\n" 2141 " bvec2 is_nan2 = isnan(pair2);\n"
2039 " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || " 2142 " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || "
2040 "is_nan2.y);\n" 2143 "is_nan2.y);\n"
2041 "}}\n", 2144 "}}\n\n");
2042 MAX_CONSTBUFFER_ELEMENTS);
2043} 2145}
2044 2146
2045ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, 2147ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage,
diff --git a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
index 969fe9ced..5450feedf 100644
--- a/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_disk_cache.cpp
@@ -341,13 +341,16 @@ std::optional<ShaderDiskCacheDecompiled> ShaderDiskCacheOpenGL::LoadDecompiledEn
341 u64 index{}; 341 u64 index{};
342 u32 type{}; 342 u32 type{};
343 u8 is_bindless{}; 343 u8 is_bindless{};
344 u8 is_read{};
345 u8 is_written{};
344 if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || 346 if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
345 !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless)) { 347 !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) ||
348 !LoadObjectFromPrecompiled(is_read) || !LoadObjectFromPrecompiled(is_written)) {
346 return {}; 349 return {};
347 } 350 }
348 entry.entries.images.emplace_back( 351 entry.entries.images.emplace_back(static_cast<u64>(offset), static_cast<std::size_t>(index),
349 static_cast<std::size_t>(offset), static_cast<std::size_t>(index), 352 static_cast<Tegra::Shader::ImageType>(type),
350 static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0); 353 is_bindless != 0, is_written != 0, is_read != 0);
351 } 354 }
352 355
353 u32 global_memory_count{}; 356 u32 global_memory_count{};
@@ -429,7 +432,9 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:
429 if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) || 432 if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) ||
430 !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) || 433 !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) ||
431 !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) || 434 !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) ||
432 !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0))) { 435 !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) ||
436 !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) ||
437 !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0))) {
433 return false; 438 return false;
434 } 439 }
435 } 440 }
diff --git a/src/video_core/renderer_opengl/gl_state.cpp b/src/video_core/renderer_opengl/gl_state.cpp
index f4777d0b0..6eabf4fac 100644
--- a/src/video_core/renderer_opengl/gl_state.cpp
+++ b/src/video_core/renderer_opengl/gl_state.cpp
@@ -34,6 +34,25 @@ bool UpdateTie(T1 current_value, const T2 new_value) {
34 return changed; 34 return changed;
35} 35}
36 36
37template <typename T>
38std::optional<std::pair<GLuint, GLsizei>> UpdateArray(T& current_values, const T& new_values) {
39 std::optional<std::size_t> first;
40 std::size_t last;
41 for (std::size_t i = 0; i < std::size(current_values); ++i) {
42 if (!UpdateValue(current_values[i], new_values[i])) {
43 continue;
44 }
45 if (!first) {
46 first = i;
47 }
48 last = i;
49 }
50 if (!first) {
51 return std::nullopt;
52 }
53 return std::make_pair(static_cast<GLuint>(*first), static_cast<GLsizei>(last - *first + 1));
54}
55
37void Enable(GLenum cap, bool enable) { 56void Enable(GLenum cap, bool enable) {
38 if (enable) { 57 if (enable) {
39 glEnable(cap); 58 glEnable(cap);
@@ -134,10 +153,6 @@ OpenGLState::OpenGLState() {
134 logic_op.enabled = false; 153 logic_op.enabled = false;
135 logic_op.operation = GL_COPY; 154 logic_op.operation = GL_COPY;
136 155
137 for (auto& texture_unit : texture_units) {
138 texture_unit.Reset();
139 }
140
141 draw.read_framebuffer = 0; 156 draw.read_framebuffer = 0;
142 draw.draw_framebuffer = 0; 157 draw.draw_framebuffer = 0;
143 draw.vertex_array = 0; 158 draw.vertex_array = 0;
@@ -496,52 +511,20 @@ void OpenGLState::ApplyAlphaTest() const {
496} 511}
497 512
498void OpenGLState::ApplyTextures() const { 513void OpenGLState::ApplyTextures() const {
499 bool has_delta{}; 514 if (const auto update = UpdateArray(cur_state.textures, textures)) {
500 std::size_t first{}; 515 glBindTextures(update->first, update->second, textures.data() + update->first);
501 std::size_t last{};
502 std::array<GLuint, Maxwell::NumTextureSamplers> textures;
503
504 for (std::size_t i = 0; i < std::size(texture_units); ++i) {
505 const auto& texture_unit = texture_units[i];
506 auto& cur_state_texture_unit = cur_state.texture_units[i];
507 textures[i] = texture_unit.texture;
508 if (cur_state_texture_unit.texture == textures[i]) {
509 continue;
510 }
511 cur_state_texture_unit.texture = textures[i];
512 if (!has_delta) {
513 first = i;
514 has_delta = true;
515 }
516 last = i;
517 }
518 if (has_delta) {
519 glBindTextures(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1),
520 textures.data() + first);
521 } 516 }
522} 517}
523 518
524void OpenGLState::ApplySamplers() const { 519void OpenGLState::ApplySamplers() const {
525 bool has_delta{}; 520 if (const auto update = UpdateArray(cur_state.samplers, samplers)) {
526 std::size_t first{}; 521 glBindSamplers(update->first, update->second, samplers.data() + update->first);
527 std::size_t last{};
528 std::array<GLuint, Maxwell::NumTextureSamplers> samplers;
529
530 for (std::size_t i = 0; i < std::size(samplers); ++i) {
531 samplers[i] = texture_units[i].sampler;
532 if (cur_state.texture_units[i].sampler == texture_units[i].sampler) {
533 continue;
534 }
535 cur_state.texture_units[i].sampler = texture_units[i].sampler;
536 if (!has_delta) {
537 first = i;
538 has_delta = true;
539 }
540 last = i;
541 } 522 }
542 if (has_delta) { 523}
543 glBindSamplers(static_cast<GLuint>(first), static_cast<GLsizei>(last - first + 1), 524
544 samplers.data() + first); 525void OpenGLState::ApplyImages() const {
526 if (const auto update = UpdateArray(cur_state.images, images)) {
527 glBindImageTextures(update->first, update->second, images.data() + update->first);
545 } 528 }
546} 529}
547 530
@@ -576,6 +559,7 @@ void OpenGLState::Apply() {
576 ApplyLogicOp(); 559 ApplyLogicOp();
577 ApplyTextures(); 560 ApplyTextures();
578 ApplySamplers(); 561 ApplySamplers();
562 ApplyImages();
579 if (dirty.polygon_offset) { 563 if (dirty.polygon_offset) {
580 ApplyPolygonOffset(); 564 ApplyPolygonOffset();
581 dirty.polygon_offset = false; 565 dirty.polygon_offset = false;
@@ -606,18 +590,18 @@ void OpenGLState::EmulateViewportWithScissor() {
606} 590}
607 591
608OpenGLState& OpenGLState::UnbindTexture(GLuint handle) { 592OpenGLState& OpenGLState::UnbindTexture(GLuint handle) {
609 for (auto& unit : texture_units) { 593 for (auto& texture : textures) {
610 if (unit.texture == handle) { 594 if (texture == handle) {
611 unit.Unbind(); 595 texture = 0;
612 } 596 }
613 } 597 }
614 return *this; 598 return *this;
615} 599}
616 600
617OpenGLState& OpenGLState::ResetSampler(GLuint handle) { 601OpenGLState& OpenGLState::ResetSampler(GLuint handle) {
618 for (auto& unit : texture_units) { 602 for (auto& sampler : samplers) {
619 if (unit.sampler == handle) { 603 if (sampler == handle) {
620 unit.sampler = 0; 604 sampler = 0;
621 } 605 }
622 } 606 }
623 return *this; 607 return *this;
diff --git a/src/video_core/renderer_opengl/gl_state.h b/src/video_core/renderer_opengl/gl_state.h
index fdf9a8a12..949b13051 100644
--- a/src/video_core/renderer_opengl/gl_state.h
+++ b/src/video_core/renderer_opengl/gl_state.h
@@ -118,21 +118,9 @@ public:
118 GLenum operation; 118 GLenum operation;
119 } logic_op; 119 } logic_op;
120 120
121 // 3 texture units - one for each that is used in PICA fragment shader emulation 121 std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> textures{};
122 struct TextureUnit { 122 std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> samplers{};
123 GLuint texture; // GL_TEXTURE_BINDING_2D 123 std::array<GLuint, Tegra::Engines::Maxwell3D::Regs::NumImages> images{};
124 GLuint sampler; // GL_SAMPLER_BINDING
125
126 void Unbind() {
127 texture = 0;
128 }
129
130 void Reset() {
131 Unbind();
132 sampler = 0;
133 }
134 };
135 std::array<TextureUnit, Tegra::Engines::Maxwell3D::Regs::NumTextureSamplers> texture_units;
136 124
137 struct { 125 struct {
138 GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING 126 GLuint read_framebuffer; // GL_READ_FRAMEBUFFER_BINDING
@@ -220,6 +208,7 @@ public:
220 void ApplyLogicOp() const; 208 void ApplyLogicOp() const;
221 void ApplyTextures() const; 209 void ApplyTextures() const;
222 void ApplySamplers() const; 210 void ApplySamplers() const;
211 void ApplyImages() const;
223 void ApplyDepthClamp() const; 212 void ApplyDepthClamp() const;
224 void ApplyPolygonOffset() const; 213 void ApplyPolygonOffset() const;
225 void ApplyAlphaTest() const; 214 void ApplyAlphaTest() const;
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.h b/src/video_core/renderer_opengl/gl_texture_cache.h
index 21324488a..8e13ab38b 100644
--- a/src/video_core/renderer_opengl/gl_texture_cache.h
+++ b/src/video_core/renderer_opengl/gl_texture_cache.h
@@ -78,6 +78,17 @@ public:
78 /// Attaches this texture view to the current bound GL_DRAW_FRAMEBUFFER 78 /// Attaches this texture view to the current bound GL_DRAW_FRAMEBUFFER
79 void Attach(GLenum attachment, GLenum target) const; 79 void Attach(GLenum attachment, GLenum target) const;
80 80
81 void ApplySwizzle(Tegra::Texture::SwizzleSource x_source,
82 Tegra::Texture::SwizzleSource y_source,
83 Tegra::Texture::SwizzleSource z_source,
84 Tegra::Texture::SwizzleSource w_source);
85
86 void DecorateViewName(GPUVAddr gpu_addr, std::string prefix);
87
88 void MarkAsModified(u64 tick) {
89 surface.MarkAsModified(true, tick);
90 }
91
81 GLuint GetTexture() const { 92 GLuint GetTexture() const {
82 if (is_proxy) { 93 if (is_proxy) {
83 return surface.GetTexture(); 94 return surface.GetTexture();
@@ -89,13 +100,6 @@ public:
89 return surface.GetSurfaceParams(); 100 return surface.GetSurfaceParams();
90 } 101 }
91 102
92 void ApplySwizzle(Tegra::Texture::SwizzleSource x_source,
93 Tegra::Texture::SwizzleSource y_source,
94 Tegra::Texture::SwizzleSource z_source,
95 Tegra::Texture::SwizzleSource w_source);
96
97 void DecorateViewName(GPUVAddr gpu_addr, std::string prefix);
98
99private: 103private:
100 u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source, 104 u32 EncodeSwizzle(Tegra::Texture::SwizzleSource x_source,
101 Tegra::Texture::SwizzleSource y_source, 105 Tegra::Texture::SwizzleSource y_source,
@@ -111,8 +115,8 @@ private:
111 GLenum target{}; 115 GLenum target{};
112 116
113 OGLTextureView texture_view; 117 OGLTextureView texture_view;
114 u32 swizzle; 118 u32 swizzle{};
115 bool is_proxy; 119 bool is_proxy{};
116}; 120};
117 121
118class TextureCacheOpenGL final : public TextureCacheBase { 122class TextureCacheOpenGL final : public TextureCacheBase {
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index af9684839..839178152 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -342,7 +342,7 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
342 ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v), 342 ScreenRectVertex(x + w, y + h, texcoords.bottom * scale_u, right * scale_v),
343 }}; 343 }};
344 344
345 state.texture_units[0].texture = screen_info.display_texture; 345 state.textures[0] = screen_info.display_texture;
346 // Workaround brigthness problems in SMO by enabling sRGB in the final output 346 // Workaround brigthness problems in SMO by enabling sRGB in the final output
347 // if it has been used in the frame. Needed because of this bug in QT: QTBUG-50987 347 // if it has been used in the frame. Needed because of this bug in QT: QTBUG-50987
348 state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed(); 348 state.framebuffer_srgb.enabled = OpenGLState::GetsRGBUsed();
@@ -352,7 +352,7 @@ void RendererOpenGL::DrawScreenTriangles(const ScreenInfo& screen_info, float x,
352 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); 352 glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
353 // Restore default state 353 // Restore default state
354 state.framebuffer_srgb.enabled = false; 354 state.framebuffer_srgb.enabled = false;
355 state.texture_units[0].texture = 0; 355 state.textures[0] = 0;
356 state.AllDirty(); 356 state.AllDirty();
357 state.Apply(); 357 state.Apply();
358 // Clear sRGB state for the next frame 358 // Clear sRGB state for the next frame
diff --git a/src/video_core/shader/decode/half_set_predicate.cpp b/src/video_core/shader/decode/half_set_predicate.cpp
index afea33e5f..840694527 100644
--- a/src/video_core/shader/decode/half_set_predicate.cpp
+++ b/src/video_core/shader/decode/half_set_predicate.cpp
@@ -42,9 +42,8 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
42 cond = instr.hsetp2.reg.cond; 42 cond = instr.hsetp2.reg.cond;
43 h_and = instr.hsetp2.reg.h_and; 43 h_and = instr.hsetp2.reg.h_and;
44 op_b = 44 op_b =
45 UnpackHalfFloat(GetOperandAbsNegHalf(GetRegister(instr.gpr20), instr.hsetp2.reg.abs_b, 45 GetOperandAbsNegHalf(UnpackHalfFloat(GetRegister(instr.gpr20), instr.hsetp2.reg.type_b),
46 instr.hsetp2.reg.negate_b), 46 instr.hsetp2.reg.abs_b, instr.hsetp2.reg.negate_b);
47 instr.hsetp2.reg.type_b);
48 break; 47 break;
49 default: 48 default:
50 UNREACHABLE(); 49 UNREACHABLE();
@@ -52,22 +51,22 @@ u32 ShaderIR::DecodeHalfSetPredicate(NodeBlock& bb, u32 pc) {
52 } 51 }
53 52
54 const OperationCode combiner = GetPredicateCombiner(instr.hsetp2.op); 53 const OperationCode combiner = GetPredicateCombiner(instr.hsetp2.op);
55 const Node combined_pred = GetPredicate(instr.hsetp2.pred3, instr.hsetp2.neg_pred); 54 const Node combined_pred = GetPredicate(instr.hsetp2.pred39, instr.hsetp2.neg_pred);
56 55
57 const auto Write = [&](u64 dest, Node src) { 56 const auto Write = [&](u64 dest, Node src) {
58 SetPredicate(bb, dest, Operation(combiner, std::move(src), combined_pred)); 57 SetPredicate(bb, dest, Operation(combiner, std::move(src), combined_pred));
59 }; 58 };
60 59
61 const Node comparison = GetPredicateComparisonHalf(cond, op_a, op_b); 60 const Node comparison = GetPredicateComparisonHalf(cond, op_a, op_b);
62 const u64 first = instr.hsetp2.pred0; 61 const u64 first = instr.hsetp2.pred3;
63 const u64 second = instr.hsetp2.pred39; 62 const u64 second = instr.hsetp2.pred0;
64 if (h_and) { 63 if (h_and) {
65 const Node joined = Operation(OperationCode::LogicalAnd2, comparison); 64 Node joined = Operation(OperationCode::LogicalAnd2, comparison);
66 Write(first, joined); 65 Write(first, joined);
67 Write(second, Operation(OperationCode::LogicalNegate, joined)); 66 Write(second, Operation(OperationCode::LogicalNegate, std::move(joined)));
68 } else { 67 } else {
69 Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0u))); 68 Write(first, Operation(OperationCode::LogicalPick2, comparison, Immediate(0U)));
70 Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1u))); 69 Write(second, Operation(OperationCode::LogicalPick2, comparison, Immediate(1U)));
71 } 70 }
72 71
73 return pc; 72 return pc;
diff --git a/src/video_core/shader/decode/image.cpp b/src/video_core/shader/decode/image.cpp
index 77151a24b..008109a99 100644
--- a/src/video_core/shader/decode/image.cpp
+++ b/src/video_core/shader/decode/image.cpp
@@ -61,56 +61,54 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
61 } 61 }
62 62
63 const auto type{instr.sust.image_type}; 63 const auto type{instr.sust.image_type};
64 const auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) 64 auto& image{instr.sust.is_immediate ? GetImage(instr.image, type)
65 : GetBindlessImage(instr.gpr39, type)}; 65 : GetBindlessImage(instr.gpr39, type)};
66 image.MarkWrite();
67
66 MetaImage meta{image, values}; 68 MetaImage meta{image, values};
67 const Node store{Operation(OperationCode::ImageStore, meta, std::move(coords))}; 69 const Node store{Operation(OperationCode::ImageStore, meta, std::move(coords))};
68 bb.push_back(store); 70 bb.push_back(store);
69 break; 71 break;
70 } 72 }
71 default: 73 default:
72 UNIMPLEMENTED_MSG("Unhandled conversion instruction: {}", opcode->get().GetName()); 74 UNIMPLEMENTED_MSG("Unhandled image instruction: {}", opcode->get().GetName());
73 } 75 }
74 76
75 return pc; 77 return pc;
76} 78}
77 79
78const Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { 80Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) {
79 const auto offset{static_cast<std::size_t>(image.index.Value())}; 81 const auto offset{static_cast<u64>(image.index.Value())};
80 82
81 // If this image has already been used, return the existing mapping. 83 // If this image has already been used, return the existing mapping.
82 const auto itr{std::find_if(used_images.begin(), used_images.end(), 84 const auto it = used_images.find(offset);
83 [=](const Image& entry) { return entry.GetOffset() == offset; })}; 85 if (it != used_images.end()) {
84 if (itr != used_images.end()) { 86 ASSERT(it->second.GetType() == type);
85 ASSERT(itr->GetType() == type); 87 return it->second;
86 return *itr;
87 } 88 }
88 89
89 // Otherwise create a new mapping for this image. 90 // Otherwise create a new mapping for this image.
90 const std::size_t next_index{used_images.size()}; 91 const std::size_t next_index{used_images.size()};
91 const Image entry{offset, next_index, type}; 92 return used_images.emplace(offset, Image{offset, next_index, type}).first->second;
92 return *used_images.emplace(entry).first;
93} 93}
94 94
95const Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, 95Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type) {
96 Tegra::Shader::ImageType type) {
97 const Node image_register{GetRegister(reg)}; 96 const Node image_register{GetRegister(reg)};
98 const auto [base_image, cbuf_index, cbuf_offset]{ 97 const auto [base_image, cbuf_index, cbuf_offset]{
99 TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))}; 98 TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))};
100 const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)}; 99 const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)};
101 100
102 // If this image has already been used, return the existing mapping. 101 // If this image has already been used, return the existing mapping.
103 const auto itr{std::find_if(used_images.begin(), used_images.end(), 102 const auto it = used_images.find(cbuf_key);
104 [=](const Image& entry) { return entry.GetOffset() == cbuf_key; })}; 103 if (it != used_images.end()) {
105 if (itr != used_images.end()) { 104 ASSERT(it->second.GetType() == type);
106 ASSERT(itr->GetType() == type); 105 return it->second;
107 return *itr;
108 } 106 }
109 107
110 // Otherwise create a new mapping for this image. 108 // Otherwise create a new mapping for this image.
111 const std::size_t next_index{used_images.size()}; 109 const std::size_t next_index{used_images.size()};
112 const Image entry{cbuf_index, cbuf_offset, next_index, type}; 110 return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type})
113 return *used_images.emplace(entry).first; 111 .first->second;
114} 112}
115 113
116} // namespace VideoCommon::Shader 114} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index 5db9313c4..b29aedce8 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -273,46 +273,64 @@ private:
273 bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. 273 bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not.
274}; 274};
275 275
276class Image { 276class Image final {
277public: 277public:
278 explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type) 278 constexpr explicit Image(u64 offset, std::size_t index, Tegra::Shader::ImageType type)
279 : offset{offset}, index{index}, type{type}, is_bindless{false} {} 279 : offset{offset}, index{index}, type{type}, is_bindless{false} {}
280 280
281 explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, 281 constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index,
282 Tegra::Shader::ImageType type) 282 Tegra::Shader::ImageType type)
283 : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, 283 : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type},
284 is_bindless{true} {} 284 is_bindless{true} {}
285 285
286 explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, 286 constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type,
287 bool is_bindless) 287 bool is_bindless, bool is_written, bool is_read)
288 : offset{offset}, index{index}, type{type}, is_bindless{is_bindless} {} 288 : offset{offset}, index{index}, type{type}, is_bindless{is_bindless},
289 is_written{is_written}, is_read{is_read} {}
289 290
290 std::size_t GetOffset() const { 291 void MarkRead() {
292 is_read = true;
293 }
294
295 void MarkWrite() {
296 is_written = true;
297 }
298
299 constexpr std::size_t GetOffset() const {
291 return offset; 300 return offset;
292 } 301 }
293 302
294 std::size_t GetIndex() const { 303 constexpr std::size_t GetIndex() const {
295 return index; 304 return index;
296 } 305 }
297 306
298 Tegra::Shader::ImageType GetType() const { 307 constexpr Tegra::Shader::ImageType GetType() const {
299 return type; 308 return type;
300 } 309 }
301 310
302 bool IsBindless() const { 311 constexpr bool IsBindless() const {
303 return is_bindless; 312 return is_bindless;
304 } 313 }
305 314
306 bool operator<(const Image& rhs) const { 315 constexpr bool IsRead() const {
307 return std::tie(offset, index, type, is_bindless) < 316 return is_read;
308 std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless); 317 }
318
319 constexpr bool IsWritten() const {
320 return is_written;
321 }
322
323 constexpr std::pair<u32, u32> GetBindlessCBuf() const {
324 return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};
309 } 325 }
310 326
311private: 327private:
312 std::size_t offset{}; 328 u64 offset{};
313 std::size_t index{}; 329 std::size_t index{};
314 Tegra::Shader::ImageType type{}; 330 Tegra::Shader::ImageType type{};
315 bool is_bindless{}; 331 bool is_bindless{};
332 bool is_read{};
333 bool is_written{};
316}; 334};
317 335
318struct GlobalMemoryBase { 336struct GlobalMemoryBase {
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index bcc9b79b6..0f891eace 100644
--- a/src/video_core/shader/shader_ir.h
+++ b/src/video_core/shader/shader_ir.h
@@ -95,7 +95,7 @@ public:
95 return used_samplers; 95 return used_samplers;
96 } 96 }
97 97
98 const std::set<Image>& GetImages() const { 98 const std::map<u64, Image>& GetImages() const {
99 return used_images; 99 return used_images;
100 } 100 }
101 101
@@ -272,10 +272,10 @@ private:
272 bool is_shadow); 272 bool is_shadow);
273 273
274 /// Accesses an image. 274 /// Accesses an image.
275 const Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type); 275 Image& GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type);
276 276
277 /// Access a bindless image sampler. 277 /// Access a bindless image sampler.
278 const Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); 278 Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type);
279 279
280 /// Extracts a sequence of bits from a node 280 /// Extracts a sequence of bits from a node
281 Node BitfieldExtract(Node value, u32 offset, u32 bits); 281 Node BitfieldExtract(Node value, u32 offset, u32 bits);
@@ -356,7 +356,7 @@ private:
356 std::set<Tegra::Shader::Attribute::Index> used_output_attributes; 356 std::set<Tegra::Shader::Attribute::Index> used_output_attributes;
357 std::map<u32, ConstBuffer> used_cbufs; 357 std::map<u32, ConstBuffer> used_cbufs;
358 std::set<Sampler> used_samplers; 358 std::set<Sampler> used_samplers;
359 std::set<Image> used_images; 359 std::map<u64, Image> used_images;
360 std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{}; 360 std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{};
361 std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory; 361 std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory;
362 bool uses_layer{}; 362 bool uses_layer{};
diff --git a/src/video_core/texture_cache/surface_base.h b/src/video_core/texture_cache/surface_base.h
index bcce8d863..5e497e49f 100644
--- a/src/video_core/texture_cache/surface_base.h
+++ b/src/video_core/texture_cache/surface_base.h
@@ -195,18 +195,18 @@ public:
195 195
196 virtual void DownloadTexture(std::vector<u8>& staging_buffer) = 0; 196 virtual void DownloadTexture(std::vector<u8>& staging_buffer) = 0;
197 197
198 void MarkAsModified(const bool is_modified_, const u64 tick) { 198 void MarkAsModified(bool is_modified_, u64 tick) {
199 is_modified = is_modified_ || is_target; 199 is_modified = is_modified_ || is_target;
200 modification_tick = tick; 200 modification_tick = tick;
201 } 201 }
202 202
203 void MarkAsRenderTarget(const bool is_target, const u32 index) { 203 void MarkAsRenderTarget(bool is_target_, u32 index_) {
204 this->is_target = is_target; 204 is_target = is_target_;
205 this->index = index; 205 index = index_;
206 } 206 }
207 207
208 void MarkAsPicked(const bool is_picked) { 208 void MarkAsPicked(bool is_picked_) {
209 this->is_picked = is_picked; 209 is_picked = is_picked_;
210 } 210 }
211 211
212 bool IsModified() const { 212 bool IsModified() const {
diff --git a/src/video_core/texture_cache/surface_params.cpp b/src/video_core/texture_cache/surface_params.cpp
index fd5472451..1e4d3fb79 100644
--- a/src/video_core/texture_cache/surface_params.cpp
+++ b/src/video_core/texture_cache/surface_params.cpp
@@ -24,55 +24,62 @@ using VideoCore::Surface::SurfaceTarget;
24using VideoCore::Surface::SurfaceTargetFromTextureType; 24using VideoCore::Surface::SurfaceTargetFromTextureType;
25using VideoCore::Surface::SurfaceType; 25using VideoCore::Surface::SurfaceType;
26 26
27SurfaceTarget TextureType2SurfaceTarget(Tegra::Shader::TextureType type, bool is_array) { 27namespace {
28
29SurfaceTarget TextureTypeToSurfaceTarget(Tegra::Shader::TextureType type, bool is_array) {
28 switch (type) { 30 switch (type) {
29 case Tegra::Shader::TextureType::Texture1D: { 31 case Tegra::Shader::TextureType::Texture1D:
30 if (is_array) 32 return is_array ? SurfaceTarget::Texture1DArray : SurfaceTarget::Texture1D;
31 return SurfaceTarget::Texture1DArray; 33 case Tegra::Shader::TextureType::Texture2D:
32 else 34 return is_array ? SurfaceTarget::Texture2DArray : SurfaceTarget::Texture2D;
33 return SurfaceTarget::Texture1D; 35 case Tegra::Shader::TextureType::Texture3D:
34 }
35 case Tegra::Shader::TextureType::Texture2D: {
36 if (is_array)
37 return SurfaceTarget::Texture2DArray;
38 else
39 return SurfaceTarget::Texture2D;
40 }
41 case Tegra::Shader::TextureType::Texture3D: {
42 ASSERT(!is_array); 36 ASSERT(!is_array);
43 return SurfaceTarget::Texture3D; 37 return SurfaceTarget::Texture3D;
44 } 38 case Tegra::Shader::TextureType::TextureCube:
45 case Tegra::Shader::TextureType::TextureCube: { 39 return is_array ? SurfaceTarget::TextureCubeArray : SurfaceTarget::TextureCubemap;
46 if (is_array) 40 default:
47 return SurfaceTarget::TextureCubeArray;
48 else
49 return SurfaceTarget::TextureCubemap;
50 }
51 default: {
52 UNREACHABLE(); 41 UNREACHABLE();
53 return SurfaceTarget::Texture2D; 42 return SurfaceTarget::Texture2D;
54 } 43 }
44}
45
46SurfaceTarget ImageTypeToSurfaceTarget(Tegra::Shader::ImageType type) {
47 switch (type) {
48 case Tegra::Shader::ImageType::Texture1D:
49 return SurfaceTarget::Texture1D;
50 case Tegra::Shader::ImageType::TextureBuffer:
51 return SurfaceTarget::TextureBuffer;
52 case Tegra::Shader::ImageType::Texture1DArray:
53 return SurfaceTarget::Texture1DArray;
54 case Tegra::Shader::ImageType::Texture2D:
55 return SurfaceTarget::Texture2D;
56 case Tegra::Shader::ImageType::Texture2DArray:
57 return SurfaceTarget::Texture2DArray;
58 case Tegra::Shader::ImageType::Texture3D:
59 return SurfaceTarget::Texture3D;
60 default:
61 UNREACHABLE();
62 return SurfaceTarget::Texture2D;
55 } 63 }
56} 64}
57 65
58namespace {
59constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) { 66constexpr u32 GetMipmapSize(bool uncompressed, u32 mip_size, u32 tile) {
60 return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile); 67 return uncompressed ? mip_size : std::max(1U, (mip_size + tile - 1) / tile);
61} 68}
69
62} // Anonymous namespace 70} // Anonymous namespace
63 71
64SurfaceParams SurfaceParams::CreateForTexture(Core::System& system, 72SurfaceParams SurfaceParams::CreateForTexture(const Tegra::Texture::TICEntry& tic,
65 const Tegra::Texture::FullTextureInfo& config,
66 const VideoCommon::Shader::Sampler& entry) { 73 const VideoCommon::Shader::Sampler& entry) {
67 SurfaceParams params; 74 SurfaceParams params;
68 params.is_tiled = config.tic.IsTiled(); 75 params.is_tiled = tic.IsTiled();
69 params.srgb_conversion = config.tic.IsSrgbConversionEnabled(); 76 params.srgb_conversion = tic.IsSrgbConversionEnabled();
70 params.block_width = params.is_tiled ? config.tic.BlockWidth() : 0, 77 params.block_width = params.is_tiled ? tic.BlockWidth() : 0,
71 params.block_height = params.is_tiled ? config.tic.BlockHeight() : 0, 78 params.block_height = params.is_tiled ? tic.BlockHeight() : 0,
72 params.block_depth = params.is_tiled ? config.tic.BlockDepth() : 0, 79 params.block_depth = params.is_tiled ? tic.BlockDepth() : 0,
73 params.tile_width_spacing = params.is_tiled ? (1 << config.tic.tile_width_spacing.Value()) : 1; 80 params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
74 params.pixel_format = PixelFormatFromTextureFormat(config.tic.format, config.tic.r_type.Value(), 81 params.pixel_format =
75 params.srgb_conversion); 82 PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion);
76 params.type = GetFormatType(params.pixel_format); 83 params.type = GetFormatType(params.pixel_format);
77 if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) { 84 if (entry.IsShadow() && params.type == SurfaceType::ColorTexture) {
78 switch (params.pixel_format) { 85 switch (params.pixel_format) {
@@ -92,31 +99,72 @@ SurfaceParams SurfaceParams::CreateForTexture(Core::System& system,
92 } 99 }
93 params.type = GetFormatType(params.pixel_format); 100 params.type = GetFormatType(params.pixel_format);
94 } 101 }
95 params.component_type = ComponentTypeFromTexture(config.tic.r_type.Value()); 102 params.component_type = ComponentTypeFromTexture(tic.r_type.Value());
96 params.type = GetFormatType(params.pixel_format); 103 params.type = GetFormatType(params.pixel_format);
97 // TODO: on 1DBuffer we should use the tic info. 104 // TODO: on 1DBuffer we should use the tic info.
98 if (!config.tic.IsBuffer()) { 105 if (tic.IsBuffer()) {
99 params.target = TextureType2SurfaceTarget(entry.GetType(), entry.IsArray()); 106 params.target = SurfaceTarget::TextureBuffer;
100 params.width = config.tic.Width(); 107 params.width = tic.Width();
101 params.height = config.tic.Height(); 108 params.pitch = params.width * params.GetBytesPerPixel();
102 params.depth = config.tic.Depth(); 109 params.height = 1;
103 params.pitch = params.is_tiled ? 0 : config.tic.Pitch(); 110 params.depth = 1;
111 params.num_levels = 1;
112 params.emulated_levels = 1;
113 params.is_layered = false;
114 } else {
115 params.target = TextureTypeToSurfaceTarget(entry.GetType(), entry.IsArray());
116 params.width = tic.Width();
117 params.height = tic.Height();
118 params.depth = tic.Depth();
119 params.pitch = params.is_tiled ? 0 : tic.Pitch();
104 if (params.target == SurfaceTarget::TextureCubemap || 120 if (params.target == SurfaceTarget::TextureCubemap ||
105 params.target == SurfaceTarget::TextureCubeArray) { 121 params.target == SurfaceTarget::TextureCubeArray) {
106 params.depth *= 6; 122 params.depth *= 6;
107 } 123 }
108 params.num_levels = config.tic.max_mip_level + 1; 124 params.num_levels = tic.max_mip_level + 1;
109 params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap()); 125 params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap());
110 params.is_layered = params.IsLayered(); 126 params.is_layered = params.IsLayered();
111 } else { 127 }
128 return params;
129}
130
131SurfaceParams SurfaceParams::CreateForImage(const Tegra::Texture::TICEntry& tic,
132 const VideoCommon::Shader::Image& entry) {
133 SurfaceParams params;
134 params.is_tiled = tic.IsTiled();
135 params.srgb_conversion = tic.IsSrgbConversionEnabled();
136 params.block_width = params.is_tiled ? tic.BlockWidth() : 0,
137 params.block_height = params.is_tiled ? tic.BlockHeight() : 0,
138 params.block_depth = params.is_tiled ? tic.BlockDepth() : 0,
139 params.tile_width_spacing = params.is_tiled ? (1 << tic.tile_width_spacing.Value()) : 1;
140 params.pixel_format =
141 PixelFormatFromTextureFormat(tic.format, tic.r_type.Value(), params.srgb_conversion);
142 params.type = GetFormatType(params.pixel_format);
143 params.component_type = ComponentTypeFromTexture(tic.r_type.Value());
144 params.type = GetFormatType(params.pixel_format);
145 params.target = ImageTypeToSurfaceTarget(entry.GetType());
146 // TODO: on 1DBuffer we should use the tic info.
147 if (tic.IsBuffer()) {
112 params.target = SurfaceTarget::TextureBuffer; 148 params.target = SurfaceTarget::TextureBuffer;
113 params.width = config.tic.Width(); 149 params.width = tic.Width();
114 params.pitch = params.width * params.GetBytesPerPixel(); 150 params.pitch = params.width * params.GetBytesPerPixel();
115 params.height = 1; 151 params.height = 1;
116 params.depth = 1; 152 params.depth = 1;
117 params.num_levels = 1; 153 params.num_levels = 1;
118 params.emulated_levels = 1; 154 params.emulated_levels = 1;
119 params.is_layered = false; 155 params.is_layered = false;
156 } else {
157 params.width = tic.Width();
158 params.height = tic.Height();
159 params.depth = tic.Depth();
160 params.pitch = params.is_tiled ? 0 : tic.Pitch();
161 if (params.target == SurfaceTarget::TextureCubemap ||
162 params.target == SurfaceTarget::TextureCubeArray) {
163 params.depth *= 6;
164 }
165 params.num_levels = tic.max_mip_level + 1;
166 params.emulated_levels = std::min(params.num_levels, params.MaxPossibleMipmap());
167 params.is_layered = params.IsLayered();
120 } 168 }
121 return params; 169 return params;
122} 170}
diff --git a/src/video_core/texture_cache/surface_params.h b/src/video_core/texture_cache/surface_params.h
index e7ef66ee2..c58e7f8a4 100644
--- a/src/video_core/texture_cache/surface_params.h
+++ b/src/video_core/texture_cache/surface_params.h
@@ -4,8 +4,6 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <map>
8
9#include "common/alignment.h" 7#include "common/alignment.h"
10#include "common/bit_util.h" 8#include "common/bit_util.h"
11#include "common/cityhash.h" 9#include "common/cityhash.h"
@@ -23,10 +21,13 @@ using VideoCore::Surface::SurfaceCompression;
23class SurfaceParams { 21class SurfaceParams {
24public: 22public:
25 /// Creates SurfaceCachedParams from a texture configuration. 23 /// Creates SurfaceCachedParams from a texture configuration.
26 static SurfaceParams CreateForTexture(Core::System& system, 24 static SurfaceParams CreateForTexture(const Tegra::Texture::TICEntry& tic,
27 const Tegra::Texture::FullTextureInfo& config,
28 const VideoCommon::Shader::Sampler& entry); 25 const VideoCommon::Shader::Sampler& entry);
29 26
27 /// Creates SurfaceCachedParams from an image configuration.
28 static SurfaceParams CreateForImage(const Tegra::Texture::TICEntry& tic,
29 const VideoCommon::Shader::Image& entry);
30
30 /// Creates SurfaceCachedParams for a depth buffer configuration. 31 /// Creates SurfaceCachedParams for a depth buffer configuration.
31 static SurfaceParams CreateForDepthBuffer( 32 static SurfaceParams CreateForDepthBuffer(
32 Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format, 33 Core::System& system, u32 zeta_width, u32 zeta_height, Tegra::DepthFormat format,
diff --git a/src/video_core/texture_cache/surface_view.cpp b/src/video_core/texture_cache/surface_view.cpp
index 467696a4c..57a1f5803 100644
--- a/src/video_core/texture_cache/surface_view.cpp
+++ b/src/video_core/texture_cache/surface_view.cpp
@@ -10,7 +10,7 @@
10namespace VideoCommon { 10namespace VideoCommon {
11 11
12std::size_t ViewParams::Hash() const { 12std::size_t ViewParams::Hash() const {
13 return static_cast<std::size_t>(base_layer) ^ static_cast<std::size_t>(num_layers << 16) ^ 13 return static_cast<std::size_t>(base_layer) ^ (static_cast<std::size_t>(num_layers) << 16) ^
14 (static_cast<std::size_t>(base_level) << 24) ^ 14 (static_cast<std::size_t>(base_level) << 24) ^
15 (static_cast<std::size_t>(num_levels) << 32) ^ (static_cast<std::size_t>(target) << 36); 15 (static_cast<std::size_t>(num_levels) << 32) ^ (static_cast<std::size_t>(target) << 36);
16} 16}
diff --git a/src/video_core/texture_cache/surface_view.h b/src/video_core/texture_cache/surface_view.h
index 04ca5639b..b17fd11a9 100644
--- a/src/video_core/texture_cache/surface_view.h
+++ b/src/video_core/texture_cache/surface_view.h
@@ -13,8 +13,8 @@
13namespace VideoCommon { 13namespace VideoCommon {
14 14
15struct ViewParams { 15struct ViewParams {
16 ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer, u32 num_layers, 16 constexpr explicit ViewParams(VideoCore::Surface::SurfaceTarget target, u32 base_layer,
17 u32 base_level, u32 num_levels) 17 u32 num_layers, u32 base_level, u32 num_levels)
18 : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level}, 18 : target{target}, base_layer{base_layer}, num_layers{num_layers}, base_level{base_level},
19 num_levels{num_levels} {} 19 num_levels{num_levels} {}
20 20
@@ -22,12 +22,6 @@ struct ViewParams {
22 22
23 bool operator==(const ViewParams& rhs) const; 23 bool operator==(const ViewParams& rhs) const;
24 24
25 VideoCore::Surface::SurfaceTarget target{};
26 u32 base_layer{};
27 u32 num_layers{};
28 u32 base_level{};
29 u32 num_levels{};
30
31 bool IsLayered() const { 25 bool IsLayered() const {
32 switch (target) { 26 switch (target) {
33 case VideoCore::Surface::SurfaceTarget::Texture1DArray: 27 case VideoCore::Surface::SurfaceTarget::Texture1DArray:
@@ -39,13 +33,19 @@ struct ViewParams {
39 return false; 33 return false;
40 } 34 }
41 } 35 }
36
37 VideoCore::Surface::SurfaceTarget target{};
38 u32 base_layer{};
39 u32 num_layers{};
40 u32 base_level{};
41 u32 num_levels{};
42}; 42};
43 43
44class ViewBase { 44class ViewBase {
45public: 45public:
46 ViewBase(const ViewParams& params) : params{params} {} 46 constexpr explicit ViewBase(const ViewParams& params) : params{params} {}
47 47
48 const ViewParams& GetViewParams() const { 48 constexpr const ViewParams& GetViewParams() const {
49 return params; 49 return params;
50 } 50 }
51 51
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 2ec0203d1..877c6635d 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -89,14 +89,29 @@ public:
89 } 89 }
90 } 90 }
91 91
92 TView GetTextureSurface(const Tegra::Texture::FullTextureInfo& config, 92 TView GetTextureSurface(const Tegra::Texture::TICEntry& tic,
93 const VideoCommon::Shader::Sampler& entry) { 93 const VideoCommon::Shader::Sampler& entry) {
94 std::lock_guard lock{mutex}; 94 std::lock_guard lock{mutex};
95 const auto gpu_addr{config.tic.Address()}; 95 const auto gpu_addr{tic.Address()};
96 if (!gpu_addr) { 96 if (!gpu_addr) {
97 return {}; 97 return {};
98 } 98 }
99 const auto params{SurfaceParams::CreateForTexture(system, config, entry)}; 99 const auto params{SurfaceParams::CreateForTexture(tic, entry)};
100 const auto [surface, view] = GetSurface(gpu_addr, params, true, false);
101 if (guard_samplers) {
102 sampled_textures.push_back(surface);
103 }
104 return view;
105 }
106
107 TView GetImageSurface(const Tegra::Texture::TICEntry& tic,
108 const VideoCommon::Shader::Image& entry) {
109 std::lock_guard lock{mutex};
110 const auto gpu_addr{tic.Address()};
111 if (!gpu_addr) {
112 return {};
113 }
114 const auto params{SurfaceParams::CreateForImage(tic, entry)};
100 const auto [surface, view] = GetSurface(gpu_addr, params, true, false); 115 const auto [surface, view] = GetSurface(gpu_addr, params, true, false);
101 if (guard_samplers) { 116 if (guard_samplers) {
102 sampled_textures.push_back(surface); 117 sampled_textures.push_back(surface);
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 0456248ac..f594106bf 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -517,10 +517,37 @@ void Config::ReadPathValues() {
517 UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString(); 517 UISettings::values.roms_path = ReadSetting(QStringLiteral("romsPath")).toString();
518 UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString(); 518 UISettings::values.symbols_path = ReadSetting(QStringLiteral("symbolsPath")).toString();
519 UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString(); 519 UISettings::values.screenshot_path = ReadSetting(QStringLiteral("screenshotPath")).toString();
520 UISettings::values.game_directory_path = 520 UISettings::values.game_dir_deprecated =
521 ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString(); 521 ReadSetting(QStringLiteral("gameListRootDir"), QStringLiteral(".")).toString();
522 UISettings::values.game_directory_deepscan = 522 UISettings::values.game_dir_deprecated_deepscan =
523 ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool(); 523 ReadSetting(QStringLiteral("gameListDeepScan"), false).toBool();
524 const int gamedirs_size = qt_config->beginReadArray(QStringLiteral("gamedirs"));
525 for (int i = 0; i < gamedirs_size; ++i) {
526 qt_config->setArrayIndex(i);
527 UISettings::GameDir game_dir;
528 game_dir.path = ReadSetting(QStringLiteral("path")).toString();
529 game_dir.deep_scan = ReadSetting(QStringLiteral("deep_scan"), false).toBool();
530 game_dir.expanded = ReadSetting(QStringLiteral("expanded"), true).toBool();
531 UISettings::values.game_dirs.append(game_dir);
532 }
533 qt_config->endArray();
534 // create NAND and SD card directories if empty, these are not removable through the UI,
535 // also carries over old game list settings if present
536 if (UISettings::values.game_dirs.isEmpty()) {
537 UISettings::GameDir game_dir;
538 game_dir.path = QStringLiteral("SDMC");
539 game_dir.expanded = true;
540 UISettings::values.game_dirs.append(game_dir);
541 game_dir.path = QStringLiteral("UserNAND");
542 UISettings::values.game_dirs.append(game_dir);
543 game_dir.path = QStringLiteral("SysNAND");
544 UISettings::values.game_dirs.append(game_dir);
545 if (UISettings::values.game_dir_deprecated != QStringLiteral(".")) {
546 game_dir.path = UISettings::values.game_dir_deprecated;
547 game_dir.deep_scan = UISettings::values.game_dir_deprecated_deepscan;
548 UISettings::values.game_dirs.append(game_dir);
549 }
550 }
524 UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList(); 551 UISettings::values.recent_files = ReadSetting(QStringLiteral("recentFiles")).toStringList();
525 552
526 qt_config->endGroup(); 553 qt_config->endGroup();
@@ -899,10 +926,15 @@ void Config::SavePathValues() {
899 WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path); 926 WriteSetting(QStringLiteral("romsPath"), UISettings::values.roms_path);
900 WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path); 927 WriteSetting(QStringLiteral("symbolsPath"), UISettings::values.symbols_path);
901 WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path); 928 WriteSetting(QStringLiteral("screenshotPath"), UISettings::values.screenshot_path);
902 WriteSetting(QStringLiteral("gameListRootDir"), UISettings::values.game_directory_path, 929 qt_config->beginWriteArray(QStringLiteral("gamedirs"));
903 QStringLiteral(".")); 930 for (int i = 0; i < UISettings::values.game_dirs.size(); ++i) {
904 WriteSetting(QStringLiteral("gameListDeepScan"), UISettings::values.game_directory_deepscan, 931 qt_config->setArrayIndex(i);
905 false); 932 const auto& game_dir = UISettings::values.game_dirs[i];
933 WriteSetting(QStringLiteral("path"), game_dir.path);
934 WriteSetting(QStringLiteral("deep_scan"), game_dir.deep_scan, false);
935 WriteSetting(QStringLiteral("expanded"), game_dir.expanded, true);
936 }
937 qt_config->endArray();
906 WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files); 938 WriteSetting(QStringLiteral("recentFiles"), UISettings::values.recent_files);
907 939
908 qt_config->endGroup(); 940 qt_config->endGroup();
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp
index e636964e3..775e3f2ea 100644
--- a/src/yuzu/configuration/configure_dialog.cpp
+++ b/src/yuzu/configuration/configure_dialog.cpp
@@ -68,12 +68,14 @@ void ConfigureDialog::RetranslateUI() {
68 ui->tabWidget->setCurrentIndex(old_index); 68 ui->tabWidget->setCurrentIndex(old_index);
69} 69}
70 70
71Q_DECLARE_METATYPE(QList<QWidget*>);
72
71void ConfigureDialog::PopulateSelectionList() { 73void ConfigureDialog::PopulateSelectionList() {
72 const std::array<std::pair<QString, QStringList>, 4> items{ 74 const std::array<std::pair<QString, QList<QWidget*>>, 4> items{
73 {{tr("General"), {tr("General"), tr("Web"), tr("Debug"), tr("Game List")}}, 75 {{tr("General"), {ui->generalTab, ui->webTab, ui->debugTab, ui->gameListTab}},
74 {tr("System"), {tr("System"), tr("Profiles"), tr("Audio")}}, 76 {tr("System"), {ui->systemTab, ui->profileManagerTab, ui->audioTab}},
75 {tr("Graphics"), {tr("Graphics")}}, 77 {tr("Graphics"), {ui->graphicsTab}},
76 {tr("Controls"), {tr("Input"), tr("Hotkeys")}}}, 78 {tr("Controls"), {ui->inputTab, ui->hotkeysTab}}},
77 }; 79 };
78 80
79 [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList); 81 [[maybe_unused]] const QSignalBlocker blocker(ui->selectorList);
@@ -81,7 +83,7 @@ void ConfigureDialog::PopulateSelectionList() {
81 ui->selectorList->clear(); 83 ui->selectorList->clear();
82 for (const auto& entry : items) { 84 for (const auto& entry : items) {
83 auto* const item = new QListWidgetItem(entry.first); 85 auto* const item = new QListWidgetItem(entry.first);
84 item->setData(Qt::UserRole, entry.second); 86 item->setData(Qt::UserRole, QVariant::fromValue(entry.second));
85 87
86 ui->selectorList->addItem(item); 88 ui->selectorList->addItem(item);
87 } 89 }
@@ -93,24 +95,26 @@ void ConfigureDialog::UpdateVisibleTabs() {
93 return; 95 return;
94 } 96 }
95 97
96 const std::map<QString, QWidget*> widgets = { 98 const std::map<QWidget*, QString> widgets = {
97 {tr("General"), ui->generalTab}, 99 {ui->generalTab, tr("General")},
98 {tr("System"), ui->systemTab}, 100 {ui->systemTab, tr("System")},
99 {tr("Profiles"), ui->profileManagerTab}, 101 {ui->profileManagerTab, tr("Profiles")},
100 {tr("Input"), ui->inputTab}, 102 {ui->inputTab, tr("Input")},
101 {tr("Hotkeys"), ui->hotkeysTab}, 103 {ui->hotkeysTab, tr("Hotkeys")},
102 {tr("Graphics"), ui->graphicsTab}, 104 {ui->graphicsTab, tr("Graphics")},
103 {tr("Audio"), ui->audioTab}, 105 {ui->audioTab, tr("Audio")},
104 {tr("Debug"), ui->debugTab}, 106 {ui->debugTab, tr("Debug")},
105 {tr("Web"), ui->webTab}, 107 {ui->webTab, tr("Web")},
106 {tr("Game List"), ui->gameListTab}, 108 {ui->gameListTab, tr("Game List")},
107 }; 109 };
108 110
109 [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget); 111 [[maybe_unused]] const QSignalBlocker blocker(ui->tabWidget);
110 112
111 ui->tabWidget->clear(); 113 ui->tabWidget->clear();
112 const QStringList tabs = items[0]->data(Qt::UserRole).toStringList(); 114
113 for (const auto& tab : tabs) { 115 const QList<QWidget*> tabs = qvariant_cast<QList<QWidget*>>(items[0]->data(Qt::UserRole));
114 ui->tabWidget->addTab(widgets.find(tab)->second, tab); 116
117 for (const auto tab : tabs) {
118 ui->tabWidget->addTab(tab, widgets.at(tab));
115 } 119 }
116} 120}
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp
index 75fcbfea3..10bcd650e 100644
--- a/src/yuzu/configuration/configure_general.cpp
+++ b/src/yuzu/configuration/configure_general.cpp
@@ -20,25 +20,29 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent)
20 20
21 SetConfiguration(); 21 SetConfiguration();
22 22
23 connect(ui->toggle_deepscan, &QCheckBox::stateChanged, this, 23 connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
24 [] { UISettings::values.is_game_list_reload_pending.exchange(true); });
25} 24}
26 25
27ConfigureGeneral::~ConfigureGeneral() = default; 26ConfigureGeneral::~ConfigureGeneral() = default;
28 27
29void ConfigureGeneral::SetConfiguration() { 28void ConfigureGeneral::SetConfiguration() {
30 ui->toggle_deepscan->setChecked(UISettings::values.game_directory_deepscan);
31 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing); 29 ui->toggle_check_exit->setChecked(UISettings::values.confirm_before_closing);
32 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot); 30 ui->toggle_user_on_boot->setChecked(UISettings::values.select_user_on_boot);
33 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme)); 31 ui->theme_combobox->setCurrentIndex(ui->theme_combobox->findData(UISettings::values.theme));
32
33 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
34 ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
35 ui->frame_limit->setValue(Settings::values.frame_limit);
34} 36}
35 37
36void ConfigureGeneral::ApplyConfiguration() { 38void ConfigureGeneral::ApplyConfiguration() {
37 UISettings::values.game_directory_deepscan = ui->toggle_deepscan->isChecked();
38 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked(); 39 UISettings::values.confirm_before_closing = ui->toggle_check_exit->isChecked();
39 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked(); 40 UISettings::values.select_user_on_boot = ui->toggle_user_on_boot->isChecked();
40 UISettings::values.theme = 41 UISettings::values.theme =
41 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString(); 42 ui->theme_combobox->itemData(ui->theme_combobox->currentIndex()).toString();
43
44 Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
45 Settings::values.frame_limit = ui->frame_limit->value();
42} 46}
43 47
44void ConfigureGeneral::changeEvent(QEvent* event) { 48void ConfigureGeneral::changeEvent(QEvent* event) {
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui
index 184fdd329..0bb91d64b 100644
--- a/src/yuzu/configuration/configure_general.ui
+++ b/src/yuzu/configuration/configure_general.ui
@@ -25,11 +25,31 @@
25 <item> 25 <item>
26 <layout class="QVBoxLayout" name="GeneralVerticalLayout"> 26 <layout class="QVBoxLayout" name="GeneralVerticalLayout">
27 <item> 27 <item>
28 <widget class="QCheckBox" name="toggle_deepscan"> 28 <layout class="QHBoxLayout" name="horizontalLayout_2">
29 <property name="text"> 29 <item>
30 <string>Search sub-directories for games</string> 30 <widget class="QCheckBox" name="toggle_frame_limit">
31 </property> 31 <property name="text">
32 </widget> 32 <string>Limit Speed Percent</string>
33 </property>
34 </widget>
35 </item>
36 <item>
37 <widget class="QSpinBox" name="frame_limit">
38 <property name="suffix">
39 <string>%</string>
40 </property>
41 <property name="minimum">
42 <number>1</number>
43 </property>
44 <property name="maximum">
45 <number>9999</number>
46 </property>
47 <property name="value">
48 <number>100</number>
49 </property>
50 </widget>
51 </item>
52 </layout>
33 </item> 53 </item>
34 <item> 54 <item>
35 <widget class="QCheckBox" name="toggle_check_exit"> 55 <widget class="QCheckBox" name="toggle_check_exit">
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 2b17b250c..2c9e322c9 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -55,7 +55,6 @@ ConfigureGraphics::ConfigureGraphics(QWidget* parent)
55 55
56 SetConfiguration(); 56 SetConfiguration();
57 57
58 connect(ui->toggle_frame_limit, &QCheckBox::toggled, ui->frame_limit, &QSpinBox::setEnabled);
59 connect(ui->bg_button, &QPushButton::clicked, this, [this] { 58 connect(ui->bg_button, &QPushButton::clicked, this, [this] {
60 const QColor new_bg_color = QColorDialog::getColor(bg_color); 59 const QColor new_bg_color = QColorDialog::getColor(bg_color);
61 if (!new_bg_color.isValid()) { 60 if (!new_bg_color.isValid()) {
@@ -72,9 +71,6 @@ void ConfigureGraphics::SetConfiguration() {
72 71
73 ui->resolution_factor_combobox->setCurrentIndex( 72 ui->resolution_factor_combobox->setCurrentIndex(
74 static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor))); 73 static_cast<int>(FromResolutionFactor(Settings::values.resolution_factor)));
75 ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit);
76 ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked());
77 ui->frame_limit->setValue(Settings::values.frame_limit);
78 ui->use_disk_shader_cache->setEnabled(runtime_lock); 74 ui->use_disk_shader_cache->setEnabled(runtime_lock);
79 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache); 75 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache);
80 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation); 76 ui->use_accurate_gpu_emulation->setChecked(Settings::values.use_accurate_gpu_emulation);
@@ -89,8 +85,6 @@ void ConfigureGraphics::SetConfiguration() {
89void ConfigureGraphics::ApplyConfiguration() { 85void ConfigureGraphics::ApplyConfiguration() {
90 Settings::values.resolution_factor = 86 Settings::values.resolution_factor =
91 ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex())); 87 ToResolutionFactor(static_cast<Resolution>(ui->resolution_factor_combobox->currentIndex()));
92 Settings::values.use_frame_limit = ui->toggle_frame_limit->isChecked();
93 Settings::values.frame_limit = ui->frame_limit->value();
94 Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked(); 88 Settings::values.use_disk_shader_cache = ui->use_disk_shader_cache->isChecked();
95 Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked(); 89 Settings::values.use_accurate_gpu_emulation = ui->use_accurate_gpu_emulation->isChecked();
96 Settings::values.use_asynchronous_gpu_emulation = 90 Settings::values.use_asynchronous_gpu_emulation =
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 15ab18ecd..0309ee300 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -23,33 +23,6 @@
23 </property> 23 </property>
24 <layout class="QVBoxLayout" name="verticalLayout_2"> 24 <layout class="QVBoxLayout" name="verticalLayout_2">
25 <item> 25 <item>
26 <layout class="QHBoxLayout" name="horizontalLayout_2">
27 <item>
28 <widget class="QCheckBox" name="toggle_frame_limit">
29 <property name="text">
30 <string>Limit Speed Percent</string>
31 </property>
32 </widget>
33 </item>
34 <item>
35 <widget class="QSpinBox" name="frame_limit">
36 <property name="suffix">
37 <string>%</string>
38 </property>
39 <property name="minimum">
40 <number>1</number>
41 </property>
42 <property name="maximum">
43 <number>9999</number>
44 </property>
45 <property name="value">
46 <number>100</number>
47 </property>
48 </widget>
49 </item>
50 </layout>
51 </item>
52 <item>
53 <widget class="QCheckBox" name="use_disk_shader_cache"> 26 <widget class="QCheckBox" name="use_disk_shader_cache">
54 <property name="text"> 27 <property name="text">
55 <string>Use disk shader cache</string> 28 <string>Use disk shader cache</string>
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp
index 7b70f307c..a968cfb5d 100644
--- a/src/yuzu/configuration/configure_input_player.cpp
+++ b/src/yuzu/configuration/configure_input_player.cpp
@@ -301,13 +301,16 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i
301 }); 301 });
302 } 302 }
303 connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] { 303 connect(analog_map_stick[analog_id], &QPushButton::clicked, [=] {
304 QMessageBox::information(this, tr("Information"), 304 if (QMessageBox::information(
305 tr("After pressing OK, first move your joystick horizontally, " 305 this, tr("Information"),
306 "and then vertically.")); 306 tr("After pressing OK, first move your joystick horizontally, "
307 HandleClick( 307 "and then vertically."),
308 analog_map_stick[analog_id], 308 QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
309 [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; }, 309 HandleClick(
310 InputCommon::Polling::DeviceType::Analog); 310 analog_map_stick[analog_id],
311 [=](const Common::ParamPackage& params) { analogs_param[analog_id] = params; },
312 InputCommon::Polling::DeviceType::Analog);
313 }
311 }); 314 });
312 } 315 }
313 316
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp
index d18b96519..d5fab2f1f 100644
--- a/src/yuzu/game_list.cpp
+++ b/src/yuzu/game_list.cpp
@@ -34,7 +34,6 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
34 return QObject::eventFilter(obj, event); 34 return QObject::eventFilter(obj, event);
35 35
36 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event); 36 QKeyEvent* keyEvent = static_cast<QKeyEvent*>(event);
37 int rowCount = gamelist->tree_view->model()->rowCount();
38 QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower(); 37 QString edit_filter_text = gamelist->search_field->edit_filter->text().toLower();
39 38
40 // If the searchfield's text hasn't changed special function keys get checked 39 // If the searchfield's text hasn't changed special function keys get checked
@@ -56,19 +55,9 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
56 // If there is only one result launch this game 55 // If there is only one result launch this game
57 case Qt::Key_Return: 56 case Qt::Key_Return:
58 case Qt::Key_Enter: { 57 case Qt::Key_Enter: {
59 QStandardItemModel* item_model = new QStandardItemModel(gamelist->tree_view); 58 if (gamelist->search_field->visible == 1) {
60 QModelIndex root_index = item_model->invisibleRootItem()->index(); 59 QString file_path = gamelist->getLastFilterResultItem();
61 QStandardItem* child_file; 60
62 QString file_path;
63 int resultCount = 0;
64 for (int i = 0; i < rowCount; ++i) {
65 if (!gamelist->tree_view->isRowHidden(i, root_index)) {
66 ++resultCount;
67 child_file = gamelist->item_model->item(i, 0);
68 file_path = child_file->data(GameListItemPath::FullPathRole).toString();
69 }
70 }
71 if (resultCount == 1) {
72 // To avoid loading error dialog loops while confirming them using enter 61 // To avoid loading error dialog loops while confirming them using enter
73 // Also users usually want to run a different game after closing one 62 // Also users usually want to run a different game after closing one
74 gamelist->search_field->edit_filter->clear(); 63 gamelist->search_field->edit_filter->clear();
@@ -88,9 +77,31 @@ bool GameListSearchField::KeyReleaseEater::eventFilter(QObject* obj, QEvent* eve
88} 77}
89 78
90void GameListSearchField::setFilterResult(int visible, int total) { 79void GameListSearchField::setFilterResult(int visible, int total) {
80 this->visible = visible;
81 this->total = total;
82
91 label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible)); 83 label_filter_result->setText(tr("%1 of %n result(s)", "", total).arg(visible));
92} 84}
93 85
86QString GameList::getLastFilterResultItem() const {
87 QStandardItem* folder;
88 QStandardItem* child;
89 QString file_path;
90 const int folder_count = item_model->rowCount();
91 for (int i = 0; i < folder_count; ++i) {
92 folder = item_model->item(i, 0);
93 const QModelIndex folder_index = folder->index();
94 const int children_count = folder->rowCount();
95 for (int j = 0; j < children_count; ++j) {
96 if (!tree_view->isRowHidden(j, folder_index)) {
97 child = folder->child(j, 0);
98 file_path = child->data(GameListItemPath::FullPathRole).toString();
99 }
100 }
101 }
102 return file_path;
103}
104
94void GameListSearchField::clear() { 105void GameListSearchField::clear() {
95 edit_filter->clear(); 106 edit_filter->clear();
96} 107}
@@ -147,45 +158,120 @@ static bool ContainsAllWords(const QString& haystack, const QString& userinput)
147 [&haystack](const QString& s) { return haystack.contains(s); }); 158 [&haystack](const QString& s) { return haystack.contains(s); });
148} 159}
149 160
161// Syncs the expanded state of Game Directories with settings to persist across sessions
162void GameList::onItemExpanded(const QModelIndex& item) {
163 const auto type = item.data(GameListItem::TypeRole).value<GameListItemType>();
164 if (type == GameListItemType::CustomDir || type == GameListItemType::SdmcDir ||
165 type == GameListItemType::UserNandDir || type == GameListItemType::SysNandDir)
166 item.data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded =
167 tree_view->isExpanded(item);
168}
169
150// Event in order to filter the gamelist after editing the searchfield 170// Event in order to filter the gamelist after editing the searchfield
151void GameList::onTextChanged(const QString& new_text) { 171void GameList::onTextChanged(const QString& new_text) {
152 const int row_count = tree_view->model()->rowCount(); 172 const int folder_count = tree_view->model()->rowCount();
153 const QString edit_filter_text = new_text.toLower(); 173 QString edit_filter_text = new_text.toLower();
154 const QModelIndex root_index = item_model->invisibleRootItem()->index(); 174 QStandardItem* folder;
175 QStandardItem* child;
176 int children_total = 0;
177 QModelIndex root_index = item_model->invisibleRootItem()->index();
155 178
156 // If the searchfield is empty every item is visible 179 // If the searchfield is empty every item is visible
157 // Otherwise the filter gets applied 180 // Otherwise the filter gets applied
158 if (edit_filter_text.isEmpty()) { 181 if (edit_filter_text.isEmpty()) {
159 for (int i = 0; i < row_count; ++i) { 182 for (int i = 0; i < folder_count; ++i) {
160 tree_view->setRowHidden(i, root_index, false); 183 folder = item_model->item(i, 0);
184 const QModelIndex folder_index = folder->index();
185 const int children_count = folder->rowCount();
186 for (int j = 0; j < children_count; ++j) {
187 ++children_total;
188 tree_view->setRowHidden(j, folder_index, false);
189 }
161 } 190 }
162 search_field->setFilterResult(row_count, row_count); 191 search_field->setFilterResult(children_total, children_total);
163 } else { 192 } else {
164 int result_count = 0; 193 int result_count = 0;
165 for (int i = 0; i < row_count; ++i) { 194 for (int i = 0; i < folder_count; ++i) {
166 const QStandardItem* child_file = item_model->item(i, 0); 195 folder = item_model->item(i, 0);
167 const QString file_path = 196 const QModelIndex folder_index = folder->index();
168 child_file->data(GameListItemPath::FullPathRole).toString().toLower(); 197 const int children_count = folder->rowCount();
169 const QString file_title = 198 for (int j = 0; j < children_count; ++j) {
170 child_file->data(GameListItemPath::TitleRole).toString().toLower(); 199 ++children_total;
171 const QString file_program_id = 200 const QStandardItem* child = folder->child(j, 0);
172 child_file->data(GameListItemPath::ProgramIdRole).toString().toLower(); 201 const QString file_path =
173 202 child->data(GameListItemPath::FullPathRole).toString().toLower();
174 // Only items which filename in combination with its title contains all words 203 const QString file_title =
175 // that are in the searchfield will be visible in the gamelist 204 child->data(GameListItemPath::TitleRole).toString().toLower();
176 // The search is case insensitive because of toLower() 205 const QString file_program_id =
177 // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent 206 child->data(GameListItemPath::ProgramIdRole).toString().toLower();
178 // multiple conversions of edit_filter_text for each game in the gamelist 207
179 const QString file_name = file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + 208 // Only items which filename in combination with its title contains all words
180 QLatin1Char{' '} + file_title; 209 // that are in the searchfield will be visible in the gamelist
181 if (ContainsAllWords(file_name, edit_filter_text) || 210 // The search is case insensitive because of toLower()
182 (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) { 211 // I decided not to use Qt::CaseInsensitive in containsAllWords to prevent
183 tree_view->setRowHidden(i, root_index, false); 212 // multiple conversions of edit_filter_text for each game in the gamelist
184 ++result_count; 213 const QString file_name =
185 } else { 214 file_path.mid(file_path.lastIndexOf(QLatin1Char{'/'}) + 1) + QLatin1Char{' '} +
186 tree_view->setRowHidden(i, root_index, true); 215 file_title;
216 if (ContainsAllWords(file_name, edit_filter_text) ||
217 (file_program_id.count() == 16 && edit_filter_text.contains(file_program_id))) {
218 tree_view->setRowHidden(j, folder_index, false);
219 ++result_count;
220 } else {
221 tree_view->setRowHidden(j, folder_index, true);
222 }
223 search_field->setFilterResult(result_count, children_total);
187 } 224 }
188 search_field->setFilterResult(result_count, row_count); 225 }
226 }
227}
228
229void GameList::onUpdateThemedIcons() {
230 for (int i = 0; i < item_model->invisibleRootItem()->rowCount(); i++) {
231 QStandardItem* child = item_model->invisibleRootItem()->child(i);
232
233 const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
234 switch (child->data(GameListItem::TypeRole).value<GameListItemType>()) {
235 case GameListItemType::SdmcDir:
236 child->setData(
237 QIcon::fromTheme(QStringLiteral("sd_card"))
238 .pixmap(icon_size)
239 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
240 Qt::DecorationRole);
241 break;
242 case GameListItemType::UserNandDir:
243 child->setData(
244 QIcon::fromTheme(QStringLiteral("chip"))
245 .pixmap(icon_size)
246 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
247 Qt::DecorationRole);
248 break;
249 case GameListItemType::SysNandDir:
250 child->setData(
251 QIcon::fromTheme(QStringLiteral("chip"))
252 .pixmap(icon_size)
253 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
254 Qt::DecorationRole);
255 break;
256 case GameListItemType::CustomDir: {
257 const UISettings::GameDir* game_dir =
258 child->data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
259 const QString icon_name = QFileInfo::exists(game_dir->path)
260 ? QStringLiteral("folder")
261 : QStringLiteral("bad_folder");
262 child->setData(
263 QIcon::fromTheme(icon_name).pixmap(icon_size).scaled(
264 icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
265 Qt::DecorationRole);
266 break;
267 }
268 case GameListItemType::AddDir:
269 child->setData(
270 QIcon::fromTheme(QStringLiteral("plus"))
271 .pixmap(icon_size)
272 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
273 Qt::DecorationRole);
274 break;
189 } 275 }
190 } 276 }
191} 277}
@@ -214,7 +300,6 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
214 tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel); 300 tree_view->setHorizontalScrollMode(QHeaderView::ScrollPerPixel);
215 tree_view->setSortingEnabled(true); 301 tree_view->setSortingEnabled(true);
216 tree_view->setEditTriggers(QHeaderView::NoEditTriggers); 302 tree_view->setEditTriggers(QHeaderView::NoEditTriggers);
217 tree_view->setUniformRowHeights(true);
218 tree_view->setContextMenuPolicy(Qt::CustomContextMenu); 303 tree_view->setContextMenuPolicy(Qt::CustomContextMenu);
219 tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }")); 304 tree_view->setStyleSheet(QStringLiteral("QTreeView{ border: none; }"));
220 305
@@ -230,12 +315,16 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide
230 item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type")); 315 item_model->setHeaderData(COLUMN_FILE_TYPE - 1, Qt::Horizontal, tr("File type"));
231 item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size")); 316 item_model->setHeaderData(COLUMN_SIZE - 1, Qt::Horizontal, tr("Size"));
232 } 317 }
318 item_model->setSortRole(GameListItemPath::TitleRole);
233 319
320 connect(main_window, &GMainWindow::UpdateThemedIcons, this, &GameList::onUpdateThemedIcons);
234 connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry); 321 connect(tree_view, &QTreeView::activated, this, &GameList::ValidateEntry);
235 connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); 322 connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu);
323 connect(tree_view, &QTreeView::expanded, this, &GameList::onItemExpanded);
324 connect(tree_view, &QTreeView::collapsed, this, &GameList::onItemExpanded);
236 325
237 // We must register all custom types with the Qt Automoc system so that we are able to use it 326 // We must register all custom types with the Qt Automoc system so that we are able to use
238 // with signals/slots. In this case, QList falls under the umbrells of custom types. 327 // it with signals/slots. In this case, QList falls under the umbrells of custom types.
239 qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); 328 qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>");
240 329
241 layout->setContentsMargins(0, 0, 0, 0); 330 layout->setContentsMargins(0, 0, 0, 0);
@@ -263,38 +352,68 @@ void GameList::clearFilter() {
263 search_field->clear(); 352 search_field->clear();
264} 353}
265 354
266void GameList::AddEntry(const QList<QStandardItem*>& entry_items) { 355void GameList::AddDirEntry(GameListDir* entry_items) {
267 item_model->invisibleRootItem()->appendRow(entry_items); 356 item_model->invisibleRootItem()->appendRow(entry_items);
357 tree_view->setExpanded(
358 entry_items->index(),
359 entry_items->data(GameListDir::GameDirRole).value<UISettings::GameDir*>()->expanded);
268} 360}
269 361
270void GameList::ValidateEntry(const QModelIndex& item) { 362void GameList::AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent) {
271 // We don't care about the individual QStandardItem that was selected, but its row. 363 parent->appendRow(entry_items);
272 const int row = item_model->itemFromIndex(item)->row(); 364}
273 const QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
274 const QString file_path = child_file->data(GameListItemPath::FullPathRole).toString();
275
276 if (file_path.isEmpty())
277 return;
278
279 if (!QFileInfo::exists(file_path))
280 return;
281 365
282 const QFileInfo file_info{file_path}; 366void GameList::ValidateEntry(const QModelIndex& item) {
283 if (file_info.isDir()) { 367 const auto selected = item.sibling(item.row(), 0);
284 const QDir dir{file_path}; 368
285 const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files); 369 switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
286 if (matching_main.size() == 1) { 370 case GameListItemType::Game: {
287 emit GameChosen(dir.path() + QDir::separator() + matching_main[0]); 371 const QString file_path = selected.data(GameListItemPath::FullPathRole).toString();
372 if (file_path.isEmpty())
373 return;
374 const QFileInfo file_info(file_path);
375 if (!file_info.exists())
376 return;
377
378 if (file_info.isDir()) {
379 const QDir dir{file_path};
380 const QStringList matching_main = dir.entryList({QStringLiteral("main")}, QDir::Files);
381 if (matching_main.size() == 1) {
382 emit GameChosen(dir.path() + QDir::separator() + matching_main[0]);
383 }
384 return;
288 } 385 }
289 return; 386
387 // Users usually want to run a different game after closing one
388 search_field->clear();
389 emit GameChosen(file_path);
390 break;
290 } 391 }
392 case GameListItemType::AddDir:
393 emit AddDirectory();
394 break;
395 }
396}
291 397
292 // Users usually want to run a diffrent game after closing one 398bool GameList::isEmpty() const {
293 search_field->clear(); 399 for (int i = 0; i < item_model->rowCount(); i++) {
294 emit GameChosen(file_path); 400 const QStandardItem* child = item_model->invisibleRootItem()->child(i);
401 const auto type = static_cast<GameListItemType>(child->type());
402 if (!child->hasChildren() &&
403 (type == GameListItemType::SdmcDir || type == GameListItemType::UserNandDir ||
404 type == GameListItemType::SysNandDir)) {
405 item_model->invisibleRootItem()->removeRow(child->row());
406 i--;
407 };
408 }
409 return !item_model->invisibleRootItem()->hasChildren();
295} 410}
296 411
297void GameList::DonePopulating(QStringList watch_list) { 412void GameList::DonePopulating(QStringList watch_list) {
413 emit ShowList(!isEmpty());
414
415 item_model->invisibleRootItem()->appendRow(new GameListAddDir());
416
298 // Clear out the old directories to watch for changes and add the new ones 417 // Clear out the old directories to watch for changes and add the new ones
299 auto watch_dirs = watcher->directories(); 418 auto watch_dirs = watcher->directories();
300 if (!watch_dirs.isEmpty()) { 419 if (!watch_dirs.isEmpty()) {
@@ -311,9 +430,13 @@ void GameList::DonePopulating(QStringList watch_list) {
311 QCoreApplication::processEvents(); 430 QCoreApplication::processEvents();
312 } 431 }
313 tree_view->setEnabled(true); 432 tree_view->setEnabled(true);
314 int rowCount = tree_view->model()->rowCount(); 433 const int folder_count = tree_view->model()->rowCount();
315 search_field->setFilterResult(rowCount, rowCount); 434 int children_total = 0;
316 if (rowCount > 0) { 435 for (int i = 0; i < folder_count; ++i) {
436 children_total += item_model->item(i, 0)->rowCount();
437 }
438 search_field->setFilterResult(children_total, children_total);
439 if (children_total > 0) {
317 search_field->setFocus(); 440 search_field->setFocus();
318 } 441 }
319} 442}
@@ -323,12 +446,27 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
323 if (!item.isValid()) 446 if (!item.isValid())
324 return; 447 return;
325 448
326 int row = item_model->itemFromIndex(item)->row(); 449 const auto selected = item.sibling(item.row(), 0);
327 QStandardItem* child_file = item_model->invisibleRootItem()->child(row, COLUMN_NAME);
328 u64 program_id = child_file->data(GameListItemPath::ProgramIdRole).toULongLong();
329 std::string path = child_file->data(GameListItemPath::FullPathRole).toString().toStdString();
330
331 QMenu context_menu; 450 QMenu context_menu;
451 switch (selected.data(GameListItem::TypeRole).value<GameListItemType>()) {
452 case GameListItemType::Game:
453 AddGamePopup(context_menu, selected.data(GameListItemPath::ProgramIdRole).toULongLong(),
454 selected.data(GameListItemPath::FullPathRole).toString().toStdString());
455 break;
456 case GameListItemType::CustomDir:
457 AddPermDirPopup(context_menu, selected);
458 AddCustomDirPopup(context_menu, selected);
459 break;
460 case GameListItemType::SdmcDir:
461 case GameListItemType::UserNandDir:
462 case GameListItemType::SysNandDir:
463 AddPermDirPopup(context_menu, selected);
464 break;
465 }
466 context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location));
467}
468
469void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, std::string path) {
332 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); 470 QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location"));
333 QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location")); 471 QAction* open_lfs_location = context_menu.addAction(tr("Open Mod Data Location"));
334 QAction* open_transferable_shader_cache = 472 QAction* open_transferable_shader_cache =
@@ -344,19 +482,86 @@ void GameList::PopupContextMenu(const QPoint& menu_location) {
344 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id); 482 auto it = FindMatchingCompatibilityEntry(compatibility_list, program_id);
345 navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0); 483 navigate_to_gamedb_entry->setVisible(it != compatibility_list.end() && program_id != 0);
346 484
347 connect(open_save_location, &QAction::triggered, 485 connect(open_save_location, &QAction::triggered, [this, program_id]() {
348 [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData); }); 486 emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData);
349 connect(open_lfs_location, &QAction::triggered, 487 });
350 [&]() { emit OpenFolderRequested(program_id, GameListOpenTarget::ModData); }); 488 connect(open_lfs_location, &QAction::triggered, [this, program_id]() {
489 emit OpenFolderRequested(program_id, GameListOpenTarget::ModData);
490 });
351 connect(open_transferable_shader_cache, &QAction::triggered, 491 connect(open_transferable_shader_cache, &QAction::triggered,
352 [&]() { emit OpenTransferableShaderCacheRequested(program_id); }); 492 [this, program_id]() { emit OpenTransferableShaderCacheRequested(program_id); });
353 connect(dump_romfs, &QAction::triggered, [&]() { emit DumpRomFSRequested(program_id, path); }); 493 connect(dump_romfs, &QAction::triggered,
354 connect(copy_tid, &QAction::triggered, [&]() { emit CopyTIDRequested(program_id); }); 494 [this, program_id, path]() { emit DumpRomFSRequested(program_id, path); });
355 connect(navigate_to_gamedb_entry, &QAction::triggered, 495 connect(copy_tid, &QAction::triggered,
356 [&]() { emit NavigateToGamedbEntryRequested(program_id, compatibility_list); }); 496 [this, program_id]() { emit CopyTIDRequested(program_id); });
357 connect(properties, &QAction::triggered, [&]() { emit OpenPerGameGeneralRequested(path); }); 497 connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() {
498 emit NavigateToGamedbEntryRequested(program_id, compatibility_list);
499 });
500 connect(properties, &QAction::triggered,
501 [this, path]() { emit OpenPerGameGeneralRequested(path); });
502};
503
504void GameList::AddCustomDirPopup(QMenu& context_menu, QModelIndex selected) {
505 UISettings::GameDir& game_dir =
506 *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
507
508 QAction* deep_scan = context_menu.addAction(tr("Scan Subfolders"));
509 QAction* delete_dir = context_menu.addAction(tr("Remove Game Directory"));
510
511 deep_scan->setCheckable(true);
512 deep_scan->setChecked(game_dir.deep_scan);
513
514 connect(deep_scan, &QAction::triggered, [this, &game_dir] {
515 game_dir.deep_scan = !game_dir.deep_scan;
516 PopulateAsync(UISettings::values.game_dirs);
517 });
518 connect(delete_dir, &QAction::triggered, [this, &game_dir, selected] {
519 UISettings::values.game_dirs.removeOne(game_dir);
520 item_model->invisibleRootItem()->removeRow(selected.row());
521 });
522}
358 523
359 context_menu.exec(tree_view->viewport()->mapToGlobal(menu_location)); 524void GameList::AddPermDirPopup(QMenu& context_menu, QModelIndex selected) {
525 UISettings::GameDir& game_dir =
526 *selected.data(GameListDir::GameDirRole).value<UISettings::GameDir*>();
527
528 QAction* move_up = context_menu.addAction(tr(u8"\U000025b2 Move Up"));
529 QAction* move_down = context_menu.addAction(tr(u8"\U000025bc Move Down "));
530 QAction* open_directory_location = context_menu.addAction(tr("Open Directory Location"));
531
532 const int row = selected.row();
533
534 move_up->setEnabled(row > 0);
535 move_down->setEnabled(row < item_model->rowCount() - 2);
536
537 connect(move_up, &QAction::triggered, [this, selected, row, &game_dir] {
538 // find the indices of the items in settings and swap them
539 std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)],
540 UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(
541 *selected.sibling(row - 1, 0)
542 .data(GameListDir::GameDirRole)
543 .value<UISettings::GameDir*>())]);
544 // move the treeview items
545 QList<QStandardItem*> item = item_model->takeRow(row);
546 item_model->invisibleRootItem()->insertRow(row - 1, item);
547 tree_view->setExpanded(selected, game_dir.expanded);
548 });
549
550 connect(move_down, &QAction::triggered, [this, selected, row, &game_dir] {
551 // find the indices of the items in settings and swap them
552 std::swap(UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(game_dir)],
553 UISettings::values.game_dirs[UISettings::values.game_dirs.indexOf(
554 *selected.sibling(row + 1, 0)
555 .data(GameListDir::GameDirRole)
556 .value<UISettings::GameDir*>())]);
557 // move the treeview items
558 const QList<QStandardItem*> item = item_model->takeRow(row);
559 item_model->invisibleRootItem()->insertRow(row + 1, item);
560 tree_view->setExpanded(selected, game_dir.expanded);
561 });
562
563 connect(open_directory_location, &QAction::triggered,
564 [this, game_dir] { emit OpenDirectory(game_dir.path); });
360} 565}
361 566
362void GameList::LoadCompatibilityList() { 567void GameList::LoadCompatibilityList() {
@@ -403,14 +608,7 @@ void GameList::LoadCompatibilityList() {
403 } 608 }
404} 609}
405 610
406void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) { 611void GameList::PopulateAsync(QVector<UISettings::GameDir>& game_dirs) {
407 const QFileInfo dir_info{dir_path};
408 if (!dir_info.exists() || !dir_info.isDir()) {
409 LOG_ERROR(Frontend, "Could not find game list folder at {}", dir_path.toStdString());
410 search_field->setFilterResult(0, 0);
411 return;
412 }
413
414 tree_view->setEnabled(false); 612 tree_view->setEnabled(false);
415 613
416 // Update the columns in case UISettings has changed 614 // Update the columns in case UISettings has changed
@@ -433,17 +631,19 @@ void GameList::PopulateAsync(const QString& dir_path, bool deep_scan) {
433 631
434 // Delete any rows that might already exist if we're repopulating 632 // Delete any rows that might already exist if we're repopulating
435 item_model->removeRows(0, item_model->rowCount()); 633 item_model->removeRows(0, item_model->rowCount());
634 search_field->clear();
436 635
437 emit ShouldCancelWorker(); 636 emit ShouldCancelWorker();
438 637
439 GameListWorker* worker = 638 GameListWorker* worker = new GameListWorker(vfs, provider, game_dirs, compatibility_list);
440 new GameListWorker(vfs, provider, dir_path, deep_scan, compatibility_list);
441 639
442 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection); 640 connect(worker, &GameListWorker::EntryReady, this, &GameList::AddEntry, Qt::QueuedConnection);
641 connect(worker, &GameListWorker::DirEntryReady, this, &GameList::AddDirEntry,
642 Qt::QueuedConnection);
443 connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating, 643 connect(worker, &GameListWorker::Finished, this, &GameList::DonePopulating,
444 Qt::QueuedConnection); 644 Qt::QueuedConnection);
445 // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to cancel 645 // Use DirectConnection here because worker->Cancel() is thread-safe and we want it to
446 // without delay. 646 // cancel without delay.
447 connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel, 647 connect(this, &GameList::ShouldCancelWorker, worker, &GameListWorker::Cancel,
448 Qt::DirectConnection); 648 Qt::DirectConnection);
449 649
@@ -471,10 +671,40 @@ const QStringList GameList::supported_file_extensions = {
471 QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")}; 671 QStringLiteral("xci"), QStringLiteral("nsp"), QStringLiteral("kip")};
472 672
473void GameList::RefreshGameDirectory() { 673void GameList::RefreshGameDirectory() {
474 if (!UISettings::values.game_directory_path.isEmpty() && current_worker != nullptr) { 674 if (!UISettings::values.game_dirs.isEmpty() && current_worker != nullptr) {
475 LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list."); 675 LOG_INFO(Frontend, "Change detected in the games directory. Reloading game list.");
476 search_field->clear(); 676 PopulateAsync(UISettings::values.game_dirs);
477 PopulateAsync(UISettings::values.game_directory_path,
478 UISettings::values.game_directory_deepscan);
479 } 677 }
480} 678}
679
680GameListPlaceholder::GameListPlaceholder(GMainWindow* parent) : QWidget{parent} {
681 connect(parent, &GMainWindow::UpdateThemedIcons, this,
682 &GameListPlaceholder::onUpdateThemedIcons);
683
684 layout = new QVBoxLayout;
685 image = new QLabel;
686 text = new QLabel;
687 layout->setAlignment(Qt::AlignCenter);
688 image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
689
690 text->setText(tr("Double-click to add a new folder to the game list"));
691 QFont font = text->font();
692 font.setPointSize(20);
693 text->setFont(font);
694 text->setAlignment(Qt::AlignHCenter);
695 image->setAlignment(Qt::AlignHCenter);
696
697 layout->addWidget(image);
698 layout->addWidget(text);
699 setLayout(layout);
700}
701
702GameListPlaceholder::~GameListPlaceholder() = default;
703
704void GameListPlaceholder::onUpdateThemedIcons() {
705 image->setPixmap(QIcon::fromTheme(QStringLiteral("plus_folder")).pixmap(200));
706}
707
708void GameListPlaceholder::mouseDoubleClickEvent(QMouseEvent* event) {
709 emit GameListPlaceholder::AddDirectory();
710}
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h
index f8f8bd6c5..878d94413 100644
--- a/src/yuzu/game_list.h
+++ b/src/yuzu/game_list.h
@@ -8,6 +8,7 @@
8#include <QHBoxLayout> 8#include <QHBoxLayout>
9#include <QLabel> 9#include <QLabel>
10#include <QLineEdit> 10#include <QLineEdit>
11#include <QList>
11#include <QModelIndex> 12#include <QModelIndex>
12#include <QSettings> 13#include <QSettings>
13#include <QStandardItem> 14#include <QStandardItem>
@@ -16,13 +17,16 @@
16#include <QToolButton> 17#include <QToolButton>
17#include <QTreeView> 18#include <QTreeView>
18#include <QVBoxLayout> 19#include <QVBoxLayout>
20#include <QVector>
19#include <QWidget> 21#include <QWidget>
20 22
21#include "common/common_types.h" 23#include "common/common_types.h"
24#include "uisettings.h"
22#include "yuzu/compatibility_list.h" 25#include "yuzu/compatibility_list.h"
23 26
24class GameListWorker; 27class GameListWorker;
25class GameListSearchField; 28class GameListSearchField;
29class GameListDir;
26class GMainWindow; 30class GMainWindow;
27 31
28namespace FileSys { 32namespace FileSys {
@@ -52,12 +56,14 @@ public:
52 FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr); 56 FileSys::ManualContentProvider* provider, GMainWindow* parent = nullptr);
53 ~GameList() override; 57 ~GameList() override;
54 58
59 QString getLastFilterResultItem() const;
55 void clearFilter(); 60 void clearFilter();
56 void setFilterFocus(); 61 void setFilterFocus();
57 void setFilterVisible(bool visibility); 62 void setFilterVisible(bool visibility);
63 bool isEmpty() const;
58 64
59 void LoadCompatibilityList(); 65 void LoadCompatibilityList();
60 void PopulateAsync(const QString& dir_path, bool deep_scan); 66 void PopulateAsync(QVector<UISettings::GameDir>& game_dirs);
61 67
62 void SaveInterfaceLayout(); 68 void SaveInterfaceLayout();
63 void LoadInterfaceLayout(); 69 void LoadInterfaceLayout();
@@ -74,19 +80,29 @@ signals:
74 void NavigateToGamedbEntryRequested(u64 program_id, 80 void NavigateToGamedbEntryRequested(u64 program_id,
75 const CompatibilityList& compatibility_list); 81 const CompatibilityList& compatibility_list);
76 void OpenPerGameGeneralRequested(const std::string& file); 82 void OpenPerGameGeneralRequested(const std::string& file);
83 void OpenDirectory(const QString& directory);
84 void AddDirectory();
85 void ShowList(bool show);
77 86
78private slots: 87private slots:
88 void onItemExpanded(const QModelIndex& item);
79 void onTextChanged(const QString& new_text); 89 void onTextChanged(const QString& new_text);
80 void onFilterCloseClicked(); 90 void onFilterCloseClicked();
91 void onUpdateThemedIcons();
81 92
82private: 93private:
83 void AddEntry(const QList<QStandardItem*>& entry_items); 94 void AddDirEntry(GameListDir* entry_items);
95 void AddEntry(const QList<QStandardItem*>& entry_items, GameListDir* parent);
84 void ValidateEntry(const QModelIndex& item); 96 void ValidateEntry(const QModelIndex& item);
85 void DonePopulating(QStringList watch_list); 97 void DonePopulating(QStringList watch_list);
86 98
87 void PopupContextMenu(const QPoint& menu_location);
88 void RefreshGameDirectory(); 99 void RefreshGameDirectory();
89 100
101 void PopupContextMenu(const QPoint& menu_location);
102 void AddGamePopup(QMenu& context_menu, u64 program_id, std::string path);
103 void AddCustomDirPopup(QMenu& context_menu, QModelIndex selected);
104 void AddPermDirPopup(QMenu& context_menu, QModelIndex selected);
105
90 std::shared_ptr<FileSys::VfsFilesystem> vfs; 106 std::shared_ptr<FileSys::VfsFilesystem> vfs;
91 FileSys::ManualContentProvider* provider; 107 FileSys::ManualContentProvider* provider;
92 GameListSearchField* search_field; 108 GameListSearchField* search_field;
@@ -102,3 +118,24 @@ private:
102}; 118};
103 119
104Q_DECLARE_METATYPE(GameListOpenTarget); 120Q_DECLARE_METATYPE(GameListOpenTarget);
121
122class GameListPlaceholder : public QWidget {
123 Q_OBJECT
124public:
125 explicit GameListPlaceholder(GMainWindow* parent = nullptr);
126 ~GameListPlaceholder();
127
128signals:
129 void AddDirectory();
130
131private slots:
132 void onUpdateThemedIcons();
133
134protected:
135 void mouseDoubleClickEvent(QMouseEvent* event) override;
136
137private:
138 QVBoxLayout* layout = nullptr;
139 QLabel* image = nullptr;
140 QLabel* text = nullptr;
141};
diff --git a/src/yuzu/game_list_p.h b/src/yuzu/game_list_p.h
index ece534dd6..a8d888fee 100644
--- a/src/yuzu/game_list_p.h
+++ b/src/yuzu/game_list_p.h
@@ -10,6 +10,7 @@
10#include <utility> 10#include <utility>
11 11
12#include <QCoreApplication> 12#include <QCoreApplication>
13#include <QFileInfo>
13#include <QImage> 14#include <QImage>
14#include <QObject> 15#include <QObject>
15#include <QStandardItem> 16#include <QStandardItem>
@@ -22,6 +23,17 @@
22#include "yuzu/uisettings.h" 23#include "yuzu/uisettings.h"
23#include "yuzu/util/util.h" 24#include "yuzu/util/util.h"
24 25
26enum class GameListItemType {
27 Game = QStandardItem::UserType + 1,
28 CustomDir = QStandardItem::UserType + 2,
29 SdmcDir = QStandardItem::UserType + 3,
30 UserNandDir = QStandardItem::UserType + 4,
31 SysNandDir = QStandardItem::UserType + 5,
32 AddDir = QStandardItem::UserType + 6
33};
34
35Q_DECLARE_METATYPE(GameListItemType);
36
25/** 37/**
26 * Gets the default icon (for games without valid title metadata) 38 * Gets the default icon (for games without valid title metadata)
27 * @param size The desired width and height of the default icon. 39 * @param size The desired width and height of the default icon.
@@ -36,8 +48,13 @@ static QPixmap GetDefaultIcon(u32 size) {
36class GameListItem : public QStandardItem { 48class GameListItem : public QStandardItem {
37 49
38public: 50public:
51 // used to access type from item index
52 static const int TypeRole = Qt::UserRole + 1;
53 static const int SortRole = Qt::UserRole + 2;
39 GameListItem() = default; 54 GameListItem() = default;
40 explicit GameListItem(const QString& string) : QStandardItem(string) {} 55 GameListItem(const QString& string) : QStandardItem(string) {
56 setData(string, SortRole);
57 }
41}; 58};
42 59
43/** 60/**
@@ -48,14 +65,15 @@ public:
48 */ 65 */
49class GameListItemPath : public GameListItem { 66class GameListItemPath : public GameListItem {
50public: 67public:
51 static const int FullPathRole = Qt::UserRole + 1; 68 static const int TitleRole = SortRole;
52 static const int TitleRole = Qt::UserRole + 2; 69 static const int FullPathRole = SortRole + 1;
53 static const int ProgramIdRole = Qt::UserRole + 3; 70 static const int ProgramIdRole = SortRole + 2;
54 static const int FileTypeRole = Qt::UserRole + 4; 71 static const int FileTypeRole = SortRole + 3;
55 72
56 GameListItemPath() = default; 73 GameListItemPath() = default;
57 GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data, 74 GameListItemPath(const QString& game_path, const std::vector<u8>& picture_data,
58 const QString& game_name, const QString& game_type, u64 program_id) { 75 const QString& game_name, const QString& game_type, u64 program_id) {
76 setData(type(), TypeRole);
59 setData(game_path, FullPathRole); 77 setData(game_path, FullPathRole);
60 setData(game_name, TitleRole); 78 setData(game_name, TitleRole);
61 setData(qulonglong(program_id), ProgramIdRole); 79 setData(qulonglong(program_id), ProgramIdRole);
@@ -72,6 +90,10 @@ public:
72 setData(picture, Qt::DecorationRole); 90 setData(picture, Qt::DecorationRole);
73 } 91 }
74 92
93 int type() const override {
94 return static_cast<int>(GameListItemType::Game);
95 }
96
75 QVariant data(int role) const override { 97 QVariant data(int role) const override {
76 if (role == Qt::DisplayRole) { 98 if (role == Qt::DisplayRole) {
77 std::string filename; 99 std::string filename;
@@ -103,9 +125,11 @@ public:
103class GameListItemCompat : public GameListItem { 125class GameListItemCompat : public GameListItem {
104 Q_DECLARE_TR_FUNCTIONS(GameListItemCompat) 126 Q_DECLARE_TR_FUNCTIONS(GameListItemCompat)
105public: 127public:
106 static const int CompatNumberRole = Qt::UserRole + 1; 128 static const int CompatNumberRole = SortRole;
107 GameListItemCompat() = default; 129 GameListItemCompat() = default;
108 explicit GameListItemCompat(const QString& compatibility) { 130 explicit GameListItemCompat(const QString& compatibility) {
131 setData(type(), TypeRole);
132
109 struct CompatStatus { 133 struct CompatStatus {
110 QString color; 134 QString color;
111 const char* text; 135 const char* text;
@@ -135,6 +159,10 @@ public:
135 setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole); 159 setData(CreateCirclePixmapFromColor(status.color), Qt::DecorationRole);
136 } 160 }
137 161
162 int type() const override {
163 return static_cast<int>(GameListItemType::Game);
164 }
165
138 bool operator<(const QStandardItem& other) const override { 166 bool operator<(const QStandardItem& other) const override {
139 return data(CompatNumberRole) < other.data(CompatNumberRole); 167 return data(CompatNumberRole) < other.data(CompatNumberRole);
140 } 168 }
@@ -146,12 +174,12 @@ public:
146 * human-readable string representation will be displayed to the user. 174 * human-readable string representation will be displayed to the user.
147 */ 175 */
148class GameListItemSize : public GameListItem { 176class GameListItemSize : public GameListItem {
149
150public: 177public:
151 static const int SizeRole = Qt::UserRole + 1; 178 static const int SizeRole = SortRole;
152 179
153 GameListItemSize() = default; 180 GameListItemSize() = default;
154 explicit GameListItemSize(const qulonglong size_bytes) { 181 explicit GameListItemSize(const qulonglong size_bytes) {
182 setData(type(), TypeRole);
155 setData(size_bytes, SizeRole); 183 setData(size_bytes, SizeRole);
156 } 184 }
157 185
@@ -167,6 +195,10 @@ public:
167 } 195 }
168 } 196 }
169 197
198 int type() const override {
199 return static_cast<int>(GameListItemType::Game);
200 }
201
170 /** 202 /**
171 * This operator is, in practice, only used by the TreeView sorting systems. 203 * This operator is, in practice, only used by the TreeView sorting systems.
172 * Override it so that it will correctly sort by numerical value instead of by string 204 * Override it so that it will correctly sort by numerical value instead of by string
@@ -177,6 +209,82 @@ public:
177 } 209 }
178}; 210};
179 211
212class GameListDir : public GameListItem {
213public:
214 static const int GameDirRole = Qt::UserRole + 2;
215
216 explicit GameListDir(UISettings::GameDir& directory,
217 GameListItemType dir_type = GameListItemType::CustomDir)
218 : dir_type{dir_type} {
219 setData(type(), TypeRole);
220
221 UISettings::GameDir* game_dir = &directory;
222 setData(QVariant::fromValue(game_dir), GameDirRole);
223
224 const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
225 switch (dir_type) {
226 case GameListItemType::SdmcDir:
227 setData(
228 QIcon::fromTheme(QStringLiteral("sd_card"))
229 .pixmap(icon_size)
230 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
231 Qt::DecorationRole);
232 setData(QObject::tr("Installed SD Titles"), Qt::DisplayRole);
233 break;
234 case GameListItemType::UserNandDir:
235 setData(
236 QIcon::fromTheme(QStringLiteral("chip"))
237 .pixmap(icon_size)
238 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
239 Qt::DecorationRole);
240 setData(QObject::tr("Installed NAND Titles"), Qt::DisplayRole);
241 break;
242 case GameListItemType::SysNandDir:
243 setData(
244 QIcon::fromTheme(QStringLiteral("chip"))
245 .pixmap(icon_size)
246 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
247 Qt::DecorationRole);
248 setData(QObject::tr("System Titles"), Qt::DisplayRole);
249 break;
250 case GameListItemType::CustomDir:
251 const QString icon_name = QFileInfo::exists(game_dir->path)
252 ? QStringLiteral("folder")
253 : QStringLiteral("bad_folder");
254 setData(QIcon::fromTheme(icon_name).pixmap(icon_size).scaled(
255 icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
256 Qt::DecorationRole);
257 setData(game_dir->path, Qt::DisplayRole);
258 break;
259 };
260 };
261
262 int type() const override {
263 return static_cast<int>(dir_type);
264 }
265
266private:
267 GameListItemType dir_type;
268};
269
270class GameListAddDir : public GameListItem {
271public:
272 explicit GameListAddDir() {
273 setData(type(), TypeRole);
274
275 const int icon_size = std::min(static_cast<int>(UISettings::values.icon_size), 64);
276 setData(QIcon::fromTheme(QStringLiteral("plus"))
277 .pixmap(icon_size)
278 .scaled(icon_size, icon_size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
279 Qt::DecorationRole);
280 setData(QObject::tr("Add New Game Directory"), Qt::DisplayRole);
281 }
282
283 int type() const override {
284 return static_cast<int>(GameListItemType::AddDir);
285 }
286};
287
180class GameList; 288class GameList;
181class QHBoxLayout; 289class QHBoxLayout;
182class QTreeView; 290class QTreeView;
@@ -208,6 +316,9 @@ private:
208 // EventFilter in order to process systemkeys while editing the searchfield 316 // EventFilter in order to process systemkeys while editing the searchfield
209 bool eventFilter(QObject* obj, QEvent* event) override; 317 bool eventFilter(QObject* obj, QEvent* event) override;
210 }; 318 };
319 int visible;
320 int total;
321
211 QHBoxLayout* layout_filter = nullptr; 322 QHBoxLayout* layout_filter = nullptr;
212 QTreeView* tree_view = nullptr; 323 QTreeView* tree_view = nullptr;
213 QLabel* label_filter = nullptr; 324 QLabel* label_filter = nullptr;
diff --git a/src/yuzu/game_list_worker.cpp b/src/yuzu/game_list_worker.cpp
index 77f358630..fd21a9761 100644
--- a/src/yuzu/game_list_worker.cpp
+++ b/src/yuzu/game_list_worker.cpp
@@ -223,21 +223,37 @@ QList<QStandardItem*> MakeGameListEntry(const std::string& path, const std::stri
223} // Anonymous namespace 223} // Anonymous namespace
224 224
225GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs, 225GameListWorker::GameListWorker(FileSys::VirtualFilesystem vfs,
226 FileSys::ManualContentProvider* provider, QString dir_path, 226 FileSys::ManualContentProvider* provider,
227 bool deep_scan, const CompatibilityList& compatibility_list) 227 QVector<UISettings::GameDir>& game_dirs,
228 : vfs(std::move(vfs)), provider(provider), dir_path(std::move(dir_path)), deep_scan(deep_scan), 228 const CompatibilityList& compatibility_list)
229 : vfs(std::move(vfs)), provider(provider), game_dirs(game_dirs),
229 compatibility_list(compatibility_list) {} 230 compatibility_list(compatibility_list) {}
230 231
231GameListWorker::~GameListWorker() = default; 232GameListWorker::~GameListWorker() = default;
232 233
233void GameListWorker::AddTitlesToGameList() { 234void GameListWorker::AddTitlesToGameList(GameListDir* parent_dir) {
234 const auto& cache = dynamic_cast<FileSys::ContentProviderUnion&>( 235 using namespace FileSys;
235 Core::System::GetInstance().GetContentProvider()); 236
236 const auto installed_games = cache.ListEntriesFilterOrigin( 237 const auto& cache =
237 std::nullopt, FileSys::TitleType::Application, FileSys::ContentRecordType::Program); 238 dynamic_cast<ContentProviderUnion&>(Core::System::GetInstance().GetContentProvider());
239
240 std::vector<std::pair<ContentProviderUnionSlot, ContentProviderEntry>> installed_games;
241 installed_games = cache.ListEntriesFilterOrigin(std::nullopt, TitleType::Application,
242 ContentRecordType::Program);
243
244 if (parent_dir->type() == static_cast<int>(GameListItemType::SdmcDir)) {
245 installed_games = cache.ListEntriesFilterOrigin(
246 ContentProviderUnionSlot::SDMC, TitleType::Application, ContentRecordType::Program);
247 } else if (parent_dir->type() == static_cast<int>(GameListItemType::UserNandDir)) {
248 installed_games = cache.ListEntriesFilterOrigin(
249 ContentProviderUnionSlot::UserNAND, TitleType::Application, ContentRecordType::Program);
250 } else if (parent_dir->type() == static_cast<int>(GameListItemType::SysNandDir)) {
251 installed_games = cache.ListEntriesFilterOrigin(
252 ContentProviderUnionSlot::SysNAND, TitleType::Application, ContentRecordType::Program);
253 }
238 254
239 for (const auto& [slot, game] : installed_games) { 255 for (const auto& [slot, game] : installed_games) {
240 if (slot == FileSys::ContentProviderUnionSlot::FrontendManual) 256 if (slot == ContentProviderUnionSlot::FrontendManual)
241 continue; 257 continue;
242 258
243 const auto file = cache.GetEntryUnparsed(game.title_id, game.type); 259 const auto file = cache.GetEntryUnparsed(game.title_id, game.type);
@@ -250,21 +266,22 @@ void GameListWorker::AddTitlesToGameList() {
250 u64 program_id = 0; 266 u64 program_id = 0;
251 loader->ReadProgramId(program_id); 267 loader->ReadProgramId(program_id);
252 268
253 const FileSys::PatchManager patch{program_id}; 269 const PatchManager patch{program_id};
254 const auto control = cache.GetEntry(game.title_id, FileSys::ContentRecordType::Control); 270 const auto control = cache.GetEntry(game.title_id, ContentRecordType::Control);
255 if (control != nullptr) 271 if (control != nullptr)
256 GetMetadataFromControlNCA(patch, *control, icon, name); 272 GetMetadataFromControlNCA(patch, *control, icon, name);
257 273
258 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id, 274 emit EntryReady(MakeGameListEntry(file->GetFullPath(), name, icon, *loader, program_id,
259 compatibility_list, patch)); 275 compatibility_list, patch),
276 parent_dir);
260 } 277 }
261} 278}
262 279
263void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path, 280void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_path,
264 unsigned int recursion) { 281 unsigned int recursion, GameListDir* parent_dir) {
265 const auto callback = [this, target, recursion](u64* num_entries_out, 282 const auto callback = [this, target, recursion,
266 const std::string& directory, 283 parent_dir](u64* num_entries_out, const std::string& directory,
267 const std::string& virtual_name) -> bool { 284 const std::string& virtual_name) -> bool {
268 if (stop_processing) { 285 if (stop_processing) {
269 // Breaks the callback loop. 286 // Breaks the callback loop.
270 return false; 287 return false;
@@ -317,11 +334,12 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
317 const FileSys::PatchManager patch{program_id}; 334 const FileSys::PatchManager patch{program_id};
318 335
319 emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id, 336 emit EntryReady(MakeGameListEntry(physical_name, name, icon, *loader, program_id,
320 compatibility_list, patch)); 337 compatibility_list, patch),
338 parent_dir);
321 } 339 }
322 } else if (is_dir && recursion > 0) { 340 } else if (is_dir && recursion > 0) {
323 watch_list.append(QString::fromStdString(physical_name)); 341 watch_list.append(QString::fromStdString(physical_name));
324 ScanFileSystem(target, physical_name, recursion - 1); 342 ScanFileSystem(target, physical_name, recursion - 1, parent_dir);
325 } 343 }
326 344
327 return true; 345 return true;
@@ -332,12 +350,32 @@ void GameListWorker::ScanFileSystem(ScanTarget target, const std::string& dir_pa
332 350
333void GameListWorker::run() { 351void GameListWorker::run() {
334 stop_processing = false; 352 stop_processing = false;
335 watch_list.append(dir_path); 353
336 provider->ClearAllEntries(); 354 for (UISettings::GameDir& game_dir : game_dirs) {
337 ScanFileSystem(ScanTarget::FillManualContentProvider, dir_path.toStdString(), 355 if (game_dir.path == QStringLiteral("SDMC")) {
338 deep_scan ? 256 : 0); 356 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SdmcDir);
339 AddTitlesToGameList(); 357 emit DirEntryReady({game_list_dir});
340 ScanFileSystem(ScanTarget::PopulateGameList, dir_path.toStdString(), deep_scan ? 256 : 0); 358 AddTitlesToGameList(game_list_dir);
359 } else if (game_dir.path == QStringLiteral("UserNAND")) {
360 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::UserNandDir);
361 emit DirEntryReady({game_list_dir});
362 AddTitlesToGameList(game_list_dir);
363 } else if (game_dir.path == QStringLiteral("SysNAND")) {
364 auto* const game_list_dir = new GameListDir(game_dir, GameListItemType::SysNandDir);
365 emit DirEntryReady({game_list_dir});
366 AddTitlesToGameList(game_list_dir);
367 } else {
368 watch_list.append(game_dir.path);
369 auto* const game_list_dir = new GameListDir(game_dir);
370 emit DirEntryReady({game_list_dir});
371 provider->ClearAllEntries();
372 ScanFileSystem(ScanTarget::FillManualContentProvider, game_dir.path.toStdString(), 2,
373 game_list_dir);
374 ScanFileSystem(ScanTarget::PopulateGameList, game_dir.path.toStdString(),
375 game_dir.deep_scan ? 256 : 0, game_list_dir);
376 }
377 };
378
341 emit Finished(watch_list); 379 emit Finished(watch_list);
342} 380}
343 381
diff --git a/src/yuzu/game_list_worker.h b/src/yuzu/game_list_worker.h
index 7c3074af9..6e52fca89 100644
--- a/src/yuzu/game_list_worker.h
+++ b/src/yuzu/game_list_worker.h
@@ -14,6 +14,7 @@
14#include <QObject> 14#include <QObject>
15#include <QRunnable> 15#include <QRunnable>
16#include <QString> 16#include <QString>
17#include <QVector>
17 18
18#include "common/common_types.h" 19#include "common/common_types.h"
19#include "yuzu/compatibility_list.h" 20#include "yuzu/compatibility_list.h"
@@ -33,9 +34,10 @@ class GameListWorker : public QObject, public QRunnable {
33 Q_OBJECT 34 Q_OBJECT
34 35
35public: 36public:
36 GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs, 37 explicit GameListWorker(std::shared_ptr<FileSys::VfsFilesystem> vfs,
37 FileSys::ManualContentProvider* provider, QString dir_path, bool deep_scan, 38 FileSys::ManualContentProvider* provider,
38 const CompatibilityList& compatibility_list); 39 QVector<UISettings::GameDir>& game_dirs,
40 const CompatibilityList& compatibility_list);
39 ~GameListWorker() override; 41 ~GameListWorker() override;
40 42
41 /// Starts the processing of directory tree information. 43 /// Starts the processing of directory tree information.
@@ -48,31 +50,33 @@ signals:
48 /** 50 /**
49 * The `EntryReady` signal is emitted once an entry has been prepared and is ready 51 * The `EntryReady` signal is emitted once an entry has been prepared and is ready
50 * to be added to the game list. 52 * to be added to the game list.
51 * @param entry_items a list with `QStandardItem`s that make up the columns of the new entry. 53 * @param entry_items a list with `QStandardItem`s that make up the columns of the new
54 * entry.
52 */ 55 */
53 void EntryReady(QList<QStandardItem*> entry_items); 56 void DirEntryReady(GameListDir* entry_items);
57 void EntryReady(QList<QStandardItem*> entry_items, GameListDir* parent_dir);
54 58
55 /** 59 /**
56 * After the worker has traversed the game directory looking for entries, this signal is emitted 60 * After the worker has traversed the game directory looking for entries, this signal is
57 * with a list of folders that should be watched for changes as well. 61 * emitted with a list of folders that should be watched for changes as well.
58 */ 62 */
59 void Finished(QStringList watch_list); 63 void Finished(QStringList watch_list);
60 64
61private: 65private:
62 void AddTitlesToGameList(); 66 void AddTitlesToGameList(GameListDir* parent_dir);
63 67
64 enum class ScanTarget { 68 enum class ScanTarget {
65 FillManualContentProvider, 69 FillManualContentProvider,
66 PopulateGameList, 70 PopulateGameList,
67 }; 71 };
68 72
69 void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion = 0); 73 void ScanFileSystem(ScanTarget target, const std::string& dir_path, unsigned int recursion,
74 GameListDir* parent_dir);
70 75
71 std::shared_ptr<FileSys::VfsFilesystem> vfs; 76 std::shared_ptr<FileSys::VfsFilesystem> vfs;
72 FileSys::ManualContentProvider* provider; 77 FileSys::ManualContentProvider* provider;
73 QStringList watch_list; 78 QStringList watch_list;
74 QString dir_path;
75 bool deep_scan;
76 const CompatibilityList& compatibility_list; 79 const CompatibilityList& compatibility_list;
80 QVector<UISettings::GameDir>& game_dirs;
77 std::atomic_bool stop_processing; 81 std::atomic_bool stop_processing;
78}; 82};
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index ac57229d5..8304c6517 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -6,6 +6,9 @@
6#include <clocale> 6#include <clocale>
7#include <memory> 7#include <memory>
8#include <thread> 8#include <thread>
9#ifdef __APPLE__
10#include <unistd.h> // for chdir
11#endif
9 12
10// VFS includes must be before glad as they will conflict with Windows file api, which uses defines. 13// VFS includes must be before glad as they will conflict with Windows file api, which uses defines.
11#include "applets/error.h" 14#include "applets/error.h"
@@ -216,8 +219,7 @@ GMainWindow::GMainWindow()
216 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning); 219 OnReinitializeKeys(ReinitializeKeyBehavior::NoWarning);
217 220
218 game_list->LoadCompatibilityList(); 221 game_list->LoadCompatibilityList();
219 game_list->PopulateAsync(UISettings::values.game_directory_path, 222 game_list->PopulateAsync(UISettings::values.game_dirs);
220 UISettings::values.game_directory_deepscan);
221 223
222 // Show one-time "callout" messages to the user 224 // Show one-time "callout" messages to the user
223 ShowTelemetryCallout(); 225 ShowTelemetryCallout();
@@ -427,6 +429,10 @@ void GMainWindow::InitializeWidgets() {
427 game_list = new GameList(vfs, provider.get(), this); 429 game_list = new GameList(vfs, provider.get(), this);
428 ui.horizontalLayout->addWidget(game_list); 430 ui.horizontalLayout->addWidget(game_list);
429 431
432 game_list_placeholder = new GameListPlaceholder(this);
433 ui.horizontalLayout->addWidget(game_list_placeholder);
434 game_list_placeholder->setVisible(false);
435
430 loading_screen = new LoadingScreen(this); 436 loading_screen = new LoadingScreen(this);
431 loading_screen->hide(); 437 loading_screen->hide();
432 ui.horizontalLayout->addWidget(loading_screen); 438 ui.horizontalLayout->addWidget(loading_screen);
@@ -660,6 +666,7 @@ void GMainWindow::RestoreUIState() {
660 666
661void GMainWindow::ConnectWidgetEvents() { 667void GMainWindow::ConnectWidgetEvents() {
662 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); 668 connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile);
669 connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory);
663 connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); 670 connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder);
664 connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this, 671 connect(game_list, &GameList::OpenTransferableShaderCacheRequested, this,
665 &GMainWindow::OnTransferableShaderCacheOpenFile); 672 &GMainWindow::OnTransferableShaderCacheOpenFile);
@@ -667,6 +674,11 @@ void GMainWindow::ConnectWidgetEvents() {
667 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); 674 connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID);
668 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, 675 connect(game_list, &GameList::NavigateToGamedbEntryRequested, this,
669 &GMainWindow::OnGameListNavigateToGamedbEntry); 676 &GMainWindow::OnGameListNavigateToGamedbEntry);
677 connect(game_list, &GameList::AddDirectory, this, &GMainWindow::OnGameListAddDirectory);
678 connect(game_list_placeholder, &GameListPlaceholder::AddDirectory, this,
679 &GMainWindow::OnGameListAddDirectory);
680 connect(game_list, &GameList::ShowList, this, &GMainWindow::OnGameListShowList);
681
670 connect(game_list, &GameList::OpenPerGameGeneralRequested, this, 682 connect(game_list, &GameList::OpenPerGameGeneralRequested, this,
671 &GMainWindow::OnGameListOpenPerGameProperties); 683 &GMainWindow::OnGameListOpenPerGameProperties);
672 684
@@ -684,8 +696,6 @@ void GMainWindow::ConnectMenuEvents() {
684 connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder); 696 connect(ui.action_Load_Folder, &QAction::triggered, this, &GMainWindow::OnMenuLoadFolder);
685 connect(ui.action_Install_File_NAND, &QAction::triggered, this, 697 connect(ui.action_Install_File_NAND, &QAction::triggered, this,
686 &GMainWindow::OnMenuInstallToNAND); 698 &GMainWindow::OnMenuInstallToNAND);
687 connect(ui.action_Select_Game_List_Root, &QAction::triggered, this,
688 &GMainWindow::OnMenuSelectGameListRoot);
689 connect(ui.action_Select_NAND_Directory, &QAction::triggered, this, 699 connect(ui.action_Select_NAND_Directory, &QAction::triggered, this,
690 [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); }); 700 [this] { OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget::NAND); });
691 connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this, 701 connect(ui.action_Select_SDMC_Directory, &QAction::triggered, this,
@@ -950,6 +960,7 @@ void GMainWindow::BootGame(const QString& filename) {
950 // Update the GUI 960 // Update the GUI
951 if (ui.action_Single_Window_Mode->isChecked()) { 961 if (ui.action_Single_Window_Mode->isChecked()) {
952 game_list->hide(); 962 game_list->hide();
963 game_list_placeholder->hide();
953 } 964 }
954 status_bar_update_timer.start(2000); 965 status_bar_update_timer.start(2000);
955 966
@@ -1007,7 +1018,10 @@ void GMainWindow::ShutdownGame() {
1007 render_window->hide(); 1018 render_window->hide();
1008 loading_screen->hide(); 1019 loading_screen->hide();
1009 loading_screen->Clear(); 1020 loading_screen->Clear();
1010 game_list->show(); 1021 if (game_list->isEmpty())
1022 game_list_placeholder->show();
1023 else
1024 game_list->show();
1011 game_list->setFilterFocus(); 1025 game_list->setFilterFocus();
1012 1026
1013 UpdateWindowTitle(); 1027 UpdateWindowTitle();
@@ -1298,6 +1312,47 @@ void GMainWindow::OnGameListNavigateToGamedbEntry(u64 program_id,
1298 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory)); 1312 QDesktopServices::openUrl(QUrl(QStringLiteral("https://yuzu-emu.org/game/") + directory));
1299} 1313}
1300 1314
1315void GMainWindow::OnGameListOpenDirectory(const QString& directory) {
1316 QString path;
1317 if (directory == QStringLiteral("SDMC")) {
1318 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) +
1319 "Nintendo/Contents/registered");
1320 } else if (directory == QStringLiteral("UserNAND")) {
1321 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
1322 "user/Contents/registered");
1323 } else if (directory == QStringLiteral("SysNAND")) {
1324 path = QString::fromStdString(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
1325 "system/Contents/registered");
1326 } else {
1327 path = directory;
1328 }
1329 if (!QFileInfo::exists(path)) {
1330 QMessageBox::critical(this, tr("Error Opening %1").arg(path), tr("Folder does not exist!"));
1331 return;
1332 }
1333 QDesktopServices::openUrl(QUrl::fromLocalFile(path));
1334}
1335
1336void GMainWindow::OnGameListAddDirectory() {
1337 const QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
1338 if (dir_path.isEmpty())
1339 return;
1340 UISettings::GameDir game_dir{dir_path, false, true};
1341 if (!UISettings::values.game_dirs.contains(game_dir)) {
1342 UISettings::values.game_dirs.append(game_dir);
1343 game_list->PopulateAsync(UISettings::values.game_dirs);
1344 } else {
1345 LOG_WARNING(Frontend, "Selected directory is already in the game list");
1346 }
1347}
1348
1349void GMainWindow::OnGameListShowList(bool show) {
1350 if (emulation_running && ui.action_Single_Window_Mode->isChecked())
1351 return;
1352 game_list->setVisible(show);
1353 game_list_placeholder->setVisible(!show);
1354};
1355
1301void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) { 1356void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1302 u64 title_id{}; 1357 u64 title_id{};
1303 const auto v_file = Core::GetGameFileFromPath(vfs, file); 1358 const auto v_file = Core::GetGameFileFromPath(vfs, file);
@@ -1316,8 +1371,7 @@ void GMainWindow::OnGameListOpenPerGameProperties(const std::string& file) {
1316 1371
1317 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); 1372 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1318 if (reload) { 1373 if (reload) {
1319 game_list->PopulateAsync(UISettings::values.game_directory_path, 1374 game_list->PopulateAsync(UISettings::values.game_dirs);
1320 UISettings::values.game_directory_deepscan);
1321 } 1375 }
1322 1376
1323 config->Save(); 1377 config->Save();
@@ -1407,8 +1461,7 @@ void GMainWindow::OnMenuInstallToNAND() {
1407 const auto success = [this]() { 1461 const auto success = [this]() {
1408 QMessageBox::information(this, tr("Successfully Installed"), 1462 QMessageBox::information(this, tr("Successfully Installed"),
1409 tr("The file was successfully installed.")); 1463 tr("The file was successfully installed."));
1410 game_list->PopulateAsync(UISettings::values.game_directory_path, 1464 game_list->PopulateAsync(UISettings::values.game_dirs);
1411 UISettings::values.game_directory_deepscan);
1412 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) + 1465 FileUtil::DeleteDirRecursively(FileUtil::GetUserPath(FileUtil::UserPath::CacheDir) +
1413 DIR_SEP + "game_list"); 1466 DIR_SEP + "game_list");
1414 }; 1467 };
@@ -1533,14 +1586,6 @@ void GMainWindow::OnMenuInstallToNAND() {
1533 } 1586 }
1534} 1587}
1535 1588
1536void GMainWindow::OnMenuSelectGameListRoot() {
1537 QString dir_path = QFileDialog::getExistingDirectory(this, tr("Select Directory"));
1538 if (!dir_path.isEmpty()) {
1539 UISettings::values.game_directory_path = dir_path;
1540 game_list->PopulateAsync(dir_path, UISettings::values.game_directory_deepscan);
1541 }
1542}
1543
1544void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) { 1589void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target) {
1545 const auto res = QMessageBox::information( 1590 const auto res = QMessageBox::information(
1546 this, tr("Changing Emulated Directory"), 1591 this, tr("Changing Emulated Directory"),
@@ -1559,8 +1604,7 @@ void GMainWindow::OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target)
1559 : FileUtil::UserPath::NANDDir, 1604 : FileUtil::UserPath::NANDDir,
1560 dir_path.toStdString()); 1605 dir_path.toStdString());
1561 Service::FileSystem::CreateFactories(*vfs); 1606 Service::FileSystem::CreateFactories(*vfs);
1562 game_list->PopulateAsync(UISettings::values.game_directory_path, 1607 game_list->PopulateAsync(UISettings::values.game_dirs);
1563 UISettings::values.game_directory_deepscan);
1564 } 1608 }
1565} 1609}
1566 1610
@@ -1724,11 +1768,11 @@ void GMainWindow::OnConfigure() {
1724 if (UISettings::values.enable_discord_presence != old_discord_presence) { 1768 if (UISettings::values.enable_discord_presence != old_discord_presence) {
1725 SetDiscordEnabled(UISettings::values.enable_discord_presence); 1769 SetDiscordEnabled(UISettings::values.enable_discord_presence);
1726 } 1770 }
1771 emit UpdateThemedIcons();
1727 1772
1728 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false); 1773 const auto reload = UISettings::values.is_game_list_reload_pending.exchange(false);
1729 if (reload) { 1774 if (reload) {
1730 game_list->PopulateAsync(UISettings::values.game_directory_path, 1775 game_list->PopulateAsync(UISettings::values.game_dirs);
1731 UISettings::values.game_directory_deepscan);
1732 } 1776 }
1733 1777
1734 config->Save(); 1778 config->Save();
@@ -1992,8 +2036,7 @@ void GMainWindow::OnReinitializeKeys(ReinitializeKeyBehavior behavior) {
1992 Service::FileSystem::CreateFactories(*vfs); 2036 Service::FileSystem::CreateFactories(*vfs);
1993 2037
1994 if (behavior == ReinitializeKeyBehavior::Warning) { 2038 if (behavior == ReinitializeKeyBehavior::Warning) {
1995 game_list->PopulateAsync(UISettings::values.game_directory_path, 2039 game_list->PopulateAsync(UISettings::values.game_dirs);
1996 UISettings::values.game_directory_deepscan);
1997 } 2040 }
1998} 2041}
1999 2042
@@ -2158,7 +2201,6 @@ void GMainWindow::UpdateUITheme() {
2158 } 2201 }
2159 2202
2160 QIcon::setThemeSearchPaths(theme_paths); 2203 QIcon::setThemeSearchPaths(theme_paths);
2161 emit UpdateThemedIcons();
2162} 2204}
2163 2205
2164void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) { 2206void GMainWindow::SetDiscordEnabled([[maybe_unused]] bool state) {
@@ -2187,6 +2229,14 @@ int main(int argc, char* argv[]) {
2187 QCoreApplication::setOrganizationName(QStringLiteral("yuzu team")); 2229 QCoreApplication::setOrganizationName(QStringLiteral("yuzu team"));
2188 QCoreApplication::setApplicationName(QStringLiteral("yuzu")); 2230 QCoreApplication::setApplicationName(QStringLiteral("yuzu"));
2189 2231
2232#ifdef __APPLE__
2233 // If you start a bundle (binary) on OSX without the Terminal, the working directory is "/".
2234 // But since we require the working directory to be the executable path for the location of the
2235 // user folder in the Qt Frontend, we need to cd into that working directory
2236 const std::string bin_path = FileUtil::GetBundleDirectory() + DIR_SEP + "..";
2237 chdir(bin_path.c_str());
2238#endif
2239
2190 // Enables the core to make the qt created contexts current on std::threads 2240 // Enables the core to make the qt created contexts current on std::threads
2191 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity); 2241 QCoreApplication::setAttribute(Qt::AA_DontCheckOpenGLContextThreadAffinity);
2192 QApplication app(argc, argv); 2242 QApplication app(argc, argv);
diff --git a/src/yuzu/main.h b/src/yuzu/main.h
index 501608ddc..7d16188cb 100644
--- a/src/yuzu/main.h
+++ b/src/yuzu/main.h
@@ -30,6 +30,7 @@ class ProfilerWidget;
30class QLabel; 30class QLabel;
31class WaitTreeWidget; 31class WaitTreeWidget;
32enum class GameListOpenTarget; 32enum class GameListOpenTarget;
33class GameListPlaceholder;
33 34
34namespace Core::Frontend { 35namespace Core::Frontend {
35struct SoftwareKeyboardParameters; 36struct SoftwareKeyboardParameters;
@@ -186,12 +187,13 @@ private slots:
186 void OnGameListCopyTID(u64 program_id); 187 void OnGameListCopyTID(u64 program_id);
187 void OnGameListNavigateToGamedbEntry(u64 program_id, 188 void OnGameListNavigateToGamedbEntry(u64 program_id,
188 const CompatibilityList& compatibility_list); 189 const CompatibilityList& compatibility_list);
190 void OnGameListOpenDirectory(const QString& directory);
191 void OnGameListAddDirectory();
192 void OnGameListShowList(bool show);
189 void OnGameListOpenPerGameProperties(const std::string& file); 193 void OnGameListOpenPerGameProperties(const std::string& file);
190 void OnMenuLoadFile(); 194 void OnMenuLoadFile();
191 void OnMenuLoadFolder(); 195 void OnMenuLoadFolder();
192 void OnMenuInstallToNAND(); 196 void OnMenuInstallToNAND();
193 /// Called whenever a user selects the "File->Select Game List Root" menu item
194 void OnMenuSelectGameListRoot();
195 /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card 197 /// Called whenever a user select the "File->Select -- Directory" where -- is NAND or SD Card
196 void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target); 198 void OnMenuSelectEmulatedDirectory(EmulatedDirectoryTarget target);
197 void OnMenuRecentFile(); 199 void OnMenuRecentFile();
@@ -223,6 +225,8 @@ private:
223 GameList* game_list; 225 GameList* game_list;
224 LoadingScreen* loading_screen; 226 LoadingScreen* loading_screen;
225 227
228 GameListPlaceholder* game_list_placeholder;
229
226 // Status bar elements 230 // Status bar elements
227 QLabel* message_label = nullptr; 231 QLabel* message_label = nullptr;
228 QLabel* emu_speed_label = nullptr; 232 QLabel* emu_speed_label = nullptr;
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui
index ffcabb495..a1ce3c0c3 100644
--- a/src/yuzu/main.ui
+++ b/src/yuzu/main.ui
@@ -62,7 +62,6 @@
62 <addaction name="action_Load_File"/> 62 <addaction name="action_Load_File"/>
63 <addaction name="action_Load_Folder"/> 63 <addaction name="action_Load_Folder"/>
64 <addaction name="separator"/> 64 <addaction name="separator"/>
65 <addaction name="action_Select_Game_List_Root"/>
66 <addaction name="menu_recent_files"/> 65 <addaction name="menu_recent_files"/>
67 <addaction name="separator"/> 66 <addaction name="separator"/>
68 <addaction name="action_Select_NAND_Directory"/> 67 <addaction name="action_Select_NAND_Directory"/>
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h
index a62cd6911..c57290006 100644
--- a/src/yuzu/uisettings.h
+++ b/src/yuzu/uisettings.h
@@ -8,8 +8,10 @@
8#include <atomic> 8#include <atomic>
9#include <vector> 9#include <vector>
10#include <QByteArray> 10#include <QByteArray>
11#include <QMetaType>
11#include <QString> 12#include <QString>
12#include <QStringList> 13#include <QStringList>
14#include <QVector>
13#include "common/common_types.h" 15#include "common/common_types.h"
14 16
15namespace UISettings { 17namespace UISettings {
@@ -25,6 +27,18 @@ struct Shortcut {
25using Themes = std::array<std::pair<const char*, const char*>, 2>; 27using Themes = std::array<std::pair<const char*, const char*>, 2>;
26extern const Themes themes; 28extern const Themes themes;
27 29
30struct GameDir {
31 QString path;
32 bool deep_scan;
33 bool expanded;
34 bool operator==(const GameDir& rhs) const {
35 return path == rhs.path;
36 };
37 bool operator!=(const GameDir& rhs) const {
38 return !operator==(rhs);
39 };
40};
41
28struct Values { 42struct Values {
29 QByteArray geometry; 43 QByteArray geometry;
30 QByteArray state; 44 QByteArray state;
@@ -55,8 +69,9 @@ struct Values {
55 QString roms_path; 69 QString roms_path;
56 QString symbols_path; 70 QString symbols_path;
57 QString screenshot_path; 71 QString screenshot_path;
58 QString game_directory_path; 72 QString game_dir_deprecated;
59 bool game_directory_deepscan; 73 bool game_dir_deprecated_deepscan;
74 QVector<UISettings::GameDir> game_dirs;
60 QStringList recent_files; 75 QStringList recent_files;
61 76
62 QString theme; 77 QString theme;
@@ -84,3 +99,5 @@ struct Values {
84 99
85extern Values values; 100extern Values values;
86} // namespace UISettings 101} // namespace UISettings
102
103Q_DECLARE_METATYPE(UISettings::GameDir*);