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/Vulkan-Headers0
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/acc/acc.cpp124
-rw-r--r--src/core/hle/service/acc/acc.h1
-rw-r--r--src/core/hle/service/acc/acc_su.cpp2
-rw-r--r--src/core/hle/service/acc/profile_manager.cpp11
-rw-r--r--src/core/hle/service/acc/profile_manager.h2
-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/engines/shader_bytecode.h36
-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.cpp1087
-rw-r--r--src/video_core/renderer_opengl/gl_shader_disk_cache.cpp19
-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/renderer_vulkan/vk_device.cpp301
-rw-r--r--src/video_core/renderer_vulkan/vk_device.h62
-rw-r--r--src/video_core/renderer_vulkan/vk_shader_decompiler.cpp48
-rw-r--r--src/video_core/shader/decode/half_set_predicate.cpp19
-rw-r--r--src/video_core/shader/decode/image.cpp104
-rw-r--r--src/video_core/shader/decode/shift.cpp19
-rw-r--r--src/video_core/shader/node.h83
-rw-r--r--src/video_core/shader/shader_ir.h14
-rw-r--r--src/video_core/surface.cpp20
-rw-r--r--src/video_core/surface.h2
-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
126 files changed, 3597 insertions, 1382 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/Vulkan-Headers b/externals/Vulkan-Headers
Subproject d05c8df88da98ec1ab3bc600d7f5783b4060895 Subproject fd568d51ed3d9bc6132e1639d7492453a08fe1b
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/acc/acc.cpp b/src/core/hle/service/acc/acc.cpp
index c01ee3eda..a7c55e116 100644
--- a/src/core/hle/service/acc/acc.cpp
+++ b/src/core/hle/service/acc/acc.cpp
@@ -31,6 +31,9 @@
31 31
32namespace Service::Account { 32namespace Service::Account {
33 33
34constexpr ResultCode ERR_INVALID_BUFFER_SIZE{ErrorModule::Account, 30};
35constexpr ResultCode ERR_FAILED_SAVE_DATA{ErrorModule::Account, 100};
36
34static std::string GetImagePath(Common::UUID uuid) { 37static std::string GetImagePath(Common::UUID uuid) {
35 return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 38 return FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
36 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg"; 39 "/system/save/8000000000000010/su/avators/" + uuid.FormatSwitch() + ".jpg";
@@ -41,20 +44,31 @@ static constexpr u32 SanitizeJPEGSize(std::size_t size) {
41 return static_cast<u32>(std::min(size, max_jpeg_image_size)); 44 return static_cast<u32>(std::min(size, max_jpeg_image_size));
42} 45}
43 46
44class IProfile final : public ServiceFramework<IProfile> { 47class IProfileCommon : public ServiceFramework<IProfileCommon> {
45public: 48public:
46 explicit IProfile(Common::UUID user_id, ProfileManager& profile_manager) 49 explicit IProfileCommon(const char* name, bool editor_commands, Common::UUID user_id,
47 : ServiceFramework("IProfile"), profile_manager(profile_manager), user_id(user_id) { 50 ProfileManager& profile_manager)
51 : ServiceFramework(name), profile_manager(profile_manager), user_id(user_id) {
48 static const FunctionInfo functions[] = { 52 static const FunctionInfo functions[] = {
49 {0, &IProfile::Get, "Get"}, 53 {0, &IProfileCommon::Get, "Get"},
50 {1, &IProfile::GetBase, "GetBase"}, 54 {1, &IProfileCommon::GetBase, "GetBase"},
51 {10, &IProfile::GetImageSize, "GetImageSize"}, 55 {10, &IProfileCommon::GetImageSize, "GetImageSize"},
52 {11, &IProfile::LoadImage, "LoadImage"}, 56 {11, &IProfileCommon::LoadImage, "LoadImage"},
53 }; 57 };
58
54 RegisterHandlers(functions); 59 RegisterHandlers(functions);
60
61 if (editor_commands) {
62 static const FunctionInfo editor_functions[] = {
63 {100, &IProfileCommon::Store, "Store"},
64 {101, &IProfileCommon::StoreWithImage, "StoreWithImage"},
65 };
66
67 RegisterHandlers(editor_functions);
68 }
55 } 69 }
56 70
57private: 71protected:
58 void Get(Kernel::HLERequestContext& ctx) { 72 void Get(Kernel::HLERequestContext& ctx) {
59 LOG_INFO(Service_ACC, "called user_id={}", user_id.Format()); 73 LOG_INFO(Service_ACC, "called user_id={}", user_id.Format());
60 ProfileBase profile_base{}; 74 ProfileBase profile_base{};
@@ -127,10 +141,91 @@ private:
127 } 141 }
128 } 142 }
129 143
130 const ProfileManager& profile_manager; 144 void Store(Kernel::HLERequestContext& ctx) {
145 IPC::RequestParser rp{ctx};
146 const auto base = rp.PopRaw<ProfileBase>();
147
148 const auto user_data = ctx.ReadBuffer();
149
150 LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}",
151 Common::StringFromFixedZeroTerminatedBuffer(
152 reinterpret_cast<const char*>(base.username.data()), base.username.size()),
153 base.timestamp, base.user_uuid.Format());
154
155 if (user_data.size() < sizeof(ProfileData)) {
156 LOG_ERROR(Service_ACC, "ProfileData buffer too small!");
157 IPC::ResponseBuilder rb{ctx, 2};
158 rb.Push(ERR_INVALID_BUFFER_SIZE);
159 return;
160 }
161
162 ProfileData data;
163 std::memcpy(&data, user_data.data(), sizeof(ProfileData));
164
165 if (!profile_manager.SetProfileBaseAndData(user_id, base, data)) {
166 LOG_ERROR(Service_ACC, "Failed to update profile data and base!");
167 IPC::ResponseBuilder rb{ctx, 2};
168 rb.Push(ERR_FAILED_SAVE_DATA);
169 return;
170 }
171
172 IPC::ResponseBuilder rb{ctx, 2};
173 rb.Push(RESULT_SUCCESS);
174 }
175
176 void StoreWithImage(Kernel::HLERequestContext& ctx) {
177 IPC::RequestParser rp{ctx};
178 const auto base = rp.PopRaw<ProfileBase>();
179
180 const auto user_data = ctx.ReadBuffer();
181 const auto image_data = ctx.ReadBuffer(1);
182
183 LOG_DEBUG(Service_ACC, "called, username='{}', timestamp={:016X}, uuid={}",
184 Common::StringFromFixedZeroTerminatedBuffer(
185 reinterpret_cast<const char*>(base.username.data()), base.username.size()),
186 base.timestamp, base.user_uuid.Format());
187
188 if (user_data.size() < sizeof(ProfileData)) {
189 LOG_ERROR(Service_ACC, "ProfileData buffer too small!");
190 IPC::ResponseBuilder rb{ctx, 2};
191 rb.Push(ERR_INVALID_BUFFER_SIZE);
192 return;
193 }
194
195 ProfileData data;
196 std::memcpy(&data, user_data.data(), sizeof(ProfileData));
197
198 FileUtil::IOFile image(GetImagePath(user_id), "wb");
199
200 if (!image.IsOpen() || !image.Resize(image_data.size()) ||
201 image.WriteBytes(image_data.data(), image_data.size()) != image_data.size() ||
202 !profile_manager.SetProfileBaseAndData(user_id, base, data)) {
203 LOG_ERROR(Service_ACC, "Failed to update profile data, base, and image!");
204 IPC::ResponseBuilder rb{ctx, 2};
205 rb.Push(ERR_FAILED_SAVE_DATA);
206 return;
207 }
208
209 IPC::ResponseBuilder rb{ctx, 2};
210 rb.Push(RESULT_SUCCESS);
211 }
212
213 ProfileManager& profile_manager;
131 Common::UUID user_id; ///< The user id this profile refers to. 214 Common::UUID user_id; ///< The user id this profile refers to.
132}; 215};
133 216
217class IProfile final : public IProfileCommon {
218public:
219 IProfile(Common::UUID user_id, ProfileManager& profile_manager)
220 : IProfileCommon("IProfile", false, user_id, profile_manager) {}
221};
222
223class IProfileEditor final : public IProfileCommon {
224public:
225 IProfileEditor(Common::UUID user_id, ProfileManager& profile_manager)
226 : IProfileCommon("IProfileEditor", true, user_id, profile_manager) {}
227};
228
134class IManagerForApplication final : public ServiceFramework<IManagerForApplication> { 229class IManagerForApplication final : public ServiceFramework<IManagerForApplication> {
135public: 230public:
136 IManagerForApplication() : ServiceFramework("IManagerForApplication") { 231 IManagerForApplication() : ServiceFramework("IManagerForApplication") {
@@ -322,6 +417,17 @@ void Module::Interface::IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx
322 rb.Push(is_locked); 417 rb.Push(is_locked);
323} 418}
324 419
420void Module::Interface::GetProfileEditor(Kernel::HLERequestContext& ctx) {
421 IPC::RequestParser rp{ctx};
422 Common::UUID user_id = rp.PopRaw<Common::UUID>();
423
424 LOG_DEBUG(Service_ACC, "called, user_id={}", user_id.Format());
425
426 IPC::ResponseBuilder rb{ctx, 2, 0, 1};
427 rb.Push(RESULT_SUCCESS);
428 rb.PushIpcInterface<IProfileEditor>(user_id, *profile_manager);
429}
430
325void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) { 431void Module::Interface::TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx) {
326 LOG_DEBUG(Service_ACC, "called"); 432 LOG_DEBUG(Service_ACC, "called");
327 // A u8 is passed into this function which we can safely ignore. It's to determine if we have 433 // A u8 is passed into this function which we can safely ignore. It's to determine if we have
diff --git a/src/core/hle/service/acc/acc.h b/src/core/hle/service/acc/acc.h
index f651773b7..7a7dc9ec6 100644
--- a/src/core/hle/service/acc/acc.h
+++ b/src/core/hle/service/acc/acc.h
@@ -32,6 +32,7 @@ public:
32 void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx); 32 void IsUserRegistrationRequestPermitted(Kernel::HLERequestContext& ctx);
33 void TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx); 33 void TrySelectUserWithoutInteraction(Kernel::HLERequestContext& ctx);
34 void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx); 34 void IsUserAccountSwitchLocked(Kernel::HLERequestContext& ctx);
35 void GetProfileEditor(Kernel::HLERequestContext& ctx);
35 36
36 private: 37 private:
37 ResultCode InitializeApplicationInfoBase(u64 process_id); 38 ResultCode InitializeApplicationInfoBase(u64 process_id);
diff --git a/src/core/hle/service/acc/acc_su.cpp b/src/core/hle/service/acc/acc_su.cpp
index 1b7ec3ed0..0d1663657 100644
--- a/src/core/hle/service/acc/acc_su.cpp
+++ b/src/core/hle/service/acc/acc_su.cpp
@@ -41,7 +41,7 @@ ACC_SU::ACC_SU(std::shared_ptr<Module> module, std::shared_ptr<ProfileManager> p
41 {202, nullptr, "CancelUserRegistration"}, 41 {202, nullptr, "CancelUserRegistration"},
42 {203, nullptr, "DeleteUser"}, 42 {203, nullptr, "DeleteUser"},
43 {204, nullptr, "SetUserPosition"}, 43 {204, nullptr, "SetUserPosition"},
44 {205, nullptr, "GetProfileEditor"}, 44 {205, &ACC_SU::GetProfileEditor, "GetProfileEditor"},
45 {206, nullptr, "CompleteUserRegistrationForcibly"}, 45 {206, nullptr, "CompleteUserRegistrationForcibly"},
46 {210, nullptr, "CreateFloatingRegistrationRequest"}, 46 {210, nullptr, "CreateFloatingRegistrationRequest"},
47 {230, nullptr, "AuthenticateServiceAsync"}, 47 {230, nullptr, "AuthenticateServiceAsync"},
diff --git a/src/core/hle/service/acc/profile_manager.cpp b/src/core/hle/service/acc/profile_manager.cpp
index 49aa5908b..8f9986326 100644
--- a/src/core/hle/service/acc/profile_manager.cpp
+++ b/src/core/hle/service/acc/profile_manager.cpp
@@ -305,6 +305,17 @@ bool ProfileManager::SetProfileBase(UUID uuid, const ProfileBase& profile_new) {
305 return true; 305 return true;
306} 306}
307 307
308bool ProfileManager::SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
309 const ProfileData& data_new) {
310 const auto index = GetUserIndex(uuid);
311 if (index.has_value() && SetProfileBase(uuid, profile_new)) {
312 profiles[*index].data = data_new;
313 return true;
314 }
315
316 return false;
317}
318
308void ProfileManager::ParseUserSaveFile() { 319void ProfileManager::ParseUserSaveFile() {
309 FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) + 320 FileUtil::IOFile save(FileUtil::GetUserPath(FileUtil::UserPath::NANDDir) +
310 ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat", 321 ACC_SAVE_AVATORS_BASE_PATH + "profiles.dat",
diff --git a/src/core/hle/service/acc/profile_manager.h b/src/core/hle/service/acc/profile_manager.h
index fd7abb541..5a6d28925 100644
--- a/src/core/hle/service/acc/profile_manager.h
+++ b/src/core/hle/service/acc/profile_manager.h
@@ -91,6 +91,8 @@ public:
91 91
92 bool RemoveUser(Common::UUID uuid); 92 bool RemoveUser(Common::UUID uuid);
93 bool SetProfileBase(Common::UUID uuid, const ProfileBase& profile_new); 93 bool SetProfileBase(Common::UUID uuid, const ProfileBase& profile_new);
94 bool SetProfileBaseAndData(Common::UUID uuid, const ProfileBase& profile_new,
95 const ProfileData& data_new);
94 96
95private: 97private:
96 void ParseUserSaveFile(); 98 void ParseUserSaveFile();
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 5f7738e7b..5d516cdb3 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 95d434b40..e5ec90717 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;
@@ -1309,9 +1310,10 @@ private:
1309 /** 1310 /**
1310 * Call a macro on this engine. 1311 * Call a macro on this engine.
1311 * @param method Method to call 1312 * @param method Method to call
1313 * @param num_parameters Number of arguments
1312 * @param parameters Arguments to the method call 1314 * @param parameters Arguments to the method call
1313 */ 1315 */
1314 void CallMacroMethod(u32 method, std::vector<u32> parameters); 1316 void CallMacroMethod(u32 method, std::size_t num_parameters, const u32* parameters);
1315 1317
1316 /// Handles writes to the macro uploading register. 1318 /// Handles writes to the macro uploading register.
1317 void ProcessMacroUpload(u32 data); 1319 void ProcessMacroUpload(u32 data);
diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index c3678b9ea..052e6d24e 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -544,6 +544,28 @@ enum class VoteOperation : u64 {
544 Eq = 2, // allThreadsEqualNV 544 Eq = 2, // allThreadsEqualNV
545}; 545};
546 546
547enum class ImageAtomicSize : u64 {
548 U32 = 0,
549 S32 = 1,
550 U64 = 2,
551 F32 = 3,
552 S64 = 5,
553 SD32 = 6,
554 SD64 = 7,
555};
556
557enum class ImageAtomicOperation : u64 {
558 Add = 0,
559 Min = 1,
560 Max = 2,
561 Inc = 3,
562 Dec = 4,
563 And = 5,
564 Or = 6,
565 Xor = 7,
566 Exch = 8,
567};
568
547union Instruction { 569union Instruction {
548 Instruction& operator=(const Instruction& instr) { 570 Instruction& operator=(const Instruction& instr) {
549 value = instr.value; 571 value = instr.value;
@@ -675,6 +697,10 @@ union Instruction {
675 } shift; 697 } shift;
676 698
677 union { 699 union {
700 BitField<39, 1, u64> wrap;
701 } shr;
702
703 union {
678 BitField<39, 5, u64> shift_amount; 704 BitField<39, 5, u64> shift_amount;
679 BitField<48, 1, u64> negate_b; 705 BitField<48, 1, u64> negate_b;
680 BitField<49, 1, u64> negate_a; 706 BitField<49, 1, u64> negate_a;
@@ -1388,6 +1414,14 @@ union Instruction {
1388 } sust; 1414 } sust;
1389 1415
1390 union { 1416 union {
1417 BitField<28, 1, u64> is_ba;
1418 BitField<51, 3, ImageAtomicSize> size;
1419 BitField<33, 3, ImageType> image_type;
1420 BitField<29, 4, ImageAtomicOperation> operation;
1421 BitField<49, 2, OutOfBoundsStore> out_of_bounds_store;
1422 } suatom_d;
1423
1424 union {
1391 BitField<20, 24, u64> target; 1425 BitField<20, 24, u64> target;
1392 BitField<5, 1, u64> constant_buffer; 1426 BitField<5, 1, u64> constant_buffer;
1393 1427
@@ -1539,6 +1573,7 @@ public:
1539 TMML_B, // Texture Mip Map Level 1573 TMML_B, // Texture Mip Map Level
1540 TMML, // Texture Mip Map Level 1574 TMML, // Texture Mip Map Level
1541 SUST, // Surface Store 1575 SUST, // Surface Store
1576 SUATOM, // Surface Atomic Operation
1542 EXIT, 1577 EXIT,
1543 NOP, 1578 NOP,
1544 IPA, 1579 IPA,
@@ -1822,6 +1857,7 @@ private:
1822 INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"), 1857 INST("110111110110----", Id::TMML_B, Type::Texture, "TMML_B"),
1823 INST("1101111101011---", Id::TMML, Type::Texture, "TMML"), 1858 INST("1101111101011---", Id::TMML, Type::Texture, "TMML"),
1824 INST("11101011001-----", Id::SUST, Type::Image, "SUST"), 1859 INST("11101011001-----", Id::SUST, Type::Image, "SUST"),
1860 INST("1110101000------", Id::SUATOM, Type::Image, "SUATOM_D"),
1825 INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"), 1861 INST("0101000010110---", Id::NOP, Type::Trivial, "NOP"),
1826 INST("11100000--------", Id::IPA, Type::Trivial, "IPA"), 1862 INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
1827 INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"), 1863 INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
diff --git a/src/video_core/macro_interpreter.cpp b/src/video_core/macro_interpreter.cpp
index a891e412a..62afc0d11 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;
@@ -227,7 +234,8 @@ void MacroInterpreter::ProcessResult(ResultOperation operation, u32 reg, u32 res
227} 234}
228 235
229u32 MacroInterpreter::FetchParameter() { 236u32 MacroInterpreter::FetchParameter() {
230 return parameters.at(next_parameter_index++); 237 ASSERT(next_parameter_index < num_parameters);
238 return parameters[next_parameter_index++];
231} 239}
232 240
233u32 MacroInterpreter::GetRegister(u32 register_id) const { 241u32 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..137b23740 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,8 +705,8 @@ 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 char* image_type = [&] {
564 switch (image.GetType()) { 710 switch (image.GetType()) {
565 case Tegra::Shader::ImageType::Texture1D: 711 case Tegra::Shader::ImageType::Texture1D:
566 return "image1D"; 712 return "image1D";
@@ -579,9 +725,33 @@ private:
579 return "image1D"; 725 return "image1D";
580 } 726 }
581 }(); 727 }();
582 code.AddLine("layout (binding = IMAGE_BINDING_{}) coherent volatile writeonly uniform " 728
729 const auto [type_prefix, format] = [&]() -> std::pair<const char*, const char*> {
730 if (!image.IsSizeKnown()) {
731 return {"", ""};
732 }
733 switch (image.GetSize()) {
734 case Tegra::Shader::ImageAtomicSize::U32:
735 return {"u", "r32ui, "};
736 case Tegra::Shader::ImageAtomicSize::S32:
737 return {"i", "r32i, "};
738 default:
739 UNIMPLEMENTED_MSG("Unimplemented atomic size={}",
740 static_cast<u32>(image.GetSize()));
741 return {"", ""};
742 }
743 }();
744
745 std::string qualifier = "coherent volatile";
746 if (image.IsRead() && !image.IsWritten()) {
747 qualifier += " readonly";
748 } else if (image.IsWritten() && !image.IsRead()) {
749 qualifier += " writeonly";
750 }
751
752 code.AddLine("layout (binding = IMAGE_BINDING_{}) {} uniform "
583 "{} {};", 753 "{} {};",
584 image.GetIndex(), image_type, GetImage(image)); 754 image.GetIndex(), qualifier, image_type, GetImage(image));
585 } 755 }
586 if (!images.empty()) { 756 if (!images.empty()) {
587 code.AddNewLine(); 757 code.AddNewLine();
@@ -590,13 +760,11 @@ private:
590 760
591 void VisitBlock(const NodeBlock& bb) { 761 void VisitBlock(const NodeBlock& bb) {
592 for (const auto& node : bb) { 762 for (const auto& node : bb) {
593 if (const std::string expr = Visit(node); !expr.empty()) { 763 Visit(node).CheckVoid();
594 code.AddLine(expr);
595 }
596 } 764 }
597 } 765 }
598 766
599 std::string Visit(const Node& node) { 767 Expression Visit(const Node& node) {
600 if (const auto operation = std::get_if<OperationNode>(&*node)) { 768 if (const auto operation = std::get_if<OperationNode>(&*node)) {
601 const auto operation_index = static_cast<std::size_t>(operation->GetCode()); 769 const auto operation_index = static_cast<std::size_t>(operation->GetCode());
602 if (operation_index >= operation_decompilers.size()) { 770 if (operation_index >= operation_decompilers.size()) {
@@ -614,18 +782,18 @@ private:
614 if (const auto gpr = std::get_if<GprNode>(&*node)) { 782 if (const auto gpr = std::get_if<GprNode>(&*node)) {
615 const u32 index = gpr->GetIndex(); 783 const u32 index = gpr->GetIndex();
616 if (index == Register::ZeroIndex) { 784 if (index == Register::ZeroIndex) {
617 return "0"; 785 return {"0U", Type::Uint};
618 } 786 }
619 return GetRegister(index); 787 return {GetRegister(index), Type::Float};
620 } 788 }
621 789
622 if (const auto immediate = std::get_if<ImmediateNode>(&*node)) { 790 if (const auto immediate = std::get_if<ImmediateNode>(&*node)) {
623 const u32 value = immediate->GetValue(); 791 const u32 value = immediate->GetValue();
624 if (value < 10) { 792 if (value < 10) {
625 // For eyecandy avoid using hex numbers on single digits 793 // For eyecandy avoid using hex numbers on single digits
626 return fmt::format("utof({}u)", immediate->GetValue()); 794 return {fmt::format("{}U", immediate->GetValue()), Type::Uint};
627 } 795 }
628 return fmt::format("utof(0x{:x}u)", immediate->GetValue()); 796 return {fmt::format("0x{:X}U", immediate->GetValue()), Type::Uint};
629 } 797 }
630 798
631 if (const auto predicate = std::get_if<PredicateNode>(&*node)) { 799 if (const auto predicate = std::get_if<PredicateNode>(&*node)) {
@@ -640,17 +808,18 @@ private:
640 } 808 }
641 }(); 809 }();
642 if (predicate->IsNegated()) { 810 if (predicate->IsNegated()) {
643 return fmt::format("!({})", value); 811 return {fmt::format("!({})", value), Type::Bool};
644 } 812 }
645 return value; 813 return {value, Type::Bool};
646 } 814 }
647 815
648 if (const auto abuf = std::get_if<AbufNode>(&*node)) { 816 if (const auto abuf = std::get_if<AbufNode>(&*node)) {
649 UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry, 817 UNIMPLEMENTED_IF_MSG(abuf->IsPhysicalBuffer() && stage == ProgramType::Geometry,
650 "Physical attributes in geometry shaders are not implemented"); 818 "Physical attributes in geometry shaders are not implemented");
651 if (abuf->IsPhysicalBuffer()) { 819 if (abuf->IsPhysicalBuffer()) {
652 return fmt::format("readPhysicalAttribute(ftou({}))", 820 return {fmt::format("ReadPhysicalAttribute({})",
653 Visit(abuf->GetPhysicalAddress())); 821 Visit(abuf->GetPhysicalAddress()).AsUint()),
822 Type::Float};
654 } 823 }
655 return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer()); 824 return ReadAttribute(abuf->GetIndex(), abuf->GetElement(), abuf->GetBuffer());
656 } 825 }
@@ -661,59 +830,64 @@ private:
661 // Direct access 830 // Direct access
662 const u32 offset_imm = immediate->GetValue(); 831 const u32 offset_imm = immediate->GetValue();
663 ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access"); 832 ASSERT_MSG(offset_imm % 4 == 0, "Unaligned cbuf direct access");
664 return fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()), 833 return {fmt::format("{}[{}][{}]", GetConstBuffer(cbuf->GetIndex()),
665 offset_imm / (4 * 4), (offset_imm / 4) % 4); 834 offset_imm / (4 * 4), (offset_imm / 4) % 4),
835 Type::Uint};
666 } 836 }
667 837
668 if (std::holds_alternative<OperationNode>(*offset)) { 838 if (std::holds_alternative<OperationNode>(*offset)) {
669 // Indirect access 839 // Indirect access
670 const std::string final_offset = code.GenerateTemporary(); 840 const std::string final_offset = code.GenerateTemporary();
671 code.AddLine("uint {} = ftou({}) >> 2;", final_offset, Visit(offset)); 841 code.AddLine("uint {} = {} >> 2;", final_offset, Visit(offset).AsUint());
672 842
673 if (!device.HasComponentIndexingBug()) { 843 if (!device.HasComponentIndexingBug()) {
674 return fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()), 844 return {fmt::format("{}[{} >> 2][{} & 3]", GetConstBuffer(cbuf->GetIndex()),
675 final_offset, final_offset); 845 final_offset, final_offset),
846 Type::Uint};
676 } 847 }
677 848
678 // AMD's proprietary GLSL compiler emits ill code for variable component access. 849 // 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. 850 // To bypass this driver bug generate 4 ifs, one per each component.
680 const std::string pack = code.GenerateTemporary(); 851 const std::string pack = code.GenerateTemporary();
681 code.AddLine("vec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()), 852 code.AddLine("uvec4 {} = {}[{} >> 2];", pack, GetConstBuffer(cbuf->GetIndex()),
682 final_offset); 853 final_offset);
683 854
684 const std::string result = code.GenerateTemporary(); 855 const std::string result = code.GenerateTemporary();
685 code.AddLine("float {};", result); 856 code.AddLine("uint {};", result);
686 for (u32 swizzle = 0; swizzle < 4; ++swizzle) { 857 for (u32 swizzle = 0; swizzle < 4; ++swizzle) {
687 code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result, 858 code.AddLine("if (({} & 3) == {}) {} = {}{};", final_offset, swizzle, result,
688 pack, GetSwizzle(swizzle)); 859 pack, GetSwizzle(swizzle));
689 } 860 }
690 return result; 861 return {result, Type::Uint};
691 } 862 }
692 863
693 UNREACHABLE_MSG("Unmanaged offset node type"); 864 UNREACHABLE_MSG("Unmanaged offset node type");
694 } 865 }
695 866
696 if (const auto gmem = std::get_if<GmemNode>(&*node)) { 867 if (const auto gmem = std::get_if<GmemNode>(&*node)) {
697 const std::string real = Visit(gmem->GetRealAddress()); 868 const std::string real = Visit(gmem->GetRealAddress()).AsUint();
698 const std::string base = Visit(gmem->GetBaseAddress()); 869 const std::string base = Visit(gmem->GetBaseAddress()).AsUint();
699 const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); 870 const std::string final_offset = fmt::format("({} - {}) >> 2", real, base);
700 return fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); 871 return {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset),
872 Type::Uint};
701 } 873 }
702 874
703 if (const auto lmem = std::get_if<LmemNode>(&*node)) { 875 if (const auto lmem = std::get_if<LmemNode>(&*node)) {
704 if (stage == ProgramType::Compute) { 876 if (stage == ProgramType::Compute) {
705 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); 877 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders");
706 } 878 }
707 return fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); 879 return {
880 fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()),
881 Type::Uint};
708 } 882 }
709 883
710 if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) { 884 if (const auto internal_flag = std::get_if<InternalFlagNode>(&*node)) {
711 return GetInternalFlag(internal_flag->GetFlag()); 885 return {GetInternalFlag(internal_flag->GetFlag()), Type::Bool};
712 } 886 }
713 887
714 if (const auto conditional = std::get_if<ConditionalNode>(&*node)) { 888 if (const auto conditional = std::get_if<ConditionalNode>(&*node)) {
715 // It's invalid to call conditional on nested nodes, use an operation instead 889 // It's invalid to call conditional on nested nodes, use an operation instead
716 code.AddLine("if ({}) {{", Visit(conditional->GetCondition())); 890 code.AddLine("if ({}) {{", Visit(conditional->GetCondition()).AsBool());
717 ++code.scope; 891 ++code.scope;
718 892
719 VisitBlock(conditional->GetCode()); 893 VisitBlock(conditional->GetCode());
@@ -724,20 +898,21 @@ private:
724 } 898 }
725 899
726 if (const auto comment = std::get_if<CommentNode>(&*node)) { 900 if (const auto comment = std::get_if<CommentNode>(&*node)) {
727 return "// " + comment->GetText(); 901 code.AddLine("// " + comment->GetText());
902 return {};
728 } 903 }
729 904
730 UNREACHABLE(); 905 UNREACHABLE();
731 return {}; 906 return {};
732 } 907 }
733 908
734 std::string ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) { 909 Expression ReadAttribute(Attribute::Index attribute, u32 element, const Node& buffer = {}) {
735 const auto GeometryPass = [&](std::string_view name) { 910 const auto GeometryPass = [&](std::string_view name) {
736 if (stage == ProgramType::Geometry && buffer) { 911 if (stage == ProgramType::Geometry && buffer) {
737 // TODO(Rodrigo): Guard geometry inputs against out of bound reads. Some games 912 // 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 913 // set an 0x80000000 index for those and the shader fails to build. Find out why
739 // this happens and what's its intent. 914 // this happens and what's its intent.
740 return fmt::format("gs_{}[ftou({}) % MAX_VERTEX_INPUT]", name, Visit(buffer)); 915 return fmt::format("gs_{}[{} % MAX_VERTEX_INPUT]", name, Visit(buffer).AsUint());
741 } 916 }
742 return std::string(name); 917 return std::string(name);
743 }; 918 };
@@ -746,25 +921,27 @@ private:
746 case Attribute::Index::Position: 921 case Attribute::Index::Position:
747 switch (stage) { 922 switch (stage) {
748 case ProgramType::Geometry: 923 case ProgramType::Geometry:
749 return fmt::format("gl_in[ftou({})].gl_Position{}", Visit(buffer), 924 return {fmt::format("gl_in[{}].gl_Position{}", Visit(buffer).AsUint(),
750 GetSwizzle(element)); 925 GetSwizzle(element)),
926 Type::Float};
751 case ProgramType::Fragment: 927 case ProgramType::Fragment:
752 return element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)); 928 return {element == 3 ? "1.0f" : ("gl_FragCoord"s + GetSwizzle(element)),
929 Type::Float};
753 default: 930 default:
754 UNREACHABLE(); 931 UNREACHABLE();
755 } 932 }
756 case Attribute::Index::PointCoord: 933 case Attribute::Index::PointCoord:
757 switch (element) { 934 switch (element) {
758 case 0: 935 case 0:
759 return "gl_PointCoord.x"; 936 return {"gl_PointCoord.x", Type::Float};
760 case 1: 937 case 1:
761 return "gl_PointCoord.y"; 938 return {"gl_PointCoord.y", Type::Float};
762 case 2: 939 case 2:
763 case 3: 940 case 3:
764 return "0"; 941 return {"0.0f", Type::Float};
765 } 942 }
766 UNREACHABLE(); 943 UNREACHABLE();
767 return "0"; 944 return {"0", Type::Int};
768 case Attribute::Index::TessCoordInstanceIDVertexID: 945 case Attribute::Index::TessCoordInstanceIDVertexID:
769 // TODO(Subv): Find out what the values are for the first two elements when inside a 946 // 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 947 // vertex shader, and what's the value of the fourth element when inside a Tess Eval
@@ -773,44 +950,49 @@ private:
773 switch (element) { 950 switch (element) {
774 case 2: 951 case 2:
775 // Config pack's first value is instance_id. 952 // Config pack's first value is instance_id.
776 return "uintBitsToFloat(config_pack[0])"; 953 return {"config_pack[0]", Type::Uint};
777 case 3: 954 case 3:
778 return "uintBitsToFloat(gl_VertexID)"; 955 return {"gl_VertexID", Type::Int};
779 } 956 }
780 UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element); 957 UNIMPLEMENTED_MSG("Unmanaged TessCoordInstanceIDVertexID element={}", element);
781 return "0"; 958 return {"0", Type::Int};
782 case Attribute::Index::FrontFacing: 959 case Attribute::Index::FrontFacing:
783 // TODO(Subv): Find out what the values are for the other elements. 960 // TODO(Subv): Find out what the values are for the other elements.
784 ASSERT(stage == ProgramType::Fragment); 961 ASSERT(stage == ProgramType::Fragment);
785 switch (element) { 962 switch (element) {
786 case 3: 963 case 3:
787 return "itof(gl_FrontFacing ? -1 : 0)"; 964 return {"(gl_FrontFacing ? -1 : 0)", Type::Int};
788 } 965 }
789 UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element); 966 UNIMPLEMENTED_MSG("Unmanaged FrontFacing element={}", element);
790 return "0"; 967 return {"0", Type::Int};
791 default: 968 default:
792 if (IsGenericAttribute(attribute)) { 969 if (IsGenericAttribute(attribute)) {
793 return GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element); 970 return {GeometryPass(GetInputAttribute(attribute)) + GetSwizzle(element),
971 Type::Float};
794 } 972 }
795 break; 973 break;
796 } 974 }
797 UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute)); 975 UNIMPLEMENTED_MSG("Unhandled input attribute: {}", static_cast<u32>(attribute));
798 return "0"; 976 return {"0", Type::Int};
799 } 977 }
800 978
801 std::string ApplyPrecise(Operation operation, const std::string& value) { 979 Expression ApplyPrecise(Operation operation, std::string value, Type type) {
802 if (!IsPrecise(operation)) { 980 if (!IsPrecise(operation)) {
803 return value; 981 return {std::move(value), type};
804 } 982 }
805 // There's a bug in NVidia's proprietary drivers that makes precise fail on fragment shaders 983 // 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 " : ""; 984 // be found in fragment shaders, so we disable precise there. There are vertex shaders that
985 // also fail to build but nobody seems to care about those.
986 // Note: Only bugged drivers will skip precise.
987 const bool disable_precise = device.HasPreciseBug() && stage == ProgramType::Fragment;
807 988
808 const std::string temporary = code.GenerateTemporary(); 989 std::string temporary = code.GenerateTemporary();
809 code.AddLine("{}float {} = {};", precise, temporary, value); 990 code.AddLine("{}{} {} = {};", disable_precise ? "" : "precise ", GetTypeString(type),
810 return temporary; 991 temporary, value);
992 return {std::move(temporary), type};
811 } 993 }
812 994
813 std::string VisitOperand(Operation operation, std::size_t operand_index) { 995 Expression VisitOperand(Operation operation, std::size_t operand_index) {
814 const auto& operand = operation[operand_index]; 996 const auto& operand = operation[operand_index];
815 const bool parent_precise = IsPrecise(operation); 997 const bool parent_precise = IsPrecise(operation);
816 const bool child_precise = IsPrecise(operand); 998 const bool child_precise = IsPrecise(operand);
@@ -819,19 +1001,16 @@ private:
819 return Visit(operand); 1001 return Visit(operand);
820 } 1002 }
821 1003
822 const std::string temporary = code.GenerateTemporary(); 1004 Expression value = Visit(operand);
823 code.AddLine("float {} = {};", temporary, Visit(operand)); 1005 std::string temporary = code.GenerateTemporary();
824 return temporary; 1006 code.AddLine("{} {} = {};", GetTypeString(value.GetType()), temporary, value.GetCode());
825 } 1007 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 } 1008 }
830 1009
831 std::optional<std::pair<std::string, bool>> GetOutputAttribute(const AbufNode* abuf) { 1010 Expression GetOutputAttribute(const AbufNode* abuf) {
832 switch (const auto attribute = abuf->GetIndex()) { 1011 switch (const auto attribute = abuf->GetIndex()) {
833 case Attribute::Index::Position: 1012 case Attribute::Index::Position:
834 return std::make_pair("gl_Position"s + GetSwizzle(abuf->GetElement()), false); 1013 return {"gl_Position"s + GetSwizzle(abuf->GetElement()), Type::Float};
835 case Attribute::Index::LayerViewportPointSize: 1014 case Attribute::Index::LayerViewportPointSize:
836 switch (abuf->GetElement()) { 1015 switch (abuf->GetElement()) {
837 case 0: 1016 case 0:
@@ -841,119 +1020,79 @@ private:
841 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { 1020 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) {
842 return {}; 1021 return {};
843 } 1022 }
844 return std::make_pair("gl_Layer", true); 1023 return {"gl_Layer", Type::Int};
845 case 2: 1024 case 2:
846 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) { 1025 if (IsVertexShader(stage) && !device.HasVertexViewportLayer()) {
847 return {}; 1026 return {};
848 } 1027 }
849 return std::make_pair("gl_ViewportIndex", true); 1028 return {"gl_ViewportIndex", Type::Int};
850 case 3: 1029 case 3:
851 UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader"); 1030 UNIMPLEMENTED_MSG("Requires some state changes for gl_PointSize to work in shader");
852 return std::make_pair("gl_PointSize", false); 1031 return {"gl_PointSize", Type::Float};
853 } 1032 }
854 return {}; 1033 return {};
855 case Attribute::Index::ClipDistances0123: 1034 case Attribute::Index::ClipDistances0123:
856 return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), false); 1035 return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement()), Type::Float};
857 case Attribute::Index::ClipDistances4567: 1036 case Attribute::Index::ClipDistances4567:
858 return std::make_pair(fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), 1037 return {fmt::format("gl_ClipDistance[{}]", abuf->GetElement() + 4), Type::Float};
859 false);
860 default: 1038 default:
861 if (IsGenericAttribute(attribute)) { 1039 if (IsGenericAttribute(attribute)) {
862 return std::make_pair( 1040 return {GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()),
863 GetOutputAttribute(attribute) + GetSwizzle(abuf->GetElement()), false); 1041 Type::Float};
864 } 1042 }
865 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute)); 1043 UNIMPLEMENTED_MSG("Unhandled output attribute: {}", static_cast<u32>(attribute));
866 return {}; 1044 return {};
867 } 1045 }
868 } 1046 }
869 1047
870 std::string CastOperand(const std::string& value, Type type) const { 1048 Expression GenerateUnary(Operation operation, std::string_view func, Type result_type,
871 switch (type) { 1049 Type type_a) {
872 case Type::Bool: 1050 std::string op_str = fmt::format("{}({})", func, VisitOperand(operation, 0).As(type_a));
873 case Type::Bool2: 1051 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 }
886
887 std::string BitwiseCastResult(const std::string& value, Type type,
888 bool needs_parenthesis = false) {
889 switch (type) {
890 case Type::Bool:
891 case Type::Bool2:
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 } 1052 }
914 1053
915 std::string GenerateBinaryInfix(Operation operation, const std::string& func, Type result_type, 1054 Expression GenerateBinaryInfix(Operation operation, std::string_view func, Type result_type,
916 Type type_a, Type type_b) { 1055 Type type_a, Type type_b) {
917 const std::string op_a = VisitOperand(operation, 0, type_a); 1056 const std::string op_a = VisitOperand(operation, 0).As(type_a);
918 const std::string op_b = VisitOperand(operation, 1, type_b); 1057 const std::string op_b = VisitOperand(operation, 1).As(type_b);
919 const std::string op_str = fmt::format("({} {} {})", op_a, func, op_b); 1058 std::string op_str = fmt::format("({} {} {})", op_a, func, op_b);
920 1059
921 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1060 return ApplyPrecise(operation, std::move(op_str), result_type);
922 } 1061 }
923 1062
924 std::string GenerateBinaryCall(Operation operation, const std::string& func, Type result_type, 1063 Expression GenerateBinaryCall(Operation operation, std::string_view func, Type result_type,
925 Type type_a, Type type_b) { 1064 Type type_a, Type type_b) {
926 const std::string op_a = VisitOperand(operation, 0, type_a); 1065 const std::string op_a = VisitOperand(operation, 0).As(type_a);
927 const std::string op_b = VisitOperand(operation, 1, type_b); 1066 const std::string op_b = VisitOperand(operation, 1).As(type_b);
928 const std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b); 1067 std::string op_str = fmt::format("{}({}, {})", func, op_a, op_b);
929 1068
930 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1069 return ApplyPrecise(operation, std::move(op_str), result_type);
931 } 1070 }
932 1071
933 std::string GenerateTernary(Operation operation, const std::string& func, Type result_type, 1072 Expression GenerateTernary(Operation operation, std::string_view func, Type result_type,
934 Type type_a, Type type_b, Type type_c) { 1073 Type type_a, Type type_b, Type type_c) {
935 const std::string op_a = VisitOperand(operation, 0, type_a); 1074 const std::string op_a = VisitOperand(operation, 0).As(type_a);
936 const std::string op_b = VisitOperand(operation, 1, type_b); 1075 const std::string op_b = VisitOperand(operation, 1).As(type_b);
937 const std::string op_c = VisitOperand(operation, 2, type_c); 1076 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); 1077 std::string op_str = fmt::format("{}({}, {}, {})", func, op_a, op_b, op_c);
939 1078
940 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1079 return ApplyPrecise(operation, std::move(op_str), result_type);
941 } 1080 }
942 1081
943 std::string GenerateQuaternary(Operation operation, const std::string& func, Type result_type, 1082 Expression GenerateQuaternary(Operation operation, const std::string& func, Type result_type,
944 Type type_a, Type type_b, Type type_c, Type type_d) { 1083 Type type_a, Type type_b, Type type_c, Type type_d) {
945 const std::string op_a = VisitOperand(operation, 0, type_a); 1084 const std::string op_a = VisitOperand(operation, 0).As(type_a);
946 const std::string op_b = VisitOperand(operation, 1, type_b); 1085 const std::string op_b = VisitOperand(operation, 1).As(type_b);
947 const std::string op_c = VisitOperand(operation, 2, type_c); 1086 const std::string op_c = VisitOperand(operation, 2).As(type_c);
948 const std::string op_d = VisitOperand(operation, 3, type_d); 1087 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); 1088 std::string op_str = fmt::format("{}({}, {}, {}, {})", func, op_a, op_b, op_c, op_d);
950 1089
951 return ApplyPrecise(operation, BitwiseCastResult(op_str, result_type)); 1090 return ApplyPrecise(operation, std::move(op_str), result_type);
952 } 1091 }
953 1092
954 std::string GenerateTexture(Operation operation, const std::string& function_suffix, 1093 std::string GenerateTexture(Operation operation, const std::string& function_suffix,
955 const std::vector<TextureIR>& extras) { 1094 const std::vector<TextureIR>& extras) {
956 constexpr std::array<const char*, 4> coord_constructors = {"float", "vec2", "vec3", "vec4"}; 1095 constexpr std::array coord_constructors = {"float", "vec2", "vec3", "vec4"};
957 1096
958 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1097 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
959 ASSERT(meta); 1098 ASSERT(meta);
@@ -970,17 +1109,17 @@ private:
970 expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1); 1109 expr += coord_constructors.at(count + (has_array ? 1 : 0) + (has_shadow ? 1 : 0) - 1);
971 expr += '('; 1110 expr += '(';
972 for (std::size_t i = 0; i < count; ++i) { 1111 for (std::size_t i = 0; i < count; ++i) {
973 expr += Visit(operation[i]); 1112 expr += Visit(operation[i]).AsFloat();
974 1113
975 const std::size_t next = i + 1; 1114 const std::size_t next = i + 1;
976 if (next < count) 1115 if (next < count)
977 expr += ", "; 1116 expr += ", ";
978 } 1117 }
979 if (has_array) { 1118 if (has_array) {
980 expr += ", float(ftoi(" + Visit(meta->array) + "))"; 1119 expr += ", float(" + Visit(meta->array).AsInt() + ')';
981 } 1120 }
982 if (has_shadow) { 1121 if (has_shadow) {
983 expr += ", " + Visit(meta->depth_compare); 1122 expr += ", " + Visit(meta->depth_compare).AsFloat();
984 } 1123 }
985 expr += ')'; 1124 expr += ')';
986 1125
@@ -1011,11 +1150,11 @@ private:
1011 // required to be constant) 1150 // required to be constant)
1012 expr += std::to_string(static_cast<s32>(immediate->GetValue())); 1151 expr += std::to_string(static_cast<s32>(immediate->GetValue()));
1013 } else { 1152 } else {
1014 expr += fmt::format("ftoi({})", Visit(operand)); 1153 expr += Visit(operand).AsInt();
1015 } 1154 }
1016 break; 1155 break;
1017 case Type::Float: 1156 case Type::Float:
1018 expr += Visit(operand); 1157 expr += Visit(operand).AsFloat();
1019 break; 1158 break;
1020 default: { 1159 default: {
1021 const auto type_int = static_cast<u32>(type); 1160 const auto type_int = static_cast<u32>(type);
@@ -1031,7 +1170,7 @@ private:
1031 if (aoffi.empty()) { 1170 if (aoffi.empty()) {
1032 return {}; 1171 return {};
1033 } 1172 }
1034 constexpr std::array<const char*, 3> coord_constructors = {"int", "ivec2", "ivec3"}; 1173 constexpr std::array coord_constructors = {"int", "ivec2", "ivec3"};
1035 std::string expr = ", "; 1174 std::string expr = ", ";
1036 expr += coord_constructors.at(aoffi.size() - 1); 1175 expr += coord_constructors.at(aoffi.size() - 1);
1037 expr += '('; 1176 expr += '(';
@@ -1044,7 +1183,7 @@ private:
1044 expr += std::to_string(static_cast<s32>(immediate->GetValue())); 1183 expr += std::to_string(static_cast<s32>(immediate->GetValue()));
1045 } else if (device.HasVariableAoffi()) { 1184 } else if (device.HasVariableAoffi()) {
1046 // Avoid using variable AOFFI on unsupported devices. 1185 // Avoid using variable AOFFI on unsupported devices.
1047 expr += fmt::format("ftoi({})", Visit(operand)); 1186 expr += Visit(operand).AsInt();
1048 } else { 1187 } else {
1049 // Insert 0 on devices not supporting variable AOFFI. 1188 // Insert 0 on devices not supporting variable AOFFI.
1050 expr += '0'; 1189 expr += '0';
@@ -1058,328 +1197,382 @@ private:
1058 return expr; 1197 return expr;
1059 } 1198 }
1060 1199
1061 std::string Assign(Operation operation) { 1200 std::string BuildIntegerCoordinates(Operation operation) {
1201 constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("};
1202 const std::size_t coords_count{operation.GetOperandsCount()};
1203 std::string expr = constructors.at(coords_count - 1);
1204 for (std::size_t i = 0; i < coords_count; ++i) {
1205 expr += VisitOperand(operation, i).AsInt();
1206 if (i + 1 < coords_count) {
1207 expr += ", ";
1208 }
1209 }
1210 expr += ')';
1211 return expr;
1212 }
1213
1214 std::string BuildImageValues(Operation operation) {
1215 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1216 const auto [constructors, type] = [&]() -> std::pair<std::array<const char*, 4>, Type> {
1217 constexpr std::array float_constructors{"float", "vec2", "vec3", "vec4"};
1218 if (!meta.image.IsSizeKnown()) {
1219 return {float_constructors, Type::Float};
1220 }
1221 switch (meta.image.GetSize()) {
1222 case Tegra::Shader::ImageAtomicSize::U32:
1223 return {{"uint", "uvec2", "uvec3", "uvec4"}, Type::Uint};
1224 case Tegra::Shader::ImageAtomicSize::S32:
1225 return {{"int", "ivec2", "ivec3", "ivec4"}, Type::Uint};
1226 default:
1227 UNIMPLEMENTED_MSG("Unimplemented image size={}",
1228 static_cast<u32>(meta.image.GetSize()));
1229 return {float_constructors, Type::Float};
1230 }
1231 }();
1232
1233 const std::size_t values_count{meta.values.size()};
1234 std::string expr = fmt::format("{}(", constructors.at(values_count - 1));
1235 for (std::size_t i = 0; i < values_count; ++i) {
1236 expr += Visit(meta.values.at(i)).As(type);
1237 if (i + 1 < values_count) {
1238 expr += ", ";
1239 }
1240 }
1241 expr += ')';
1242 return expr;
1243 }
1244
1245 Expression AtomicImage(Operation operation, const char* opname) {
1246 constexpr std::array constructors{"int(", "ivec2(", "ivec3(", "ivec4("};
1247 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1248 ASSERT(meta.values.size() == 1);
1249 ASSERT(meta.image.IsSizeKnown());
1250
1251 const auto type = [&]() {
1252 switch (const auto size = meta.image.GetSize()) {
1253 case Tegra::Shader::ImageAtomicSize::U32:
1254 return Type::Uint;
1255 case Tegra::Shader::ImageAtomicSize::S32:
1256 return Type::Int;
1257 default:
1258 UNIMPLEMENTED_MSG("Unimplemented image size={}", static_cast<u32>(size));
1259 return Type::Uint;
1260 }
1261 }();
1262
1263 return {fmt::format("{}({}, {}, {})", opname, GetImage(meta.image),
1264 BuildIntegerCoordinates(operation), Visit(meta.values[0]).As(type)),
1265 type};
1266 }
1267
1268 Expression Assign(Operation operation) {
1062 const Node& dest = operation[0]; 1269 const Node& dest = operation[0];
1063 const Node& src = operation[1]; 1270 const Node& src = operation[1];
1064 1271
1065 std::string target; 1272 Expression target;
1066 bool is_integer = false;
1067
1068 if (const auto gpr = std::get_if<GprNode>(&*dest)) { 1273 if (const auto gpr = std::get_if<GprNode>(&*dest)) {
1069 if (gpr->GetIndex() == Register::ZeroIndex) { 1274 if (gpr->GetIndex() == Register::ZeroIndex) {
1070 // Writing to Register::ZeroIndex is a no op 1275 // Writing to Register::ZeroIndex is a no op
1071 return {}; 1276 return {};
1072 } 1277 }
1073 target = GetRegister(gpr->GetIndex()); 1278 target = {GetRegister(gpr->GetIndex()), Type::Float};
1074 } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) { 1279 } else if (const auto abuf = std::get_if<AbufNode>(&*dest)) {
1075 UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer()); 1280 UNIMPLEMENTED_IF(abuf->IsPhysicalBuffer());
1076 const auto result = GetOutputAttribute(abuf); 1281 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)) { 1282 } else if (const auto lmem = std::get_if<LmemNode>(&*dest)) {
1083 if (stage == ProgramType::Compute) { 1283 if (stage == ProgramType::Compute) {
1084 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders"); 1284 LOG_WARNING(Render_OpenGL, "Local memory is stubbed on compute shaders");
1085 } 1285 }
1086 target = fmt::format("{}[ftou({}) / 4]", GetLocalMemory(), Visit(lmem->GetAddress())); 1286 target = {
1287 fmt::format("{}[{} >> 2]", GetLocalMemory(), Visit(lmem->GetAddress()).AsUint()),
1288 Type::Uint};
1087 } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) { 1289 } else if (const auto gmem = std::get_if<GmemNode>(&*dest)) {
1088 const std::string real = Visit(gmem->GetRealAddress()); 1290 const std::string real = Visit(gmem->GetRealAddress()).AsUint();
1089 const std::string base = Visit(gmem->GetBaseAddress()); 1291 const std::string base = Visit(gmem->GetBaseAddress()).AsUint();
1090 const std::string final_offset = fmt::format("(ftou({}) - ftou({})) / 4", real, base); 1292 const std::string final_offset = fmt::format("({} - {}) >> 2", real, base);
1091 target = fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset); 1293 target = {fmt::format("{}[{}]", GetGlobalMemory(gmem->GetDescriptor()), final_offset),
1294 Type::Uint};
1092 } else { 1295 } else {
1093 UNREACHABLE_MSG("Assign called without a proper target"); 1296 UNREACHABLE_MSG("Assign called without a proper target");
1094 } 1297 }
1095 1298
1096 if (is_integer) { 1299 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 {}; 1300 return {};
1102 } 1301 }
1103 1302
1104 template <Type type> 1303 template <Type type>
1105 std::string Add(Operation operation) { 1304 Expression Add(Operation operation) {
1106 return GenerateBinaryInfix(operation, "+", type, type, type); 1305 return GenerateBinaryInfix(operation, "+", type, type, type);
1107 } 1306 }
1108 1307
1109 template <Type type> 1308 template <Type type>
1110 std::string Mul(Operation operation) { 1309 Expression Mul(Operation operation) {
1111 return GenerateBinaryInfix(operation, "*", type, type, type); 1310 return GenerateBinaryInfix(operation, "*", type, type, type);
1112 } 1311 }
1113 1312
1114 template <Type type> 1313 template <Type type>
1115 std::string Div(Operation operation) { 1314 Expression Div(Operation operation) {
1116 return GenerateBinaryInfix(operation, "/", type, type, type); 1315 return GenerateBinaryInfix(operation, "/", type, type, type);
1117 } 1316 }
1118 1317
1119 template <Type type> 1318 template <Type type>
1120 std::string Fma(Operation operation) { 1319 Expression Fma(Operation operation) {
1121 return GenerateTernary(operation, "fma", type, type, type, type); 1320 return GenerateTernary(operation, "fma", type, type, type, type);
1122 } 1321 }
1123 1322
1124 template <Type type> 1323 template <Type type>
1125 std::string Negate(Operation operation) { 1324 Expression Negate(Operation operation) {
1126 return GenerateUnary(operation, "-", type, type, true); 1325 return GenerateUnary(operation, "-", type, type);
1127 } 1326 }
1128 1327
1129 template <Type type> 1328 template <Type type>
1130 std::string Absolute(Operation operation) { 1329 Expression Absolute(Operation operation) {
1131 return GenerateUnary(operation, "abs", type, type, false); 1330 return GenerateUnary(operation, "abs", type, type);
1132 } 1331 }
1133 1332
1134 std::string FClamp(Operation operation) { 1333 Expression FClamp(Operation operation) {
1135 return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float, 1334 return GenerateTernary(operation, "clamp", Type::Float, Type::Float, Type::Float,
1136 Type::Float); 1335 Type::Float);
1137 } 1336 }
1138 1337
1139 std::string FCastHalf0(Operation operation) { 1338 Expression FCastHalf0(Operation operation) {
1140 const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); 1339 return {fmt::format("({})[0]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float};
1141 return fmt::format("({})[0]", op_a);
1142 } 1340 }
1143 1341
1144 std::string FCastHalf1(Operation operation) { 1342 Expression FCastHalf1(Operation operation) {
1145 const std::string op_a = VisitOperand(operation, 0, Type::HalfFloat); 1343 return {fmt::format("({})[1]", VisitOperand(operation, 0).AsHalfFloat()), Type::Float};
1146 return fmt::format("({})[1]", op_a);
1147 } 1344 }
1148 1345
1149 template <Type type> 1346 template <Type type>
1150 std::string Min(Operation operation) { 1347 Expression Min(Operation operation) {
1151 return GenerateBinaryCall(operation, "min", type, type, type); 1348 return GenerateBinaryCall(operation, "min", type, type, type);
1152 } 1349 }
1153 1350
1154 template <Type type> 1351 template <Type type>
1155 std::string Max(Operation operation) { 1352 Expression Max(Operation operation) {
1156 return GenerateBinaryCall(operation, "max", type, type, type); 1353 return GenerateBinaryCall(operation, "max", type, type, type);
1157 } 1354 }
1158 1355
1159 std::string Select(Operation operation) { 1356 Expression Select(Operation operation) {
1160 const std::string condition = Visit(operation[0]); 1357 const std::string condition = Visit(operation[0]).AsBool();
1161 const std::string true_case = Visit(operation[1]); 1358 const std::string true_case = Visit(operation[1]).AsUint();
1162 const std::string false_case = Visit(operation[2]); 1359 const std::string false_case = Visit(operation[2]).AsUint();
1163 const std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case); 1360 std::string op_str = fmt::format("({} ? {} : {})", condition, true_case, false_case);
1164 1361
1165 return ApplyPrecise(operation, op_str); 1362 return ApplyPrecise(operation, std::move(op_str), Type::Uint);
1166 } 1363 }
1167 1364
1168 std::string FCos(Operation operation) { 1365 Expression FCos(Operation operation) {
1169 return GenerateUnary(operation, "cos", Type::Float, Type::Float, false); 1366 return GenerateUnary(operation, "cos", Type::Float, Type::Float);
1170 } 1367 }
1171 1368
1172 std::string FSin(Operation operation) { 1369 Expression FSin(Operation operation) {
1173 return GenerateUnary(operation, "sin", Type::Float, Type::Float, false); 1370 return GenerateUnary(operation, "sin", Type::Float, Type::Float);
1174 } 1371 }
1175 1372
1176 std::string FExp2(Operation operation) { 1373 Expression FExp2(Operation operation) {
1177 return GenerateUnary(operation, "exp2", Type::Float, Type::Float, false); 1374 return GenerateUnary(operation, "exp2", Type::Float, Type::Float);
1178 } 1375 }
1179 1376
1180 std::string FLog2(Operation operation) { 1377 Expression FLog2(Operation operation) {
1181 return GenerateUnary(operation, "log2", Type::Float, Type::Float, false); 1378 return GenerateUnary(operation, "log2", Type::Float, Type::Float);
1182 } 1379 }
1183 1380
1184 std::string FInverseSqrt(Operation operation) { 1381 Expression FInverseSqrt(Operation operation) {
1185 return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float, false); 1382 return GenerateUnary(operation, "inversesqrt", Type::Float, Type::Float);
1186 } 1383 }
1187 1384
1188 std::string FSqrt(Operation operation) { 1385 Expression FSqrt(Operation operation) {
1189 return GenerateUnary(operation, "sqrt", Type::Float, Type::Float, false); 1386 return GenerateUnary(operation, "sqrt", Type::Float, Type::Float);
1190 } 1387 }
1191 1388
1192 std::string FRoundEven(Operation operation) { 1389 Expression FRoundEven(Operation operation) {
1193 return GenerateUnary(operation, "roundEven", Type::Float, Type::Float, false); 1390 return GenerateUnary(operation, "roundEven", Type::Float, Type::Float);
1194 } 1391 }
1195 1392
1196 std::string FFloor(Operation operation) { 1393 Expression FFloor(Operation operation) {
1197 return GenerateUnary(operation, "floor", Type::Float, Type::Float, false); 1394 return GenerateUnary(operation, "floor", Type::Float, Type::Float);
1198 } 1395 }
1199 1396
1200 std::string FCeil(Operation operation) { 1397 Expression FCeil(Operation operation) {
1201 return GenerateUnary(operation, "ceil", Type::Float, Type::Float, false); 1398 return GenerateUnary(operation, "ceil", Type::Float, Type::Float);
1202 } 1399 }
1203 1400
1204 std::string FTrunc(Operation operation) { 1401 Expression FTrunc(Operation operation) {
1205 return GenerateUnary(operation, "trunc", Type::Float, Type::Float, false); 1402 return GenerateUnary(operation, "trunc", Type::Float, Type::Float);
1206 } 1403 }
1207 1404
1208 template <Type type> 1405 template <Type type>
1209 std::string FCastInteger(Operation operation) { 1406 Expression FCastInteger(Operation operation) {
1210 return GenerateUnary(operation, "float", Type::Float, type, false); 1407 return GenerateUnary(operation, "float", Type::Float, type);
1211 } 1408 }
1212 1409
1213 std::string ICastFloat(Operation operation) { 1410 Expression ICastFloat(Operation operation) {
1214 return GenerateUnary(operation, "int", Type::Int, Type::Float, false); 1411 return GenerateUnary(operation, "int", Type::Int, Type::Float);
1215 } 1412 }
1216 1413
1217 std::string ICastUnsigned(Operation operation) { 1414 Expression ICastUnsigned(Operation operation) {
1218 return GenerateUnary(operation, "int", Type::Int, Type::Uint, false); 1415 return GenerateUnary(operation, "int", Type::Int, Type::Uint);
1219 } 1416 }
1220 1417
1221 template <Type type> 1418 template <Type type>
1222 std::string LogicalShiftLeft(Operation operation) { 1419 Expression LogicalShiftLeft(Operation operation) {
1223 return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint); 1420 return GenerateBinaryInfix(operation, "<<", type, type, Type::Uint);
1224 } 1421 }
1225 1422
1226 std::string ILogicalShiftRight(Operation operation) { 1423 Expression ILogicalShiftRight(Operation operation) {
1227 const std::string op_a = VisitOperand(operation, 0, Type::Uint); 1424 const std::string op_a = VisitOperand(operation, 0).AsUint();
1228 const std::string op_b = VisitOperand(operation, 1, Type::Uint); 1425 const std::string op_b = VisitOperand(operation, 1).AsUint();
1229 const std::string op_str = fmt::format("int({} >> {})", op_a, op_b); 1426 std::string op_str = fmt::format("int({} >> {})", op_a, op_b);
1230 1427
1231 return ApplyPrecise(operation, BitwiseCastResult(op_str, Type::Int)); 1428 return ApplyPrecise(operation, std::move(op_str), Type::Int);
1232 } 1429 }
1233 1430
1234 std::string IArithmeticShiftRight(Operation operation) { 1431 Expression IArithmeticShiftRight(Operation operation) {
1235 return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint); 1432 return GenerateBinaryInfix(operation, ">>", Type::Int, Type::Int, Type::Uint);
1236 } 1433 }
1237 1434
1238 template <Type type> 1435 template <Type type>
1239 std::string BitwiseAnd(Operation operation) { 1436 Expression BitwiseAnd(Operation operation) {
1240 return GenerateBinaryInfix(operation, "&", type, type, type); 1437 return GenerateBinaryInfix(operation, "&", type, type, type);
1241 } 1438 }
1242 1439
1243 template <Type type> 1440 template <Type type>
1244 std::string BitwiseOr(Operation operation) { 1441 Expression BitwiseOr(Operation operation) {
1245 return GenerateBinaryInfix(operation, "|", type, type, type); 1442 return GenerateBinaryInfix(operation, "|", type, type, type);
1246 } 1443 }
1247 1444
1248 template <Type type> 1445 template <Type type>
1249 std::string BitwiseXor(Operation operation) { 1446 Expression BitwiseXor(Operation operation) {
1250 return GenerateBinaryInfix(operation, "^", type, type, type); 1447 return GenerateBinaryInfix(operation, "^", type, type, type);
1251 } 1448 }
1252 1449
1253 template <Type type> 1450 template <Type type>
1254 std::string BitwiseNot(Operation operation) { 1451 Expression BitwiseNot(Operation operation) {
1255 return GenerateUnary(operation, "~", type, type, false); 1452 return GenerateUnary(operation, "~", type, type);
1256 } 1453 }
1257 1454
1258 std::string UCastFloat(Operation operation) { 1455 Expression UCastFloat(Operation operation) {
1259 return GenerateUnary(operation, "uint", Type::Uint, Type::Float, false); 1456 return GenerateUnary(operation, "uint", Type::Uint, Type::Float);
1260 } 1457 }
1261 1458
1262 std::string UCastSigned(Operation operation) { 1459 Expression UCastSigned(Operation operation) {
1263 return GenerateUnary(operation, "uint", Type::Uint, Type::Int, false); 1460 return GenerateUnary(operation, "uint", Type::Uint, Type::Int);
1264 } 1461 }
1265 1462
1266 std::string UShiftRight(Operation operation) { 1463 Expression UShiftRight(Operation operation) {
1267 return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint); 1464 return GenerateBinaryInfix(operation, ">>", Type::Uint, Type::Uint, Type::Uint);
1268 } 1465 }
1269 1466
1270 template <Type type> 1467 template <Type type>
1271 std::string BitfieldInsert(Operation operation) { 1468 Expression BitfieldInsert(Operation operation) {
1272 return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int, 1469 return GenerateQuaternary(operation, "bitfieldInsert", type, type, type, Type::Int,
1273 Type::Int); 1470 Type::Int);
1274 } 1471 }
1275 1472
1276 template <Type type> 1473 template <Type type>
1277 std::string BitfieldExtract(Operation operation) { 1474 Expression BitfieldExtract(Operation operation) {
1278 return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int); 1475 return GenerateTernary(operation, "bitfieldExtract", type, type, Type::Int, Type::Int);
1279 } 1476 }
1280 1477
1281 template <Type type> 1478 template <Type type>
1282 std::string BitCount(Operation operation) { 1479 Expression BitCount(Operation operation) {
1283 return GenerateUnary(operation, "bitCount", type, type, false); 1480 return GenerateUnary(operation, "bitCount", type, type);
1284 } 1481 }
1285 1482
1286 std::string HNegate(Operation operation) { 1483 Expression HNegate(Operation operation) {
1287 const auto GetNegate = [&](std::size_t index) { 1484 const auto GetNegate = [&](std::size_t index) {
1288 return VisitOperand(operation, index, Type::Bool) + " ? -1 : 1"; 1485 return VisitOperand(operation, index).AsBool() + " ? -1 : 1";
1289 }; 1486 };
1290 const std::string value = 1487 return {fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0).AsHalfFloat(),
1291 fmt::format("({} * vec2({}, {}))", VisitOperand(operation, 0, Type::HalfFloat), 1488 GetNegate(1), GetNegate(2)),
1292 GetNegate(1), GetNegate(2)); 1489 Type::HalfFloat};
1293 return BitwiseCastResult(value, Type::HalfFloat); 1490 }
1294 } 1491
1295 1492 Expression HClamp(Operation operation) {
1296 std::string HClamp(Operation operation) { 1493 const std::string value = VisitOperand(operation, 0).AsHalfFloat();
1297 const std::string value = VisitOperand(operation, 0, Type::HalfFloat); 1494 const std::string min = VisitOperand(operation, 1).AsFloat();
1298 const std::string min = VisitOperand(operation, 1, Type::Float); 1495 const std::string max = VisitOperand(operation, 2).AsFloat();
1299 const std::string max = VisitOperand(operation, 2, Type::Float); 1496 std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max);
1300 const std::string clamped = fmt::format("clamp({}, vec2({}), vec2({}))", value, min, max); 1497
1301 1498 return ApplyPrecise(operation, std::move(clamped), Type::HalfFloat);
1302 return ApplyPrecise(operation, BitwiseCastResult(clamped, Type::HalfFloat));
1303 }
1304
1305 std::string HCastFloat(Operation operation) {
1306 const std::string op_a = VisitOperand(operation, 0, Type::Float);
1307 return fmt::format("fromHalf2(vec2({}, 0.0f))", op_a);
1308 }
1309
1310 std::string HUnpack(Operation operation) {
1311 const std::string operand{VisitOperand(operation, 0, Type::HalfFloat)};
1312 const auto value = [&]() -> std::string {
1313 switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) {
1314 case Tegra::Shader::HalfType::H0_H1:
1315 return operand;
1316 case Tegra::Shader::HalfType::F32:
1317 return fmt::format("vec2(fromHalf2({}))", operand);
1318 case Tegra::Shader::HalfType::H0_H0:
1319 return fmt::format("vec2({}[0])", operand);
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 } 1499 }
1328 1500
1329 std::string HMergeF32(Operation operation) { 1501 Expression HCastFloat(Operation operation) {
1330 return fmt::format("float(toHalf2({})[0])", Visit(operation[0])); 1502 return {fmt::format("vec2({})", VisitOperand(operation, 0).AsFloat()), Type::HalfFloat};
1331 } 1503 }
1332 1504
1333 std::string HMergeH0(Operation operation) { 1505 Expression HUnpack(Operation operation) {
1334 return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[1]), 1506 Expression operand = VisitOperand(operation, 0);
1335 Visit(operation[0])); 1507 switch (std::get<Tegra::Shader::HalfType>(operation.GetMeta())) {
1508 case Tegra::Shader::HalfType::H0_H1:
1509 return operand;
1510 case Tegra::Shader::HalfType::F32:
1511 return {fmt::format("vec2({})", operand.AsFloat()), Type::HalfFloat};
1512 case Tegra::Shader::HalfType::H0_H0:
1513 return {fmt::format("vec2({}[0])", operand.AsHalfFloat()), Type::HalfFloat};
1514 case Tegra::Shader::HalfType::H1_H1:
1515 return {fmt::format("vec2({}[1])", operand.AsHalfFloat()), Type::HalfFloat};
1516 }
1517 }
1518
1519 Expression HMergeF32(Operation operation) {
1520 return {fmt::format("float({}[0])", VisitOperand(operation, 0).AsHalfFloat()), Type::Float};
1336 } 1521 }
1337 1522
1338 std::string HMergeH1(Operation operation) { 1523 Expression HMergeH0(Operation operation) {
1339 return fmt::format("fromHalf2(vec2(toHalf2({})[0], toHalf2({})[1]))", Visit(operation[0]), 1524 std::string dest = VisitOperand(operation, 0).AsUint();
1340 Visit(operation[1])); 1525 std::string src = VisitOperand(operation, 1).AsUint();
1526 return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", src, dest), Type::Uint};
1341 } 1527 }
1342 1528
1343 std::string HPack2(Operation operation) { 1529 Expression HMergeH1(Operation operation) {
1344 return fmt::format("utof(packHalf2x16(vec2({}, {})))", Visit(operation[0]), 1530 std::string dest = VisitOperand(operation, 0).AsUint();
1345 Visit(operation[1])); 1531 std::string src = VisitOperand(operation, 1).AsUint();
1532 return {fmt::format("(({} & 0x0000FFFFU) | ({} & 0xFFFF0000U))", dest, src), Type::Uint};
1533 }
1534
1535 Expression HPack2(Operation operation) {
1536 return {fmt::format("vec2({}, {})", VisitOperand(operation, 0).AsFloat(),
1537 VisitOperand(operation, 1).AsFloat()),
1538 Type::HalfFloat};
1346 } 1539 }
1347 1540
1348 template <Type type> 1541 template <Type type>
1349 std::string LogicalLessThan(Operation operation) { 1542 Expression LogicalLessThan(Operation operation) {
1350 return GenerateBinaryInfix(operation, "<", Type::Bool, type, type); 1543 return GenerateBinaryInfix(operation, "<", Type::Bool, type, type);
1351 } 1544 }
1352 1545
1353 template <Type type> 1546 template <Type type>
1354 std::string LogicalEqual(Operation operation) { 1547 Expression LogicalEqual(Operation operation) {
1355 return GenerateBinaryInfix(operation, "==", Type::Bool, type, type); 1548 return GenerateBinaryInfix(operation, "==", Type::Bool, type, type);
1356 } 1549 }
1357 1550
1358 template <Type type> 1551 template <Type type>
1359 std::string LogicalLessEqual(Operation operation) { 1552 Expression LogicalLessEqual(Operation operation) {
1360 return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type); 1553 return GenerateBinaryInfix(operation, "<=", Type::Bool, type, type);
1361 } 1554 }
1362 1555
1363 template <Type type> 1556 template <Type type>
1364 std::string LogicalGreaterThan(Operation operation) { 1557 Expression LogicalGreaterThan(Operation operation) {
1365 return GenerateBinaryInfix(operation, ">", Type::Bool, type, type); 1558 return GenerateBinaryInfix(operation, ">", Type::Bool, type, type);
1366 } 1559 }
1367 1560
1368 template <Type type> 1561 template <Type type>
1369 std::string LogicalNotEqual(Operation operation) { 1562 Expression LogicalNotEqual(Operation operation) {
1370 return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type); 1563 return GenerateBinaryInfix(operation, "!=", Type::Bool, type, type);
1371 } 1564 }
1372 1565
1373 template <Type type> 1566 template <Type type>
1374 std::string LogicalGreaterEqual(Operation operation) { 1567 Expression LogicalGreaterEqual(Operation operation) {
1375 return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type); 1568 return GenerateBinaryInfix(operation, ">=", Type::Bool, type, type);
1376 } 1569 }
1377 1570
1378 std::string LogicalFIsNan(Operation operation) { 1571 Expression LogicalFIsNan(Operation operation) {
1379 return GenerateUnary(operation, "isnan", Type::Bool, Type::Float, false); 1572 return GenerateUnary(operation, "isnan", Type::Bool, Type::Float);
1380 } 1573 }
1381 1574
1382 std::string LogicalAssign(Operation operation) { 1575 Expression LogicalAssign(Operation operation) {
1383 const Node& dest = operation[0]; 1576 const Node& dest = operation[0];
1384 const Node& src = operation[1]; 1577 const Node& src = operation[1];
1385 1578
@@ -1400,78 +1593,80 @@ private:
1400 target = GetInternalFlag(flag->GetFlag()); 1593 target = GetInternalFlag(flag->GetFlag());
1401 } 1594 }
1402 1595
1403 code.AddLine("{} = {};", target, Visit(src)); 1596 code.AddLine("{} = {};", target, Visit(src).AsBool());
1404 return {}; 1597 return {};
1405 } 1598 }
1406 1599
1407 std::string LogicalAnd(Operation operation) { 1600 Expression LogicalAnd(Operation operation) {
1408 return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool); 1601 return GenerateBinaryInfix(operation, "&&", Type::Bool, Type::Bool, Type::Bool);
1409 } 1602 }
1410 1603
1411 std::string LogicalOr(Operation operation) { 1604 Expression LogicalOr(Operation operation) {
1412 return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool); 1605 return GenerateBinaryInfix(operation, "||", Type::Bool, Type::Bool, Type::Bool);
1413 } 1606 }
1414 1607
1415 std::string LogicalXor(Operation operation) { 1608 Expression LogicalXor(Operation operation) {
1416 return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool); 1609 return GenerateBinaryInfix(operation, "^^", Type::Bool, Type::Bool, Type::Bool);
1417 } 1610 }
1418 1611
1419 std::string LogicalNegate(Operation operation) { 1612 Expression LogicalNegate(Operation operation) {
1420 return GenerateUnary(operation, "!", Type::Bool, Type::Bool, false); 1613 return GenerateUnary(operation, "!", Type::Bool, Type::Bool);
1421 } 1614 }
1422 1615
1423 std::string LogicalPick2(Operation operation) { 1616 Expression LogicalPick2(Operation operation) {
1424 const std::string pair = VisitOperand(operation, 0, Type::Bool2); 1617 return {fmt::format("{}[{}]", VisitOperand(operation, 0).AsBool2(),
1425 return fmt::format("{}[{}]", pair, VisitOperand(operation, 1, Type::Uint)); 1618 VisitOperand(operation, 1).AsUint()),
1619 Type::Bool};
1426 } 1620 }
1427 1621
1428 std::string LogicalAnd2(Operation operation) { 1622 Expression LogicalAnd2(Operation operation) {
1429 return GenerateUnary(operation, "all", Type::Bool, Type::Bool2); 1623 return GenerateUnary(operation, "all", Type::Bool, Type::Bool2);
1430 } 1624 }
1431 1625
1432 template <bool with_nan> 1626 template <bool with_nan>
1433 std::string GenerateHalfComparison(Operation operation, const std::string& compare_op) { 1627 Expression GenerateHalfComparison(Operation operation, std::string_view compare_op) {
1434 const std::string comparison{GenerateBinaryCall(operation, compare_op, Type::Bool2, 1628 Expression comparison = GenerateBinaryCall(operation, compare_op, Type::Bool2,
1435 Type::HalfFloat, Type::HalfFloat)}; 1629 Type::HalfFloat, Type::HalfFloat);
1436 if constexpr (!with_nan) { 1630 if constexpr (!with_nan) {
1437 return comparison; 1631 return comparison;
1438 } 1632 }
1439 return fmt::format("halfFloatNanComparison({}, {}, {})", comparison, 1633 return {fmt::format("HalfFloatNanComparison({}, {}, {})", comparison.AsBool2(),
1440 VisitOperand(operation, 0, Type::HalfFloat), 1634 VisitOperand(operation, 0).AsHalfFloat(),
1441 VisitOperand(operation, 1, Type::HalfFloat)); 1635 VisitOperand(operation, 1).AsHalfFloat()),
1636 Type::Bool2};
1442 } 1637 }
1443 1638
1444 template <bool with_nan> 1639 template <bool with_nan>
1445 std::string Logical2HLessThan(Operation operation) { 1640 Expression Logical2HLessThan(Operation operation) {
1446 return GenerateHalfComparison<with_nan>(operation, "lessThan"); 1641 return GenerateHalfComparison<with_nan>(operation, "lessThan");
1447 } 1642 }
1448 1643
1449 template <bool with_nan> 1644 template <bool with_nan>
1450 std::string Logical2HEqual(Operation operation) { 1645 Expression Logical2HEqual(Operation operation) {
1451 return GenerateHalfComparison<with_nan>(operation, "equal"); 1646 return GenerateHalfComparison<with_nan>(operation, "equal");
1452 } 1647 }
1453 1648
1454 template <bool with_nan> 1649 template <bool with_nan>
1455 std::string Logical2HLessEqual(Operation operation) { 1650 Expression Logical2HLessEqual(Operation operation) {
1456 return GenerateHalfComparison<with_nan>(operation, "lessThanEqual"); 1651 return GenerateHalfComparison<with_nan>(operation, "lessThanEqual");
1457 } 1652 }
1458 1653
1459 template <bool with_nan> 1654 template <bool with_nan>
1460 std::string Logical2HGreaterThan(Operation operation) { 1655 Expression Logical2HGreaterThan(Operation operation) {
1461 return GenerateHalfComparison<with_nan>(operation, "greaterThan"); 1656 return GenerateHalfComparison<with_nan>(operation, "greaterThan");
1462 } 1657 }
1463 1658
1464 template <bool with_nan> 1659 template <bool with_nan>
1465 std::string Logical2HNotEqual(Operation operation) { 1660 Expression Logical2HNotEqual(Operation operation) {
1466 return GenerateHalfComparison<with_nan>(operation, "notEqual"); 1661 return GenerateHalfComparison<with_nan>(operation, "notEqual");
1467 } 1662 }
1468 1663
1469 template <bool with_nan> 1664 template <bool with_nan>
1470 std::string Logical2HGreaterEqual(Operation operation) { 1665 Expression Logical2HGreaterEqual(Operation operation) {
1471 return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual"); 1666 return GenerateHalfComparison<with_nan>(operation, "greaterThanEqual");
1472 } 1667 }
1473 1668
1474 std::string Texture(Operation operation) { 1669 Expression Texture(Operation operation) {
1475 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1670 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1476 ASSERT(meta); 1671 ASSERT(meta);
1477 1672
@@ -1480,10 +1675,10 @@ private:
1480 if (meta->sampler.IsShadow()) { 1675 if (meta->sampler.IsShadow()) {
1481 expr = "vec4(" + expr + ')'; 1676 expr = "vec4(" + expr + ')';
1482 } 1677 }
1483 return expr + GetSwizzle(meta->element); 1678 return {expr + GetSwizzle(meta->element), Type::Float};
1484 } 1679 }
1485 1680
1486 std::string TextureLod(Operation operation) { 1681 Expression TextureLod(Operation operation) {
1487 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1682 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1488 ASSERT(meta); 1683 ASSERT(meta);
1489 1684
@@ -1492,54 +1687,54 @@ private:
1492 if (meta->sampler.IsShadow()) { 1687 if (meta->sampler.IsShadow()) {
1493 expr = "vec4(" + expr + ')'; 1688 expr = "vec4(" + expr + ')';
1494 } 1689 }
1495 return expr + GetSwizzle(meta->element); 1690 return {expr + GetSwizzle(meta->element), Type::Float};
1496 } 1691 }
1497 1692
1498 std::string TextureGather(Operation operation) { 1693 Expression TextureGather(Operation operation) {
1499 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1694 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1500 ASSERT(meta); 1695 ASSERT(meta);
1501 1696
1502 const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int; 1697 const auto type = meta->sampler.IsShadow() ? Type::Float : Type::Int;
1503 return GenerateTexture(operation, "Gather", 1698 return {GenerateTexture(operation, "Gather",
1504 {TextureArgument{type, meta->component}, TextureAoffi{}}) + 1699 {TextureArgument{type, meta->component}, TextureAoffi{}}) +
1505 GetSwizzle(meta->element); 1700 GetSwizzle(meta->element),
1701 Type::Float};
1506 } 1702 }
1507 1703
1508 std::string TextureQueryDimensions(Operation operation) { 1704 Expression TextureQueryDimensions(Operation operation) {
1509 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1705 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1510 ASSERT(meta); 1706 ASSERT(meta);
1511 1707
1512 const std::string sampler = GetSampler(meta->sampler); 1708 const std::string sampler = GetSampler(meta->sampler);
1513 const std::string lod = VisitOperand(operation, 0, Type::Int); 1709 const std::string lod = VisitOperand(operation, 0).AsInt();
1514 1710
1515 switch (meta->element) { 1711 switch (meta->element) {
1516 case 0: 1712 case 0:
1517 case 1: 1713 case 1:
1518 return fmt::format("itof(int(textureSize({}, {}){}))", sampler, lod, 1714 return {fmt::format("textureSize({}, {}){}", sampler, lod, GetSwizzle(meta->element)),
1519 GetSwizzle(meta->element)); 1715 Type::Int};
1520 case 2:
1521 return "0";
1522 case 3: 1716 case 3:
1523 return fmt::format("itof(textureQueryLevels({}))", sampler); 1717 return {fmt::format("textureQueryLevels({})", sampler), Type::Int};
1524 } 1718 }
1525 UNREACHABLE(); 1719 UNREACHABLE();
1526 return "0"; 1720 return {"0", Type::Int};
1527 } 1721 }
1528 1722
1529 std::string TextureQueryLod(Operation operation) { 1723 Expression TextureQueryLod(Operation operation) {
1530 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1724 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1531 ASSERT(meta); 1725 ASSERT(meta);
1532 1726
1533 if (meta->element < 2) { 1727 if (meta->element < 2) {
1534 return fmt::format("itof(int(({} * vec2(256)){}))", 1728 return {fmt::format("int(({} * vec2(256)){})",
1535 GenerateTexture(operation, "QueryLod", {}), 1729 GenerateTexture(operation, "QueryLod", {}),
1536 GetSwizzle(meta->element)); 1730 GetSwizzle(meta->element)),
1731 Type::Int};
1537 } 1732 }
1538 return "0"; 1733 return {"0", Type::Int};
1539 } 1734 }
1540 1735
1541 std::string TexelFetch(Operation operation) { 1736 Expression TexelFetch(Operation operation) {
1542 constexpr std::array<const char*, 4> constructors = {"int", "ivec2", "ivec3", "ivec4"}; 1737 constexpr std::array constructors = {"int", "ivec2", "ivec3", "ivec4"};
1543 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta()); 1738 const auto meta = std::get_if<MetaTexture>(&operation.GetMeta());
1544 ASSERT(meta); 1739 ASSERT(meta);
1545 UNIMPLEMENTED_IF(meta->sampler.IsArray()); 1740 UNIMPLEMENTED_IF(meta->sampler.IsArray());
@@ -1552,7 +1747,7 @@ private:
1552 expr += constructors.at(operation.GetOperandsCount() - 1); 1747 expr += constructors.at(operation.GetOperandsCount() - 1);
1553 expr += '('; 1748 expr += '(';
1554 for (std::size_t i = 0; i < count; ++i) { 1749 for (std::size_t i = 0; i < count; ++i) {
1555 expr += VisitOperand(operation, i, Type::Int); 1750 expr += VisitOperand(operation, i).AsInt();
1556 const std::size_t next = i + 1; 1751 const std::size_t next = i + 1;
1557 if (next == count) 1752 if (next == count)
1558 expr += ')'; 1753 expr += ')';
@@ -1565,7 +1760,7 @@ private:
1565 1760
1566 if (meta->lod) { 1761 if (meta->lod) {
1567 expr += ", "; 1762 expr += ", ";
1568 expr += CastOperand(Visit(meta->lod), Type::Int); 1763 expr += Visit(meta->lod).AsInt();
1569 } 1764 }
1570 expr += ')'; 1765 expr += ')';
1571 expr += GetSwizzle(meta->element); 1766 expr += GetSwizzle(meta->element);
@@ -1580,88 +1775,89 @@ private:
1580 code.AddLine("float {} = {};", tmp, expr); 1775 code.AddLine("float {} = {};", tmp, expr);
1581 code.AddLine("#endif"); 1776 code.AddLine("#endif");
1582 1777
1583 return tmp; 1778 return {tmp, Type::Float};
1584 } 1779 }
1585 1780
1586 std::string ImageStore(Operation operation) { 1781 Expression ImageStore(Operation operation) {
1587 constexpr std::array<const char*, 4> constructors{"int(", "ivec2(", "ivec3(", "ivec4("};
1588 const auto meta{std::get<MetaImage>(operation.GetMeta())}; 1782 const auto meta{std::get<MetaImage>(operation.GetMeta())};
1783 code.AddLine("imageStore({}, {}, {});", GetImage(meta.image),
1784 BuildIntegerCoordinates(operation), BuildImageValues(operation));
1785 return {};
1786 }
1589 1787
1590 std::string expr = "imageStore("; 1788 Expression AtomicImageAdd(Operation operation) {
1591 expr += GetImage(meta.image); 1789 return AtomicImage(operation, "imageAtomicAdd");
1592 expr += ", "; 1790 }
1593 1791
1594 const std::size_t coords_count{operation.GetOperandsCount()}; 1792 Expression AtomicImageMin(Operation operation) {
1595 expr += constructors.at(coords_count - 1); 1793 return AtomicImage(operation, "imageAtomicMin");
1596 for (std::size_t i = 0; i < coords_count; ++i) { 1794 }
1597 expr += VisitOperand(operation, i, Type::Int);
1598 if (i + 1 < coords_count) {
1599 expr += ", ";
1600 }
1601 }
1602 expr += "), ";
1603 1795
1604 const std::size_t values_count{meta.values.size()}; 1796 Expression AtomicImageMax(Operation operation) {
1605 UNIMPLEMENTED_IF(values_count != 4); 1797 return AtomicImage(operation, "imageAtomicMax");
1606 expr += "vec4("; 1798 }
1607 for (std::size_t i = 0; i < values_count; ++i) { 1799 Expression AtomicImageAnd(Operation operation) {
1608 expr += Visit(meta.values.at(i)); 1800 return AtomicImage(operation, "imageAtomicAnd");
1609 if (i + 1 < values_count) { 1801 }
1610 expr += ", ";
1611 }
1612 }
1613 expr += "));";
1614 1802
1615 code.AddLine(expr); 1803 Expression AtomicImageOr(Operation operation) {
1616 return {}; 1804 return AtomicImage(operation, "imageAtomicOr");
1805 }
1806
1807 Expression AtomicImageXor(Operation operation) {
1808 return AtomicImage(operation, "imageAtomicXor");
1809 }
1810
1811 Expression AtomicImageExchange(Operation operation) {
1812 return AtomicImage(operation, "imageAtomicExchange");
1617 } 1813 }
1618 1814
1619 std::string Branch(Operation operation) { 1815 Expression Branch(Operation operation) {
1620 const auto target = std::get_if<ImmediateNode>(&*operation[0]); 1816 const auto target = std::get_if<ImmediateNode>(&*operation[0]);
1621 UNIMPLEMENTED_IF(!target); 1817 UNIMPLEMENTED_IF(!target);
1622 1818
1623 code.AddLine("jmp_to = 0x{:x}u;", target->GetValue()); 1819 code.AddLine("jmp_to = 0x{:X}U;", target->GetValue());
1624 code.AddLine("break;"); 1820 code.AddLine("break;");
1625 return {}; 1821 return {};
1626 } 1822 }
1627 1823
1628 std::string BranchIndirect(Operation operation) { 1824 Expression BranchIndirect(Operation operation) {
1629 const std::string op_a = VisitOperand(operation, 0, Type::Uint); 1825 const std::string op_a = VisitOperand(operation, 0).AsUint();
1630 1826
1631 code.AddLine("jmp_to = {};", op_a); 1827 code.AddLine("jmp_to = {};", op_a);
1632 code.AddLine("break;"); 1828 code.AddLine("break;");
1633 return {}; 1829 return {};
1634 } 1830 }
1635 1831
1636 std::string PushFlowStack(Operation operation) { 1832 Expression PushFlowStack(Operation operation) {
1637 const auto stack = std::get<MetaStackClass>(operation.GetMeta()); 1833 const auto stack = std::get<MetaStackClass>(operation.GetMeta());
1638 const auto target = std::get_if<ImmediateNode>(&*operation[0]); 1834 const auto target = std::get_if<ImmediateNode>(&*operation[0]);
1639 UNIMPLEMENTED_IF(!target); 1835 UNIMPLEMENTED_IF(!target);
1640 1836
1641 code.AddLine("{}[{}++] = 0x{:x}u;", FlowStackName(stack), FlowStackTopName(stack), 1837 code.AddLine("{}[{}++] = 0x{:X}U;", FlowStackName(stack), FlowStackTopName(stack),
1642 target->GetValue()); 1838 target->GetValue());
1643 return {}; 1839 return {};
1644 } 1840 }
1645 1841
1646 std::string PopFlowStack(Operation operation) { 1842 Expression PopFlowStack(Operation operation) {
1647 const auto stack = std::get<MetaStackClass>(operation.GetMeta()); 1843 const auto stack = std::get<MetaStackClass>(operation.GetMeta());
1648 code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack)); 1844 code.AddLine("jmp_to = {}[--{}];", FlowStackName(stack), FlowStackTopName(stack));
1649 code.AddLine("break;"); 1845 code.AddLine("break;");
1650 return {}; 1846 return {};
1651 } 1847 }
1652 1848
1653 std::string Exit(Operation operation) { 1849 Expression Exit(Operation operation) {
1654 if (stage != ProgramType::Fragment) { 1850 if (stage != ProgramType::Fragment) {
1655 code.AddLine("return;"); 1851 code.AddLine("return;");
1656 return {}; 1852 return {};
1657 } 1853 }
1658 const auto& used_registers = ir.GetRegisters(); 1854 const auto& used_registers = ir.GetRegisters();
1659 const auto SafeGetRegister = [&](u32 reg) -> std::string { 1855 const auto SafeGetRegister = [&](u32 reg) -> Expression {
1660 // TODO(Rodrigo): Replace with contains once C++20 releases 1856 // TODO(Rodrigo): Replace with contains once C++20 releases
1661 if (used_registers.find(reg) != used_registers.end()) { 1857 if (used_registers.find(reg) != used_registers.end()) {
1662 return GetRegister(reg); 1858 return {GetRegister(reg), Type::Float};
1663 } 1859 }
1664 return "0.0f"; 1860 return {"0.0f", Type::Float};
1665 }; 1861 };
1666 1862
1667 UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented"); 1863 UNIMPLEMENTED_IF_MSG(header.ps.omap.sample_mask != 0, "Sample mask write is unimplemented");
@@ -1674,7 +1870,7 @@ private:
1674 for (u32 component = 0; component < 4; ++component) { 1870 for (u32 component = 0; component < 4; ++component) {
1675 if (header.ps.IsColorComponentOutputEnabled(render_target, component)) { 1871 if (header.ps.IsColorComponentOutputEnabled(render_target, component)) {
1676 code.AddLine("FragColor{}[{}] = {};", render_target, component, 1872 code.AddLine("FragColor{}[{}] = {};", render_target, component,
1677 SafeGetRegister(current_reg)); 1873 SafeGetRegister(current_reg).AsFloat());
1678 ++current_reg; 1874 ++current_reg;
1679 } 1875 }
1680 } 1876 }
@@ -1683,14 +1879,14 @@ private:
1683 if (header.ps.omap.depth) { 1879 if (header.ps.omap.depth) {
1684 // The depth output is always 2 registers after the last color output, and current_reg 1880 // The depth output is always 2 registers after the last color output, and current_reg
1685 // already contains one past the last color register. 1881 // already contains one past the last color register.
1686 code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1)); 1882 code.AddLine("gl_FragDepth = {};", SafeGetRegister(current_reg + 1).AsFloat());
1687 } 1883 }
1688 1884
1689 code.AddLine("return;"); 1885 code.AddLine("return;");
1690 return {}; 1886 return {};
1691 } 1887 }
1692 1888
1693 std::string Discard(Operation operation) { 1889 Expression Discard(Operation operation) {
1694 // Enclose "discard" in a conditional, so that GLSL compilation does not complain 1890 // Enclose "discard" in a conditional, so that GLSL compilation does not complain
1695 // about unexecuted instructions that may follow this. 1891 // about unexecuted instructions that may follow this.
1696 code.AddLine("if (true) {{"); 1892 code.AddLine("if (true) {{");
@@ -1701,7 +1897,7 @@ private:
1701 return {}; 1897 return {};
1702 } 1898 }
1703 1899
1704 std::string EmitVertex(Operation operation) { 1900 Expression EmitVertex(Operation operation) {
1705 ASSERT_MSG(stage == ProgramType::Geometry, 1901 ASSERT_MSG(stage == ProgramType::Geometry,
1706 "EmitVertex is expected to be used in a geometry shader."); 1902 "EmitVertex is expected to be used in a geometry shader.");
1707 1903
@@ -1712,7 +1908,7 @@ private:
1712 return {}; 1908 return {};
1713 } 1909 }
1714 1910
1715 std::string EndPrimitive(Operation operation) { 1911 Expression EndPrimitive(Operation operation) {
1716 ASSERT_MSG(stage == ProgramType::Geometry, 1912 ASSERT_MSG(stage == ProgramType::Geometry,
1717 "EndPrimitive is expected to be used in a geometry shader."); 1913 "EndPrimitive is expected to be used in a geometry shader.");
1718 1914
@@ -1720,59 +1916,59 @@ private:
1720 return {}; 1916 return {};
1721 } 1917 }
1722 1918
1723 std::string YNegate(Operation operation) { 1919 Expression YNegate(Operation operation) {
1724 // Config pack's third value is Y_NEGATE's state. 1920 // Config pack's third value is Y_NEGATE's state.
1725 return "uintBitsToFloat(config_pack[2])"; 1921 return {"config_pack[2]", Type::Uint};
1726 } 1922 }
1727 1923
1728 template <u32 element> 1924 template <u32 element>
1729 std::string LocalInvocationId(Operation) { 1925 Expression LocalInvocationId(Operation) {
1730 return "utof(gl_LocalInvocationID"s + GetSwizzle(element) + ')'; 1926 return {"gl_LocalInvocationID"s + GetSwizzle(element), Type::Uint};
1731 } 1927 }
1732 1928
1733 template <u32 element> 1929 template <u32 element>
1734 std::string WorkGroupId(Operation) { 1930 Expression WorkGroupId(Operation) {
1735 return "utof(gl_WorkGroupID"s + GetSwizzle(element) + ')'; 1931 return {"gl_WorkGroupID"s + GetSwizzle(element), Type::Uint};
1736 } 1932 }
1737 1933
1738 std::string BallotThread(Operation operation) { 1934 Expression BallotThread(Operation operation) {
1739 const std::string value = VisitOperand(operation, 0, Type::Bool); 1935 const std::string value = VisitOperand(operation, 0).AsBool();
1740 if (!device.HasWarpIntrinsics()) { 1936 if (!device.HasWarpIntrinsics()) {
1741 LOG_ERROR(Render_OpenGL, 1937 LOG_ERROR(Render_OpenGL,
1742 "Nvidia warp intrinsics are not available and its required by a shader"); 1938 "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 1939 // Stub on non-Nvidia devices by simulating all threads voting the same as the active
1744 // one. 1940 // one.
1745 return fmt::format("utof({} ? 0xFFFFFFFFU : 0U)", value); 1941 return {fmt::format("({} ? 0xFFFFFFFFU : 0U)", value), Type::Uint};
1746 } 1942 }
1747 return fmt::format("utof(ballotThreadNV({}))", value); 1943 return {fmt::format("ballotThreadNV({})", value), Type::Uint};
1748 } 1944 }
1749 1945
1750 std::string Vote(Operation operation, const char* func) { 1946 Expression Vote(Operation operation, const char* func) {
1751 const std::string value = VisitOperand(operation, 0, Type::Bool); 1947 const std::string value = VisitOperand(operation, 0).AsBool();
1752 if (!device.HasWarpIntrinsics()) { 1948 if (!device.HasWarpIntrinsics()) {
1753 LOG_ERROR(Render_OpenGL, 1949 LOG_ERROR(Render_OpenGL,
1754 "Nvidia vote intrinsics are not available and its required by a shader"); 1950 "Nvidia vote intrinsics are not available and its required by a shader");
1755 // Stub with a warp size of one. 1951 // Stub with a warp size of one.
1756 return value; 1952 return {value, Type::Bool};
1757 } 1953 }
1758 return fmt::format("{}({})", func, value); 1954 return {fmt::format("{}({})", func, value), Type::Bool};
1759 } 1955 }
1760 1956
1761 std::string VoteAll(Operation operation) { 1957 Expression VoteAll(Operation operation) {
1762 return Vote(operation, "allThreadsNV"); 1958 return Vote(operation, "allThreadsNV");
1763 } 1959 }
1764 1960
1765 std::string VoteAny(Operation operation) { 1961 Expression VoteAny(Operation operation) {
1766 return Vote(operation, "anyThreadNV"); 1962 return Vote(operation, "anyThreadNV");
1767 } 1963 }
1768 1964
1769 std::string VoteEqual(Operation operation) { 1965 Expression VoteEqual(Operation operation) {
1770 if (!device.HasWarpIntrinsics()) { 1966 if (!device.HasWarpIntrinsics()) {
1771 LOG_ERROR(Render_OpenGL, 1967 LOG_ERROR(Render_OpenGL,
1772 "Nvidia vote intrinsics are not available and its required by a shader"); 1968 "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 1969 // 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. 1970 // return an equal result for all its votes.
1775 return "true"; 1971 return {"true", Type::Bool};
1776 } 1972 }
1777 return Vote(operation, "allThreadsEqualNV"); 1973 return Vote(operation, "allThreadsEqualNV");
1778 } 1974 }
@@ -1909,6 +2105,13 @@ private:
1909 &GLSLDecompiler::TexelFetch, 2105 &GLSLDecompiler::TexelFetch,
1910 2106
1911 &GLSLDecompiler::ImageStore, 2107 &GLSLDecompiler::ImageStore,
2108 &GLSLDecompiler::AtomicImageAdd,
2109 &GLSLDecompiler::AtomicImageMin,
2110 &GLSLDecompiler::AtomicImageMax,
2111 &GLSLDecompiler::AtomicImageAnd,
2112 &GLSLDecompiler::AtomicImageOr,
2113 &GLSLDecompiler::AtomicImageXor,
2114 &GLSLDecompiler::AtomicImageExchange,
1912 2115
1913 &GLSLDecompiler::Branch, 2116 &GLSLDecompiler::Branch,
1914 &GLSLDecompiler::BranchIndirect, 2117 &GLSLDecompiler::BranchIndirect,
@@ -1973,8 +2176,8 @@ private:
1973 } 2176 }
1974 2177
1975 std::string GetInternalFlag(InternalFlag flag) const { 2178 std::string GetInternalFlag(InternalFlag flag) const {
1976 constexpr std::array<const char*, 4> InternalFlagNames = {"zero_flag", "sign_flag", 2179 constexpr std::array InternalFlagNames = {"zero_flag", "sign_flag", "carry_flag",
1977 "carry_flag", "overflow_flag"}; 2180 "overflow_flag"};
1978 const auto index = static_cast<u32>(flag); 2181 const auto index = static_cast<u32>(flag);
1979 ASSERT(index < static_cast<u32>(InternalFlag::Amount)); 2182 ASSERT(index < static_cast<u32>(InternalFlag::Amount));
1980 2183
@@ -2022,24 +2225,16 @@ private:
2022 2225
2023std::string GetCommonDeclarations() { 2226std::string GetCommonDeclarations() {
2024 return fmt::format( 2227 return fmt::format(
2025 "#define MAX_CONSTBUFFER_ELEMENTS {}\n"
2026 "#define ftoi floatBitsToInt\n" 2228 "#define ftoi floatBitsToInt\n"
2027 "#define ftou floatBitsToUint\n" 2229 "#define ftou floatBitsToUint\n"
2028 "#define itof intBitsToFloat\n" 2230 "#define itof intBitsToFloat\n"
2029 "#define utof uintBitsToFloat\n\n" 2231 "#define utof uintBitsToFloat\n\n"
2030 "float fromHalf2(vec2 pair) {{\n" 2232 "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" 2233 " bvec2 is_nan1 = isnan(pair1);\n"
2038 " bvec2 is_nan2 = isnan(pair2);\n" 2234 " bvec2 is_nan2 = isnan(pair2);\n"
2039 " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || " 2235 " return bvec2(comparison.x || is_nan1.x || is_nan2.x, comparison.y || is_nan1.y || "
2040 "is_nan2.y);\n" 2236 "is_nan2.y);\n"
2041 "}}\n", 2237 "}}\n\n");
2042 MAX_CONSTBUFFER_ELEMENTS);
2043} 2238}
2044 2239
2045ProgramResult Decompile(const Device& device, const ShaderIR& ir, ProgramType stage, 2240ProgramResult 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..f141c4e3b 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,22 @@ 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_written{};
345 u8 is_read{};
346 u8 is_size_known{};
347 u32 size{};
344 if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) || 348 if (!LoadObjectFromPrecompiled(offset) || !LoadObjectFromPrecompiled(index) ||
345 !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless)) { 349 !LoadObjectFromPrecompiled(type) || !LoadObjectFromPrecompiled(is_bindless) ||
350 !LoadObjectFromPrecompiled(is_written) || !LoadObjectFromPrecompiled(is_read) ||
351 !LoadObjectFromPrecompiled(is_size_known) || !LoadObjectFromPrecompiled(size)) {
346 return {}; 352 return {};
347 } 353 }
348 entry.entries.images.emplace_back( 354 entry.entries.images.emplace_back(
349 static_cast<std::size_t>(offset), static_cast<std::size_t>(index), 355 static_cast<std::size_t>(offset), static_cast<std::size_t>(index),
350 static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0); 356 static_cast<Tegra::Shader::ImageType>(type), is_bindless != 0, is_written != 0,
357 is_read != 0,
358 is_size_known ? std::make_optional(static_cast<Tegra::Shader::ImageAtomicSize>(size))
359 : std::nullopt);
351 } 360 }
352 361
353 u32 global_memory_count{}; 362 u32 global_memory_count{};
@@ -426,10 +435,14 @@ bool ShaderDiskCacheOpenGL::SaveDecompiledFile(u64 unique_identifier, const std:
426 return false; 435 return false;
427 } 436 }
428 for (const auto& image : entries.images) { 437 for (const auto& image : entries.images) {
438 const u32 size = image.IsSizeKnown() ? static_cast<u32>(image.GetSize()) : 0U;
429 if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) || 439 if (!SaveObjectToPrecompiled(static_cast<u64>(image.GetOffset())) ||
430 !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) || 440 !SaveObjectToPrecompiled(static_cast<u64>(image.GetIndex())) ||
431 !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) || 441 !SaveObjectToPrecompiled(static_cast<u32>(image.GetType())) ||
432 !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0))) { 442 !SaveObjectToPrecompiled(static_cast<u8>(image.IsBindless() ? 1 : 0)) ||
443 !SaveObjectToPrecompiled(static_cast<u8>(image.IsWritten() ? 1 : 0)) ||
444 !SaveObjectToPrecompiled(static_cast<u8>(image.IsRead() ? 1 : 0)) ||
445 !SaveObjectToPrecompiled(image.IsSizeKnown()) || !SaveObjectToPrecompiled(size)) {
433 return false; 446 return false;
434 } 447 }
435 } 448 }
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/renderer_vulkan/vk_device.cpp b/src/video_core/renderer_vulkan/vk_device.cpp
index 3b966ddc3..897cbb4e8 100644
--- a/src/video_core/renderer_vulkan/vk_device.cpp
+++ b/src/video_core/renderer_vulkan/vk_device.cpp
@@ -2,9 +2,10 @@
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 <map> 5#include <bitset>
6#include <optional> 6#include <optional>
7#include <set> 7#include <set>
8#include <string_view>
8#include <vector> 9#include <vector>
9#include "common/assert.h" 10#include "common/assert.h"
10#include "video_core/renderer_vulkan/declarations.h" 11#include "video_core/renderer_vulkan/declarations.h"
@@ -12,13 +13,32 @@
12 13
13namespace Vulkan { 14namespace Vulkan {
14 15
16namespace {
17
18template <typename T>
19void SetNext(void**& next, T& data) {
20 *next = &data;
21 next = &data.pNext;
22}
23
24template <typename T>
25T GetFeatures(vk::PhysicalDevice physical, vk::DispatchLoaderDynamic dldi) {
26 vk::PhysicalDeviceFeatures2 features;
27 T extension_features;
28 features.pNext = &extension_features;
29 physical.getFeatures2(&features, dldi);
30 return extension_features;
31}
32
33} // Anonymous namespace
34
15namespace Alternatives { 35namespace Alternatives {
16 36
17constexpr std::array<vk::Format, 3> Depth24UnormS8Uint = { 37constexpr std::array Depth24UnormS8Uint = {vk::Format::eD32SfloatS8Uint,
18 vk::Format::eD32SfloatS8Uint, vk::Format::eD16UnormS8Uint, {}}; 38 vk::Format::eD16UnormS8Uint, vk::Format{}};
19constexpr std::array<vk::Format, 3> Depth16UnormS8Uint = { 39constexpr std::array Depth16UnormS8Uint = {vk::Format::eD24UnormS8Uint,
20 vk::Format::eD24UnormS8Uint, vk::Format::eD32SfloatS8Uint, {}}; 40 vk::Format::eD32SfloatS8Uint, vk::Format{}};
21constexpr std::array<vk::Format, 2> Astc = {vk::Format::eA8B8G8R8UnormPack32, {}}; 41constexpr std::array Astc = {vk::Format::eA8B8G8R8UnormPack32, vk::Format{}};
22 42
23} // namespace Alternatives 43} // namespace Alternatives
24 44
@@ -58,16 +78,53 @@ VKDevice::VKDevice(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice phy
58VKDevice::~VKDevice() = default; 78VKDevice::~VKDevice() = default;
59 79
60bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instance) { 80bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instance) {
61 vk::PhysicalDeviceFeatures device_features;
62 device_features.vertexPipelineStoresAndAtomics = true;
63 device_features.independentBlend = true;
64 device_features.textureCompressionASTC_LDR = is_optimal_astc_supported;
65
66 const auto queue_cis = GetDeviceQueueCreateInfos(); 81 const auto queue_cis = GetDeviceQueueCreateInfos();
67 const std::vector<const char*> extensions = LoadExtensions(dldi); 82 const std::vector extensions = LoadExtensions(dldi);
68 const vk::DeviceCreateInfo device_ci({}, static_cast<u32>(queue_cis.size()), queue_cis.data(), 83
69 0, nullptr, static_cast<u32>(extensions.size()), 84 vk::PhysicalDeviceFeatures2 features2;
70 extensions.data(), &device_features); 85 void** next = &features2.pNext;
86 auto& features = features2.features;
87 features.vertexPipelineStoresAndAtomics = true;
88 features.independentBlend = true;
89 features.depthClamp = true;
90 features.samplerAnisotropy = true;
91 features.largePoints = true;
92 features.textureCompressionASTC_LDR = is_optimal_astc_supported;
93
94 vk::PhysicalDeviceVertexAttributeDivisorFeaturesEXT vertex_divisor;
95 vertex_divisor.vertexAttributeInstanceRateDivisor = true;
96 vertex_divisor.vertexAttributeInstanceRateZeroDivisor = true;
97 SetNext(next, vertex_divisor);
98
99 vk::PhysicalDeviceFloat16Int8FeaturesKHR float16_int8;
100 if (is_float16_supported) {
101 float16_int8.shaderFloat16 = true;
102 SetNext(next, float16_int8);
103 } else {
104 LOG_INFO(Render_Vulkan, "Device doesn't support float16 natively");
105 }
106
107 vk::PhysicalDeviceUniformBufferStandardLayoutFeaturesKHR std430_layout;
108 if (khr_uniform_buffer_standard_layout) {
109 std430_layout.uniformBufferStandardLayout = true;
110 SetNext(next, std430_layout);
111 } else {
112 LOG_INFO(Render_Vulkan, "Device doesn't support packed UBOs");
113 }
114
115 vk::PhysicalDeviceIndexTypeUint8FeaturesEXT index_type_uint8;
116 if (ext_index_type_uint8) {
117 index_type_uint8.indexTypeUint8 = true;
118 SetNext(next, index_type_uint8);
119 } else {
120 LOG_INFO(Render_Vulkan, "Device doesn't support uint8 indexes");
121 }
122
123 vk::DeviceCreateInfo device_ci({}, static_cast<u32>(queue_cis.size()), queue_cis.data(), 0,
124 nullptr, static_cast<u32>(extensions.size()), extensions.data(),
125 nullptr);
126 device_ci.pNext = &features2;
127
71 vk::Device dummy_logical; 128 vk::Device dummy_logical;
72 if (physical.createDevice(&device_ci, nullptr, &dummy_logical, dldi) != vk::Result::eSuccess) { 129 if (physical.createDevice(&device_ci, nullptr, &dummy_logical, dldi) != vk::Result::eSuccess) {
73 LOG_CRITICAL(Render_Vulkan, "Logical device failed to be created!"); 130 LOG_CRITICAL(Render_Vulkan, "Logical device failed to be created!");
@@ -78,6 +135,17 @@ bool VKDevice::Create(const vk::DispatchLoaderDynamic& dldi, vk::Instance instan
78 logical = UniqueDevice( 135 logical = UniqueDevice(
79 dummy_logical, vk::ObjectDestroy<vk::NoParent, vk::DispatchLoaderDynamic>(nullptr, dld)); 136 dummy_logical, vk::ObjectDestroy<vk::NoParent, vk::DispatchLoaderDynamic>(nullptr, dld));
80 137
138 if (khr_driver_properties) {
139 vk::PhysicalDeviceDriverPropertiesKHR driver;
140 vk::PhysicalDeviceProperties2 properties;
141 properties.pNext = &driver;
142 physical.getProperties2(&properties, dld);
143 driver_id = driver.driverID;
144 LOG_INFO(Render_Vulkan, "Driver: {} {}", driver.driverName, driver.driverInfo);
145 } else {
146 LOG_INFO(Render_Vulkan, "Driver: Unknown");
147 }
148
81 graphics_queue = logical->getQueue(graphics_family, 0, dld); 149 graphics_queue = logical->getQueue(graphics_family, 0, dld);
82 present_queue = logical->getQueue(present_family, 0, dld); 150 present_queue = logical->getQueue(present_family, 0, dld);
83 return true; 151 return true;
@@ -92,20 +160,19 @@ vk::Format VKDevice::GetSupportedFormat(vk::Format wanted_format,
92 // The wanted format is not supported by hardware, search for alternatives 160 // The wanted format is not supported by hardware, search for alternatives
93 const vk::Format* alternatives = GetFormatAlternatives(wanted_format); 161 const vk::Format* alternatives = GetFormatAlternatives(wanted_format);
94 if (alternatives == nullptr) { 162 if (alternatives == nullptr) {
95 LOG_CRITICAL(Render_Vulkan, 163 UNREACHABLE_MSG("Format={} with usage={} and type={} has no defined alternatives and host "
96 "Format={} with usage={} and type={} has no defined alternatives and host " 164 "hardware does not support it",
97 "hardware does not support it", 165 vk::to_string(wanted_format), vk::to_string(wanted_usage),
98 vk::to_string(wanted_format), vk::to_string(wanted_usage), 166 static_cast<u32>(format_type));
99 static_cast<u32>(format_type));
100 UNREACHABLE();
101 return wanted_format; 167 return wanted_format;
102 } 168 }
103 169
104 std::size_t i = 0; 170 std::size_t i = 0;
105 for (vk::Format alternative = alternatives[0]; alternative != vk::Format{}; 171 for (vk::Format alternative = alternatives[0]; alternative != vk::Format{};
106 alternative = alternatives[++i]) { 172 alternative = alternatives[++i]) {
107 if (!IsFormatSupported(alternative, wanted_usage, format_type)) 173 if (!IsFormatSupported(alternative, wanted_usage, format_type)) {
108 continue; 174 continue;
175 }
109 LOG_WARNING(Render_Vulkan, 176 LOG_WARNING(Render_Vulkan,
110 "Emulating format={} with alternative format={} with usage={} and type={}", 177 "Emulating format={} with alternative format={} with usage={} and type={}",
111 static_cast<u32>(wanted_format), static_cast<u32>(alternative), 178 static_cast<u32>(wanted_format), static_cast<u32>(alternative),
@@ -114,12 +181,10 @@ vk::Format VKDevice::GetSupportedFormat(vk::Format wanted_format,
114 } 181 }
115 182
116 // No alternatives found, panic 183 // No alternatives found, panic
117 LOG_CRITICAL(Render_Vulkan, 184 UNREACHABLE_MSG("Format={} with usage={} and type={} is not supported by the host hardware and "
118 "Format={} with usage={} and type={} is not supported by the host hardware and " 185 "doesn't support any of the alternatives",
119 "doesn't support any of the alternatives", 186 static_cast<u32>(wanted_format), static_cast<u32>(wanted_usage),
120 static_cast<u32>(wanted_format), static_cast<u32>(wanted_usage), 187 static_cast<u32>(format_type));
121 static_cast<u32>(format_type));
122 UNREACHABLE();
123 return wanted_format; 188 return wanted_format;
124} 189}
125 190
@@ -132,7 +197,7 @@ bool VKDevice::IsOptimalAstcSupported(const vk::PhysicalDeviceFeatures& features
132 vk::FormatFeatureFlagBits::eSampledImage | vk::FormatFeatureFlagBits::eBlitSrc | 197 vk::FormatFeatureFlagBits::eSampledImage | vk::FormatFeatureFlagBits::eBlitSrc |
133 vk::FormatFeatureFlagBits::eBlitDst | vk::FormatFeatureFlagBits::eTransferSrc | 198 vk::FormatFeatureFlagBits::eBlitDst | vk::FormatFeatureFlagBits::eTransferSrc |
134 vk::FormatFeatureFlagBits::eTransferDst}; 199 vk::FormatFeatureFlagBits::eTransferDst};
135 constexpr std::array<vk::Format, 9> astc_formats = { 200 constexpr std::array astc_formats = {
136 vk::Format::eAstc4x4UnormBlock, vk::Format::eAstc4x4SrgbBlock, 201 vk::Format::eAstc4x4UnormBlock, vk::Format::eAstc4x4SrgbBlock,
137 vk::Format::eAstc8x8SrgbBlock, vk::Format::eAstc8x6SrgbBlock, 202 vk::Format::eAstc8x8SrgbBlock, vk::Format::eAstc8x6SrgbBlock,
138 vk::Format::eAstc5x4SrgbBlock, vk::Format::eAstc5x5UnormBlock, 203 vk::Format::eAstc5x4SrgbBlock, vk::Format::eAstc5x5UnormBlock,
@@ -151,76 +216,120 @@ bool VKDevice::IsFormatSupported(vk::Format wanted_format, vk::FormatFeatureFlag
151 FormatType format_type) const { 216 FormatType format_type) const {
152 const auto it = format_properties.find(wanted_format); 217 const auto it = format_properties.find(wanted_format);
153 if (it == format_properties.end()) { 218 if (it == format_properties.end()) {
154 LOG_CRITICAL(Render_Vulkan, "Unimplemented format query={}", vk::to_string(wanted_format)); 219 UNIMPLEMENTED_MSG("Unimplemented format query={}", vk::to_string(wanted_format));
155 UNREACHABLE();
156 return true; 220 return true;
157 } 221 }
158 const vk::FormatFeatureFlags supported_usage = GetFormatFeatures(it->second, format_type); 222 const auto supported_usage = GetFormatFeatures(it->second, format_type);
159 return (supported_usage & wanted_usage) == wanted_usage; 223 return (supported_usage & wanted_usage) == wanted_usage;
160} 224}
161 225
162bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical, 226bool VKDevice::IsSuitable(const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical,
163 vk::SurfaceKHR surface) { 227 vk::SurfaceKHR surface) {
164 bool has_swapchain{}; 228 LOG_INFO(Render_Vulkan, "{}", physical.getProperties(dldi).deviceName);
229 bool is_suitable = true;
230
231 constexpr std::array required_extensions = {VK_KHR_SWAPCHAIN_EXTENSION_NAME,
232 VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME};
233 std::bitset<required_extensions.size()> available_extensions{};
234
165 for (const auto& prop : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) { 235 for (const auto& prop : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) {
166 has_swapchain |= prop.extensionName == std::string(VK_KHR_SWAPCHAIN_EXTENSION_NAME); 236 for (std::size_t i = 0; i < required_extensions.size(); ++i) {
237 if (available_extensions[i]) {
238 continue;
239 }
240 available_extensions[i] =
241 required_extensions[i] == std::string_view{prop.extensionName};
242 }
167 } 243 }
168 if (!has_swapchain) { 244 if (!available_extensions.all()) {
169 // The device doesn't support creating swapchains. 245 for (std::size_t i = 0; i < required_extensions.size(); ++i) {
170 return false; 246 if (available_extensions[i]) {
247 continue;
248 }
249 LOG_INFO(Render_Vulkan, "Missing required extension: {}", required_extensions[i]);
250 is_suitable = false;
251 }
171 } 252 }
172 253
173 bool has_graphics{}, has_present{}; 254 bool has_graphics{}, has_present{};
174 const auto queue_family_properties = physical.getQueueFamilyProperties(dldi); 255 const auto queue_family_properties = physical.getQueueFamilyProperties(dldi);
175 for (u32 i = 0; i < static_cast<u32>(queue_family_properties.size()); ++i) { 256 for (u32 i = 0; i < static_cast<u32>(queue_family_properties.size()); ++i) {
176 const auto& family = queue_family_properties[i]; 257 const auto& family = queue_family_properties[i];
177 if (family.queueCount == 0) 258 if (family.queueCount == 0) {
178 continue; 259 continue;
179 260 }
180 has_graphics |= 261 has_graphics |=
181 (family.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast<vk::QueueFlagBits>(0); 262 (family.queueFlags & vk::QueueFlagBits::eGraphics) != static_cast<vk::QueueFlagBits>(0);
182 has_present |= physical.getSurfaceSupportKHR(i, surface, dldi) != 0; 263 has_present |= physical.getSurfaceSupportKHR(i, surface, dldi) != 0;
183 } 264 }
184 if (!has_graphics || !has_present) { 265 if (!has_graphics || !has_present) {
185 // The device doesn't have a graphics and present queue. 266 LOG_INFO(Render_Vulkan, "Device lacks a graphics and present queue");
186 return false; 267 is_suitable = false;
187 } 268 }
188 269
189 // TODO(Rodrigo): Check if the device matches all requeriments. 270 // TODO(Rodrigo): Check if the device matches all requeriments.
190 const auto properties{physical.getProperties(dldi)}; 271 const auto properties{physical.getProperties(dldi)};
191 const auto limits{properties.limits}; 272 const auto& limits{properties.limits};
192 if (limits.maxUniformBufferRange < 65536) { 273
193 return false; 274 constexpr u32 required_ubo_size = 65536;
275 if (limits.maxUniformBufferRange < required_ubo_size) {
276 LOG_INFO(Render_Vulkan, "Device UBO size {} is too small, {} is required)",
277 limits.maxUniformBufferRange, required_ubo_size);
278 is_suitable = false;
194 } 279 }
195 280
196 const vk::PhysicalDeviceFeatures features{physical.getFeatures(dldi)}; 281 const auto features{physical.getFeatures(dldi)};
197 if (!features.vertexPipelineStoresAndAtomics || !features.independentBlend) { 282 const std::array feature_report = {
198 return false; 283 std::make_pair(features.vertexPipelineStoresAndAtomics, "vertexPipelineStoresAndAtomics"),
284 std::make_pair(features.independentBlend, "independentBlend"),
285 std::make_pair(features.depthClamp, "depthClamp"),
286 std::make_pair(features.samplerAnisotropy, "samplerAnisotropy"),
287 std::make_pair(features.largePoints, "largePoints"),
288 };
289 for (const auto& [supported, name] : feature_report) {
290 if (supported) {
291 continue;
292 }
293 LOG_INFO(Render_Vulkan, "Missing required feature: {}", name);
294 is_suitable = false;
199 } 295 }
200 296
201 // Device is suitable. 297 return is_suitable;
202 return true;
203} 298}
204 299
205std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynamic& dldi) { 300std::vector<const char*> VKDevice::LoadExtensions(const vk::DispatchLoaderDynamic& dldi) {
206 std::vector<const char*> extensions; 301 std::vector<const char*> extensions;
207 extensions.reserve(2); 302 extensions.reserve(7);
208 extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME); 303 extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
304 extensions.push_back(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME);
209 305
210 const auto Test = [&](const vk::ExtensionProperties& extension, 306 const auto Test = [&](const vk::ExtensionProperties& extension,
211 std::optional<std::reference_wrapper<bool>> status, const char* name, 307 std::optional<std::reference_wrapper<bool>> status, const char* name,
212 u32 revision) { 308 bool push) {
213 if (extension.extensionName != std::string(name)) { 309 if (extension.extensionName != std::string_view(name)) {
214 return; 310 return;
215 } 311 }
216 extensions.push_back(name); 312 if (push) {
313 extensions.push_back(name);
314 }
217 if (status) { 315 if (status) {
218 status->get() = true; 316 status->get() = true;
219 } 317 }
220 }; 318 };
221 319
320 bool khr_shader_float16_int8{};
222 for (const auto& extension : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) { 321 for (const auto& extension : physical.enumerateDeviceExtensionProperties(nullptr, dldi)) {
223 Test(extension, ext_scalar_block_layout, VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME, 1); 322 Test(extension, khr_uniform_buffer_standard_layout,
323 VK_KHR_UNIFORM_BUFFER_STANDARD_LAYOUT_EXTENSION_NAME, true);
324 Test(extension, ext_index_type_uint8, VK_EXT_INDEX_TYPE_UINT8_EXTENSION_NAME, true);
325 Test(extension, khr_driver_properties, VK_KHR_DRIVER_PROPERTIES_EXTENSION_NAME, true);
326 Test(extension, khr_shader_float16_int8, VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME, false);
327 }
328
329 if (khr_shader_float16_int8) {
330 is_float16_supported =
331 GetFeatures<vk::PhysicalDeviceFloat16Int8FeaturesKHR>(physical, dldi).shaderFloat16;
332 extensions.push_back(VK_KHR_SHADER_FLOAT16_INT8_EXTENSION_NAME);
224 } 333 }
225 334
226 return extensions; 335 return extensions;
@@ -250,9 +359,10 @@ void VKDevice::SetupFamilies(const vk::DispatchLoaderDynamic& dldi, vk::SurfaceK
250} 359}
251 360
252void VKDevice::SetupProperties(const vk::DispatchLoaderDynamic& dldi) { 361void VKDevice::SetupProperties(const vk::DispatchLoaderDynamic& dldi) {
253 const vk::PhysicalDeviceProperties props = physical.getProperties(dldi); 362 const auto props = physical.getProperties(dldi);
254 device_type = props.deviceType; 363 device_type = props.deviceType;
255 uniform_buffer_alignment = static_cast<u64>(props.limits.minUniformBufferOffsetAlignment); 364 uniform_buffer_alignment = static_cast<u64>(props.limits.minUniformBufferOffsetAlignment);
365 storage_buffer_alignment = static_cast<u64>(props.limits.minStorageBufferOffsetAlignment);
256 max_storage_buffer_range = static_cast<u64>(props.limits.maxStorageBufferRange); 366 max_storage_buffer_range = static_cast<u64>(props.limits.maxStorageBufferRange);
257} 367}
258 368
@@ -273,42 +383,53 @@ std::vector<vk::DeviceQueueCreateInfo> VKDevice::GetDeviceQueueCreateInfos() con
273 return queue_cis; 383 return queue_cis;
274} 384}
275 385
276std::map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperties( 386std::unordered_map<vk::Format, vk::FormatProperties> VKDevice::GetFormatProperties(
277 const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical) { 387 const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical) {
278 static constexpr std::array formats{vk::Format::eA8B8G8R8UnormPack32, 388 constexpr std::array formats{vk::Format::eA8B8G8R8UnormPack32,
279 vk::Format::eB5G6R5UnormPack16, 389 vk::Format::eA8B8G8R8SnormPack32,
280 vk::Format::eA2B10G10R10UnormPack32, 390 vk::Format::eA8B8G8R8SrgbPack32,
281 vk::Format::eR32G32B32A32Sfloat, 391 vk::Format::eB5G6R5UnormPack16,
282 vk::Format::eR16G16Unorm, 392 vk::Format::eA2B10G10R10UnormPack32,
283 vk::Format::eR16G16Snorm, 393 vk::Format::eR32G32B32A32Sfloat,
284 vk::Format::eR8G8B8A8Srgb, 394 vk::Format::eR16G16B16A16Uint,
285 vk::Format::eR8Unorm, 395 vk::Format::eR16G16Unorm,
286 vk::Format::eB10G11R11UfloatPack32, 396 vk::Format::eR16G16Snorm,
287 vk::Format::eR32Sfloat, 397 vk::Format::eR16G16Sfloat,
288 vk::Format::eR16Sfloat, 398 vk::Format::eR16Unorm,
289 vk::Format::eR16G16B16A16Sfloat, 399 vk::Format::eR8G8B8A8Srgb,
290 vk::Format::eD32Sfloat, 400 vk::Format::eR8G8Unorm,
291 vk::Format::eD16Unorm, 401 vk::Format::eR8G8Snorm,
292 vk::Format::eD16UnormS8Uint, 402 vk::Format::eR8Unorm,
293 vk::Format::eD24UnormS8Uint, 403 vk::Format::eB10G11R11UfloatPack32,
294 vk::Format::eD32SfloatS8Uint, 404 vk::Format::eR32Sfloat,
295 vk::Format::eBc1RgbaUnormBlock, 405 vk::Format::eR16Sfloat,
296 vk::Format::eBc2UnormBlock, 406 vk::Format::eR16G16B16A16Sfloat,
297 vk::Format::eBc3UnormBlock, 407 vk::Format::eB8G8R8A8Unorm,
298 vk::Format::eBc4UnormBlock, 408 vk::Format::eD32Sfloat,
299 vk::Format::eBc5UnormBlock, 409 vk::Format::eD16Unorm,
300 vk::Format::eBc5SnormBlock, 410 vk::Format::eD16UnormS8Uint,
301 vk::Format::eBc7UnormBlock, 411 vk::Format::eD24UnormS8Uint,
302 vk::Format::eAstc4x4UnormBlock, 412 vk::Format::eD32SfloatS8Uint,
303 vk::Format::eAstc4x4SrgbBlock, 413 vk::Format::eBc1RgbaUnormBlock,
304 vk::Format::eAstc8x8SrgbBlock, 414 vk::Format::eBc2UnormBlock,
305 vk::Format::eAstc8x6SrgbBlock, 415 vk::Format::eBc3UnormBlock,
306 vk::Format::eAstc5x4SrgbBlock, 416 vk::Format::eBc4UnormBlock,
307 vk::Format::eAstc5x5UnormBlock, 417 vk::Format::eBc5UnormBlock,
308 vk::Format::eAstc5x5SrgbBlock, 418 vk::Format::eBc5SnormBlock,
309 vk::Format::eAstc10x8UnormBlock, 419 vk::Format::eBc7UnormBlock,
310 vk::Format::eAstc10x8SrgbBlock}; 420 vk::Format::eBc1RgbaSrgbBlock,
311 std::map<vk::Format, vk::FormatProperties> format_properties; 421 vk::Format::eBc3SrgbBlock,
422 vk::Format::eBc7SrgbBlock,
423 vk::Format::eAstc4x4UnormBlock,
424 vk::Format::eAstc4x4SrgbBlock,
425 vk::Format::eAstc8x8SrgbBlock,
426 vk::Format::eAstc8x6SrgbBlock,
427 vk::Format::eAstc5x4SrgbBlock,
428 vk::Format::eAstc5x5UnormBlock,
429 vk::Format::eAstc5x5SrgbBlock,
430 vk::Format::eAstc10x8UnormBlock,
431 vk::Format::eAstc10x8SrgbBlock};
432 std::unordered_map<vk::Format, vk::FormatProperties> format_properties;
312 for (const auto format : formats) { 433 for (const auto format : formats) {
313 format_properties.emplace(format, physical.getFormatProperties(format, dldi)); 434 format_properties.emplace(format, physical.getFormatProperties(format, dldi));
314 } 435 }
diff --git a/src/video_core/renderer_vulkan/vk_device.h b/src/video_core/renderer_vulkan/vk_device.h
index 537825d8b..010d4c3d6 100644
--- a/src/video_core/renderer_vulkan/vk_device.h
+++ b/src/video_core/renderer_vulkan/vk_device.h
@@ -4,7 +4,7 @@
4 4
5#pragma once 5#pragma once
6 6
7#include <map> 7#include <unordered_map>
8#include <vector> 8#include <vector>
9#include "common/common_types.h" 9#include "common/common_types.h"
10#include "video_core/renderer_vulkan/declarations.h" 10#include "video_core/renderer_vulkan/declarations.h"
@@ -69,16 +69,26 @@ public:
69 return present_family; 69 return present_family;
70 } 70 }
71 71
72 /// Returns if the device is integrated with the host CPU. 72 /// Returns true if the device is integrated with the host CPU.
73 bool IsIntegrated() const { 73 bool IsIntegrated() const {
74 return device_type == vk::PhysicalDeviceType::eIntegratedGpu; 74 return device_type == vk::PhysicalDeviceType::eIntegratedGpu;
75 } 75 }
76 76
77 /// Returns the driver ID.
78 vk::DriverIdKHR GetDriverID() const {
79 return driver_id;
80 }
81
77 /// Returns uniform buffer alignment requeriment. 82 /// Returns uniform buffer alignment requeriment.
78 u64 GetUniformBufferAlignment() const { 83 u64 GetUniformBufferAlignment() const {
79 return uniform_buffer_alignment; 84 return uniform_buffer_alignment;
80 } 85 }
81 86
87 /// Returns storage alignment requeriment.
88 u64 GetStorageBufferAlignment() const {
89 return storage_buffer_alignment;
90 }
91
82 /// Returns the maximum range for storage buffers. 92 /// Returns the maximum range for storage buffers.
83 u64 GetMaxStorageBufferRange() const { 93 u64 GetMaxStorageBufferRange() const {
84 return max_storage_buffer_range; 94 return max_storage_buffer_range;
@@ -89,9 +99,19 @@ public:
89 return is_optimal_astc_supported; 99 return is_optimal_astc_supported;
90 } 100 }
91 101
102 /// Returns true if the device supports float16 natively
103 bool IsFloat16Supported() const {
104 return is_float16_supported;
105 }
106
92 /// Returns true if the device supports VK_EXT_scalar_block_layout. 107 /// Returns true if the device supports VK_EXT_scalar_block_layout.
93 bool IsExtScalarBlockLayoutSupported() const { 108 bool IsKhrUniformBufferStandardLayoutSupported() const {
94 return ext_scalar_block_layout; 109 return khr_uniform_buffer_standard_layout;
110 }
111
112 /// Returns true if the device supports VK_EXT_index_type_uint8.
113 bool IsExtIndexTypeUint8Supported() const {
114 return ext_index_type_uint8;
95 } 115 }
96 116
97 /// Checks if the physical device is suitable. 117 /// Checks if the physical device is suitable.
@@ -123,22 +143,28 @@ private:
123 FormatType format_type) const; 143 FormatType format_type) const;
124 144
125 /// Returns the device properties for Vulkan formats. 145 /// Returns the device properties for Vulkan formats.
126 static std::map<vk::Format, vk::FormatProperties> GetFormatProperties( 146 static std::unordered_map<vk::Format, vk::FormatProperties> GetFormatProperties(
127 const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical); 147 const vk::DispatchLoaderDynamic& dldi, vk::PhysicalDevice physical);
128 148
129 const vk::PhysicalDevice physical; ///< Physical device. 149 const vk::PhysicalDevice physical; ///< Physical device.
130 vk::DispatchLoaderDynamic dld; ///< Device function pointers. 150 vk::DispatchLoaderDynamic dld; ///< Device function pointers.
131 UniqueDevice logical; ///< Logical device. 151 UniqueDevice logical; ///< Logical device.
132 vk::Queue graphics_queue; ///< Main graphics queue. 152 vk::Queue graphics_queue; ///< Main graphics queue.
133 vk::Queue present_queue; ///< Main present queue. 153 vk::Queue present_queue; ///< Main present queue.
134 u32 graphics_family{}; ///< Main graphics queue family index. 154 u32 graphics_family{}; ///< Main graphics queue family index.
135 u32 present_family{}; ///< Main present queue family index. 155 u32 present_family{}; ///< Main present queue family index.
136 vk::PhysicalDeviceType device_type; ///< Physical device type. 156 vk::PhysicalDeviceType device_type; ///< Physical device type.
137 u64 uniform_buffer_alignment{}; ///< Uniform buffer alignment requeriment. 157 vk::DriverIdKHR driver_id{}; ///< Driver ID.
138 u64 max_storage_buffer_range{}; ///< Max storage buffer size. 158 u64 uniform_buffer_alignment{}; ///< Uniform buffer alignment requeriment.
139 bool is_optimal_astc_supported{}; ///< Support for native ASTC. 159 u64 storage_buffer_alignment{}; ///< Storage buffer alignment requeriment.
140 bool ext_scalar_block_layout{}; ///< Support for VK_EXT_scalar_block_layout. 160 u64 max_storage_buffer_range{}; ///< Max storage buffer size.
141 std::map<vk::Format, vk::FormatProperties> format_properties; ///< Format properties dictionary. 161 bool is_optimal_astc_supported{}; ///< Support for native ASTC.
162 bool is_float16_supported{}; ///< Support for float16 arithmetics.
163 bool khr_uniform_buffer_standard_layout{}; ///< Support for std430 on UBOs.
164 bool ext_index_type_uint8{}; ///< Support for VK_EXT_index_type_uint8.
165 bool khr_driver_properties{}; ///< Support for VK_KHR_driver_properties.
166 std::unordered_map<vk::Format, vk::FormatProperties>
167 format_properties; ///< Format properties dictionary.
142}; 168};
143 169
144} // namespace Vulkan 170} // namespace Vulkan
diff --git a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
index a35b45c9c..b9153934e 100644
--- a/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
+++ b/src/video_core/renderer_vulkan/vk_shader_decompiler.cpp
@@ -370,8 +370,8 @@ private:
370 u32 binding = const_buffers_base_binding; 370 u32 binding = const_buffers_base_binding;
371 for (const auto& entry : ir.GetConstantBuffers()) { 371 for (const auto& entry : ir.GetConstantBuffers()) {
372 const auto [index, size] = entry; 372 const auto [index, size] = entry;
373 const Id type = 373 const Id type = device.IsKhrUniformBufferStandardLayoutSupported() ? t_cbuf_scalar_ubo
374 device.IsExtScalarBlockLayoutSupported() ? t_cbuf_scalar_ubo : t_cbuf_std140_ubo; 374 : t_cbuf_std140_ubo;
375 const Id id = OpVariable(type, spv::StorageClass::Uniform); 375 const Id id = OpVariable(type, spv::StorageClass::Uniform);
376 AddGlobalVariable(Name(id, fmt::format("cbuf_{}", index))); 376 AddGlobalVariable(Name(id, fmt::format("cbuf_{}", index)));
377 377
@@ -565,7 +565,7 @@ private:
565 const Id buffer_id = constant_buffers.at(cbuf->GetIndex()); 565 const Id buffer_id = constant_buffers.at(cbuf->GetIndex());
566 566
567 Id pointer{}; 567 Id pointer{};
568 if (device.IsExtScalarBlockLayoutSupported()) { 568 if (device.IsKhrUniformBufferStandardLayoutSupported()) {
569 const Id buffer_offset = Emit(OpShiftRightLogical( 569 const Id buffer_offset = Emit(OpShiftRightLogical(
570 t_uint, BitcastTo<Type::Uint>(Visit(offset)), Constant(t_uint, 2u))); 570 t_uint, BitcastTo<Type::Uint>(Visit(offset)), Constant(t_uint, 2u)));
571 pointer = Emit( 571 pointer = Emit(
@@ -944,6 +944,41 @@ private:
944 return {}; 944 return {};
945 } 945 }
946 946
947 Id AtomicImageAdd(Operation operation) {
948 UNIMPLEMENTED();
949 return {};
950 }
951
952 Id AtomicImageMin(Operation operation) {
953 UNIMPLEMENTED();
954 return {};
955 }
956
957 Id AtomicImageMax(Operation operation) {
958 UNIMPLEMENTED();
959 return {};
960 }
961
962 Id AtomicImageAnd(Operation operation) {
963 UNIMPLEMENTED();
964 return {};
965 }
966
967 Id AtomicImageOr(Operation operation) {
968 UNIMPLEMENTED();
969 return {};
970 }
971
972 Id AtomicImageXor(Operation operation) {
973 UNIMPLEMENTED();
974 return {};
975 }
976
977 Id AtomicImageExchange(Operation operation) {
978 UNIMPLEMENTED();
979 return {};
980 }
981
947 Id Branch(Operation operation) { 982 Id Branch(Operation operation) {
948 const auto target = std::get_if<ImmediateNode>(&*operation[0]); 983 const auto target = std::get_if<ImmediateNode>(&*operation[0]);
949 UNIMPLEMENTED_IF(!target); 984 UNIMPLEMENTED_IF(!target);
@@ -1366,6 +1401,13 @@ private:
1366 &SPIRVDecompiler::TexelFetch, 1401 &SPIRVDecompiler::TexelFetch,
1367 1402
1368 &SPIRVDecompiler::ImageStore, 1403 &SPIRVDecompiler::ImageStore,
1404 &SPIRVDecompiler::AtomicImageAdd,
1405 &SPIRVDecompiler::AtomicImageMin,
1406 &SPIRVDecompiler::AtomicImageMax,
1407 &SPIRVDecompiler::AtomicImageAnd,
1408 &SPIRVDecompiler::AtomicImageOr,
1409 &SPIRVDecompiler::AtomicImageXor,
1410 &SPIRVDecompiler::AtomicImageExchange,
1369 1411
1370 &SPIRVDecompiler::Branch, 1412 &SPIRVDecompiler::Branch,
1371 &SPIRVDecompiler::BranchIndirect, 1413 &SPIRVDecompiler::BranchIndirect,
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..d54fb88c9 100644
--- a/src/video_core/shader/decode/image.cpp
+++ b/src/video_core/shader/decode/image.cpp
@@ -44,7 +44,6 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
44 switch (opcode->get().GetId()) { 44 switch (opcode->get().GetId()) {
45 case OpCode::Id::SUST: { 45 case OpCode::Id::SUST: {
46 UNIMPLEMENTED_IF(instr.sust.mode != Tegra::Shader::SurfaceDataMode::P); 46 UNIMPLEMENTED_IF(instr.sust.mode != Tegra::Shader::SurfaceDataMode::P);
47 UNIMPLEMENTED_IF(instr.sust.image_type == Tegra::Shader::ImageType::TextureBuffer);
48 UNIMPLEMENTED_IF(instr.sust.out_of_bounds_store != Tegra::Shader::OutOfBoundsStore::Ignore); 47 UNIMPLEMENTED_IF(instr.sust.out_of_bounds_store != Tegra::Shader::OutOfBoundsStore::Ignore);
49 UNIMPLEMENTED_IF(instr.sust.component_mask_selector != 0xf); // Ensure we have an RGBA store 48 UNIMPLEMENTED_IF(instr.sust.component_mask_selector != 0xf); // Ensure we have an RGBA store
50 49
@@ -61,56 +60,105 @@ u32 ShaderIR::DecodeImage(NodeBlock& bb, u32 pc) {
61 } 60 }
62 61
63 const auto type{instr.sust.image_type}; 62 const auto type{instr.sust.image_type};
64 const auto& image{instr.sust.is_immediate ? GetImage(instr.image, type) 63 auto& image{instr.sust.is_immediate ? GetImage(instr.image, type)
65 : GetBindlessImage(instr.gpr39, type)}; 64 : GetBindlessImage(instr.gpr39, type)};
65 image.MarkWrite();
66
66 MetaImage meta{image, values}; 67 MetaImage meta{image, values};
67 const Node store{Operation(OperationCode::ImageStore, meta, std::move(coords))}; 68 bb.push_back(Operation(OperationCode::ImageStore, meta, std::move(coords)));
68 bb.push_back(store); 69 break;
70 }
71 case OpCode::Id::SUATOM: {
72 UNIMPLEMENTED_IF(instr.suatom_d.is_ba != 0);
73
74 Node value = GetRegister(instr.gpr0);
75
76 std::vector<Node> coords;
77 const std::size_t num_coords{GetImageTypeNumCoordinates(instr.sust.image_type)};
78 for (std::size_t i = 0; i < num_coords; ++i) {
79 coords.push_back(GetRegister(instr.gpr8.Value() + i));
80 }
81
82 const OperationCode operation_code = [instr] {
83 switch (instr.suatom_d.operation) {
84 case Tegra::Shader::ImageAtomicOperation::Add:
85 return OperationCode::AtomicImageAdd;
86 case Tegra::Shader::ImageAtomicOperation::Min:
87 return OperationCode::AtomicImageMin;
88 case Tegra::Shader::ImageAtomicOperation::Max:
89 return OperationCode::AtomicImageMax;
90 case Tegra::Shader::ImageAtomicOperation::And:
91 return OperationCode::AtomicImageAnd;
92 case Tegra::Shader::ImageAtomicOperation::Or:
93 return OperationCode::AtomicImageOr;
94 case Tegra::Shader::ImageAtomicOperation::Xor:
95 return OperationCode::AtomicImageXor;
96 case Tegra::Shader::ImageAtomicOperation::Exch:
97 return OperationCode::AtomicImageExchange;
98 default:
99 UNIMPLEMENTED_MSG("Unimplemented operation={}",
100 static_cast<u32>(instr.suatom_d.operation.Value()));
101 return OperationCode::AtomicImageAdd;
102 }
103 }();
104
105 const auto& image{GetImage(instr.image, instr.suatom_d.image_type, instr.suatom_d.size)};
106 MetaImage meta{image, {std::move(value)}};
107 SetRegister(bb, instr.gpr0, Operation(operation_code, meta, std::move(coords)));
69 break; 108 break;
70 } 109 }
71 default: 110 default:
72 UNIMPLEMENTED_MSG("Unhandled conversion instruction: {}", opcode->get().GetName()); 111 UNIMPLEMENTED_MSG("Unhandled image instruction: {}", opcode->get().GetName());
73 } 112 }
74 113
75 return pc; 114 return pc;
76} 115}
77 116
78const Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type) { 117Image& ShaderIR::GetImage(Tegra::Shader::Image image, Tegra::Shader::ImageType type,
118 std::optional<Tegra::Shader::ImageAtomicSize> size) {
79 const auto offset{static_cast<std::size_t>(image.index.Value())}; 119 const auto offset{static_cast<std::size_t>(image.index.Value())};
80 120 if (const auto image = TryUseExistingImage(offset, type, size)) {
81 // If this image has already been used, return the existing mapping. 121 return *image;
82 const auto itr{std::find_if(used_images.begin(), used_images.end(),
83 [=](const Image& entry) { return entry.GetOffset() == offset; })};
84 if (itr != used_images.end()) {
85 ASSERT(itr->GetType() == type);
86 return *itr;
87 } 122 }
88 123
89 // Otherwise create a new mapping for this image.
90 const std::size_t next_index{used_images.size()}; 124 const std::size_t next_index{used_images.size()};
91 const Image entry{offset, next_index, type}; 125 return used_images.emplace(offset, Image{offset, next_index, type, size}).first->second;
92 return *used_images.emplace(entry).first;
93} 126}
94 127
95const Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, 128Image& ShaderIR::GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type,
96 Tegra::Shader::ImageType type) { 129 std::optional<Tegra::Shader::ImageAtomicSize> size) {
97 const Node image_register{GetRegister(reg)}; 130 const Node image_register{GetRegister(reg)};
98 const auto [base_image, cbuf_index, cbuf_offset]{ 131 const auto [base_image, cbuf_index, cbuf_offset]{
99 TrackCbuf(image_register, global_code, static_cast<s64>(global_code.size()))}; 132 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)}; 133 const auto cbuf_key{(static_cast<u64>(cbuf_index) << 32) | static_cast<u64>(cbuf_offset)};
101 134
102 // If this image has already been used, return the existing mapping. 135 if (const auto image = TryUseExistingImage(cbuf_key, type, size)) {
103 const auto itr{std::find_if(used_images.begin(), used_images.end(), 136 return *image;
104 [=](const Image& entry) { return entry.GetOffset() == cbuf_key; })};
105 if (itr != used_images.end()) {
106 ASSERT(itr->GetType() == type);
107 return *itr;
108 } 137 }
109 138
110 // Otherwise create a new mapping for this image.
111 const std::size_t next_index{used_images.size()}; 139 const std::size_t next_index{used_images.size()};
112 const Image entry{cbuf_index, cbuf_offset, next_index, type}; 140 return used_images.emplace(cbuf_key, Image{cbuf_index, cbuf_offset, next_index, type, size})
113 return *used_images.emplace(entry).first; 141 .first->second;
142}
143
144Image* ShaderIR::TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type,
145 std::optional<Tegra::Shader::ImageAtomicSize> size) {
146 auto it = used_images.find(offset);
147 if (it == used_images.end()) {
148 return nullptr;
149 }
150 auto& image = it->second;
151 ASSERT(image.GetType() == type);
152
153 if (size) {
154 // We know the size, if it's known it has to be the same as before, otherwise we can set it.
155 if (image.IsSizeKnown()) {
156 ASSERT(image.GetSize() == size);
157 } else {
158 image.SetSize(*size);
159 }
160 }
161 return &image;
114} 162}
115 163
116} // namespace VideoCommon::Shader 164} // namespace VideoCommon::Shader
diff --git a/src/video_core/shader/decode/shift.cpp b/src/video_core/shader/decode/shift.cpp
index 2ac16eeb0..f6ee68a54 100644
--- a/src/video_core/shader/decode/shift.cpp
+++ b/src/video_core/shader/decode/shift.cpp
@@ -17,8 +17,8 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) {
17 const Instruction instr = {program_code[pc]}; 17 const Instruction instr = {program_code[pc]};
18 const auto opcode = OpCode::Decode(instr); 18 const auto opcode = OpCode::Decode(instr);
19 19
20 const Node op_a = GetRegister(instr.gpr8); 20 Node op_a = GetRegister(instr.gpr8);
21 const Node op_b = [&]() { 21 Node op_b = [&]() {
22 if (instr.is_b_imm) { 22 if (instr.is_b_imm) {
23 return Immediate(instr.alu.GetSignedImm20_20()); 23 return Immediate(instr.alu.GetSignedImm20_20());
24 } else if (instr.is_b_gpr) { 24 } else if (instr.is_b_gpr) {
@@ -32,16 +32,23 @@ u32 ShaderIR::DecodeShift(NodeBlock& bb, u32 pc) {
32 case OpCode::Id::SHR_C: 32 case OpCode::Id::SHR_C:
33 case OpCode::Id::SHR_R: 33 case OpCode::Id::SHR_R:
34 case OpCode::Id::SHR_IMM: { 34 case OpCode::Id::SHR_IMM: {
35 const Node value = SignedOperation(OperationCode::IArithmeticShiftRight, 35 if (instr.shr.wrap) {
36 instr.shift.is_signed, PRECISE, op_a, op_b); 36 op_b = Operation(OperationCode::UBitwiseAnd, std::move(op_b), Immediate(0x1f));
37 } else {
38 op_b = Operation(OperationCode::IMax, std::move(op_b), Immediate(0));
39 op_b = Operation(OperationCode::IMin, std::move(op_b), Immediate(31));
40 }
41
42 Node value = SignedOperation(OperationCode::IArithmeticShiftRight, instr.shift.is_signed,
43 std::move(op_a), std::move(op_b));
37 SetInternalFlagsFromInteger(bb, value, instr.generates_cc); 44 SetInternalFlagsFromInteger(bb, value, instr.generates_cc);
38 SetRegister(bb, instr.gpr0, value); 45 SetRegister(bb, instr.gpr0, std::move(value));
39 break; 46 break;
40 } 47 }
41 case OpCode::Id::SHL_C: 48 case OpCode::Id::SHL_C:
42 case OpCode::Id::SHL_R: 49 case OpCode::Id::SHL_R:
43 case OpCode::Id::SHL_IMM: { 50 case OpCode::Id::SHL_IMM: {
44 const Node value = Operation(OperationCode::ILogicalShiftLeft, PRECISE, op_a, op_b); 51 const Node value = Operation(OperationCode::ILogicalShiftLeft, op_a, op_b);
45 SetInternalFlagsFromInteger(bb, value, instr.generates_cc); 52 SetInternalFlagsFromInteger(bb, value, instr.generates_cc);
46 SetRegister(bb, instr.gpr0, value); 53 SetRegister(bb, instr.gpr0, value);
47 break; 54 break;
diff --git a/src/video_core/shader/node.h b/src/video_core/shader/node.h
index 5db9313c4..b47b201cf 100644
--- a/src/video_core/shader/node.h
+++ b/src/video_core/shader/node.h
@@ -7,6 +7,7 @@
7#include <array> 7#include <array>
8#include <cstddef> 8#include <cstddef>
9#include <memory> 9#include <memory>
10#include <optional>
10#include <string> 11#include <string>
11#include <tuple> 12#include <tuple>
12#include <utility> 13#include <utility>
@@ -148,7 +149,14 @@ enum class OperationCode {
148 TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4 149 TextureQueryLod, /// (MetaTexture, float[N] coords) -> float4
149 TexelFetch, /// (MetaTexture, int[N], int) -> float4 150 TexelFetch, /// (MetaTexture, int[N], int) -> float4
150 151
151 ImageStore, /// (MetaImage, float[N] coords) -> void 152 ImageStore, /// (MetaImage, int[N] values) -> void
153 AtomicImageAdd, /// (MetaImage, int[N] coords) -> void
154 AtomicImageMin, /// (MetaImage, int[N] coords) -> void
155 AtomicImageMax, /// (MetaImage, int[N] coords) -> void
156 AtomicImageAnd, /// (MetaImage, int[N] coords) -> void
157 AtomicImageOr, /// (MetaImage, int[N] coords) -> void
158 AtomicImageXor, /// (MetaImage, int[N] coords) -> void
159 AtomicImageExchange, /// (MetaImage, int[N] coords) -> void
152 160
153 Branch, /// (uint branch_target) -> void 161 Branch, /// (uint branch_target) -> void
154 BranchIndirect, /// (uint branch_target) -> void 162 BranchIndirect, /// (uint branch_target) -> void
@@ -273,46 +281,85 @@ private:
273 bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not. 281 bool is_bindless{}; ///< Whether this sampler belongs to a bindless texture or not.
274}; 282};
275 283
276class Image { 284class Image final {
277public: 285public:
278 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,
279 : offset{offset}, index{index}, type{type}, is_bindless{false} {} 287 std::optional<Tegra::Shader::ImageAtomicSize> size)
288 : offset{offset}, index{index}, type{type}, is_bindless{false}, size{size} {}
280 289
281 explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index, 290 constexpr explicit Image(u32 cbuf_index, u32 cbuf_offset, std::size_t index,
282 Tegra::Shader::ImageType type) 291 Tegra::Shader::ImageType type,
292 std::optional<Tegra::Shader::ImageAtomicSize> size)
283 : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type}, 293 : offset{(static_cast<u64>(cbuf_index) << 32) | cbuf_offset}, index{index}, type{type},
284 is_bindless{true} {} 294 is_bindless{true}, size{size} {}
285 295
286 explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type, 296 constexpr explicit Image(std::size_t offset, std::size_t index, Tegra::Shader::ImageType type,
287 bool is_bindless) 297 bool is_bindless, bool is_written, bool is_read,
288 : offset{offset}, index{index}, type{type}, is_bindless{is_bindless} {} 298 std::optional<Tegra::Shader::ImageAtomicSize> size)
299 : offset{offset}, index{index}, type{type}, is_bindless{is_bindless},
300 is_written{is_written}, is_read{is_read}, size{size} {}
289 301
290 std::size_t GetOffset() const { 302 void MarkWrite() {
303 is_written = true;
304 }
305
306 void MarkRead() {
307 is_read = true;
308 }
309
310 void SetSize(Tegra::Shader::ImageAtomicSize size_) {
311 size = size_;
312 }
313
314 constexpr std::size_t GetOffset() const {
291 return offset; 315 return offset;
292 } 316 }
293 317
294 std::size_t GetIndex() const { 318 constexpr std::size_t GetIndex() const {
295 return index; 319 return index;
296 } 320 }
297 321
298 Tegra::Shader::ImageType GetType() const { 322 constexpr Tegra::Shader::ImageType GetType() const {
299 return type; 323 return type;
300 } 324 }
301 325
302 bool IsBindless() const { 326 constexpr bool IsBindless() const {
303 return is_bindless; 327 return is_bindless;
304 } 328 }
305 329
306 bool operator<(const Image& rhs) const { 330 constexpr bool IsWritten() const {
307 return std::tie(offset, index, type, is_bindless) < 331 return is_written;
308 std::tie(rhs.offset, rhs.index, rhs.type, rhs.is_bindless); 332 }
333
334 constexpr bool IsRead() const {
335 return is_read;
336 }
337
338 constexpr std::pair<u32, u32> GetBindlessCBuf() const {
339 return {static_cast<u32>(offset >> 32), static_cast<u32>(offset)};
340 }
341
342 constexpr bool IsSizeKnown() const {
343 return size.has_value();
344 }
345
346 constexpr Tegra::Shader::ImageAtomicSize GetSize() const {
347 return size.value();
348 }
349
350 constexpr bool operator<(const Image& rhs) const {
351 return std::tie(offset, index, type, size, is_bindless) <
352 std::tie(rhs.offset, rhs.index, rhs.type, rhs.size, rhs.is_bindless);
309 } 353 }
310 354
311private: 355private:
312 std::size_t offset{}; 356 u64 offset{};
313 std::size_t index{}; 357 std::size_t index{};
314 Tegra::Shader::ImageType type{}; 358 Tegra::Shader::ImageType type{};
315 bool is_bindless{}; 359 bool is_bindless{};
360 bool is_written{};
361 bool is_read{};
362 std::optional<Tegra::Shader::ImageAtomicSize> size{};
316}; 363};
317 364
318struct GlobalMemoryBase { 365struct GlobalMemoryBase {
diff --git a/src/video_core/shader/shader_ir.h b/src/video_core/shader/shader_ir.h
index bcc9b79b6..62816bd56 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,16 @@ 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 std::optional<Tegra::Shader::ImageAtomicSize> size = {});
276 277
277 /// Access a bindless image sampler. 278 /// Access a bindless image sampler.
278 const Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type); 279 Image& GetBindlessImage(Tegra::Shader::Register reg, Tegra::Shader::ImageType type,
280 std::optional<Tegra::Shader::ImageAtomicSize> size = {});
281
282 /// Tries to access an existing image, updating it's state as needed
283 Image* TryUseExistingImage(u64 offset, Tegra::Shader::ImageType type,
284 std::optional<Tegra::Shader::ImageAtomicSize> size);
279 285
280 /// Extracts a sequence of bits from a node 286 /// Extracts a sequence of bits from a node
281 Node BitfieldExtract(Node value, u32 offset, u32 bits); 287 Node BitfieldExtract(Node value, u32 offset, u32 bits);
@@ -356,7 +362,7 @@ private:
356 std::set<Tegra::Shader::Attribute::Index> used_output_attributes; 362 std::set<Tegra::Shader::Attribute::Index> used_output_attributes;
357 std::map<u32, ConstBuffer> used_cbufs; 363 std::map<u32, ConstBuffer> used_cbufs;
358 std::set<Sampler> used_samplers; 364 std::set<Sampler> used_samplers;
359 std::set<Image> used_images; 365 std::map<u64, Image> used_images;
360 std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{}; 366 std::array<bool, Tegra::Engines::Maxwell3D::Regs::NumClipDistances> used_clip_distances{};
361 std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory; 367 std::map<GlobalMemoryBase, GlobalMemoryUsage> used_global_memory;
362 bool uses_layer{}; 368 bool uses_layer{};
diff --git a/src/video_core/surface.cpp b/src/video_core/surface.cpp
index 4ceb219be..53d0142cb 100644
--- a/src/video_core/surface.cpp
+++ b/src/video_core/surface.cpp
@@ -513,6 +513,26 @@ bool IsPixelFormatASTC(PixelFormat format) {
513 } 513 }
514} 514}
515 515
516bool IsPixelFormatSRGB(PixelFormat format) {
517 switch (format) {
518 case PixelFormat::RGBA8_SRGB:
519 case PixelFormat::BGRA8_SRGB:
520 case PixelFormat::DXT1_SRGB:
521 case PixelFormat::DXT23_SRGB:
522 case PixelFormat::DXT45_SRGB:
523 case PixelFormat::BC7U_SRGB:
524 case PixelFormat::ASTC_2D_4X4_SRGB:
525 case PixelFormat::ASTC_2D_8X8_SRGB:
526 case PixelFormat::ASTC_2D_8X5_SRGB:
527 case PixelFormat::ASTC_2D_5X4_SRGB:
528 case PixelFormat::ASTC_2D_5X5_SRGB:
529 case PixelFormat::ASTC_2D_10X8_SRGB:
530 return true;
531 default:
532 return false;
533 }
534}
535
516std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) { 536std::pair<u32, u32> GetASTCBlockSize(PixelFormat format) {
517 return {GetDefaultBlockWidth(format), GetDefaultBlockHeight(format)}; 537 return {GetDefaultBlockWidth(format), GetDefaultBlockHeight(format)};
518} 538}
diff --git a/src/video_core/surface.h b/src/video_core/surface.h
index 83f31c12c..19268b7cd 100644
--- a/src/video_core/surface.h
+++ b/src/video_core/surface.h
@@ -547,6 +547,8 @@ SurfaceType GetFormatType(PixelFormat pixel_format);
547 547
548bool IsPixelFormatASTC(PixelFormat format); 548bool IsPixelFormatASTC(PixelFormat format);
549 549
550bool IsPixelFormatSRGB(PixelFormat format);
551
550std::pair<u32, u32> GetASTCBlockSize(PixelFormat format); 552std::pair<u32, u32> GetASTCBlockSize(PixelFormat format);
551 553
552/// Returns true if the specified PixelFormat is a BCn format, e.g. DXT or DXN 554/// Returns true if the specified PixelFormat is a BCn format, e.g. DXT or DXN
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*);