summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/android-build.yml79
-rw-r--r--.github/workflows/android-merge.js218
-rw-r--r--.github/workflows/android-publish.yml57
-rw-r--r--.github/workflows/verify.yml4
-rw-r--r--src/android/app/src/main/AndroidManifest.xml1
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt35
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt69
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt18
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt410
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt3
-rw-r--r--src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt120
-rw-r--r--src/android/app/src/main/res/drawable/button_l3.xml128
-rw-r--r--src/android/app/src/main/res/drawable/button_l3_depressed.xml75
-rw-r--r--src/android/app/src/main/res/drawable/button_r3.xml128
-rw-r--r--src/android/app/src/main/res/drawable/button_r3_depressed.xml75
-rw-r--r--src/android/app/src/main/res/values/arrays.xml2
-rw-r--r--src/android/app/src/main/res/values/integers.xml12
-rw-r--r--src/android/app/src/main/res/values/strings.xml1
-rw-r--r--src/core/core.cpp32
-rw-r--r--src/core/core.h11
-rw-r--r--src/core/core_timing.cpp9
-rw-r--r--src/core/core_timing.h8
-rw-r--r--src/core/file_sys/fsmitm_romfsbuild.cpp38
-rw-r--r--src/core/gpu_dirty_memory_manager.h122
-rw-r--r--src/core/hid/emulated_controller.cpp12
-rw-r--r--src/core/hid/emulated_controller.h8
-rw-r--r--src/core/hle/kernel/k_thread.h10
-rw-r--r--src/core/hle/kernel/svc/svc_ipc.cpp37
-rw-r--r--src/core/hle/kernel/svc/svc_synchronization.cpp41
-rw-r--r--src/core/hle/service/nfc/common/device.cpp32
-rw-r--r--src/core/memory.cpp40
-rw-r--r--src/core/memory.h6
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h39
-rw-r--r--src/video_core/buffer_cache/buffer_cache_base.h5
-rw-r--r--src/video_core/compatible_formats.cpp6
-rw-r--r--src/video_core/fence_manager.h2
-rw-r--r--src/video_core/gpu.cpp12
-rw-r--r--src/video_core/gpu.h4
-rw-r--r--src/video_core/gpu_thread.cpp6
-rw-r--r--src/video_core/rasterizer_interface.h4
-rw-r--r--src/video_core/renderer_null/null_rasterizer.cpp5
-rw-r--r--src/video_core/renderer_null/null_rasterizer.h3
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp35
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp34
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h3
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp45
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.h5
-rw-r--r--src/video_core/shader_cache.cpp2
-rw-r--r--src/video_core/shader_cache.h2
-rw-r--r--src/video_core/texture_cache/texture_cache.h16
-rw-r--r--src/video_core/texture_cache/types.h1
-rw-r--r--src/video_core/texture_cache/util.cpp9
-rw-r--r--src/video_core/vulkan_common/vulkan_device.cpp43
-rw-r--r--src/video_core/vulkan_common/vulkan_device.h14
-rw-r--r--src/yuzu/main.cpp4
-rw-r--r--src/yuzu_cmd/yuzu.cpp8
58 files changed, 1795 insertions, 359 deletions
diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
new file mode 100644
index 000000000..e639e965a
--- /dev/null
+++ b/.github/workflows/android-build.yml
@@ -0,0 +1,79 @@
1# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-3.0-or-later
3
4name: 'yuzu-android-build'
5
6on:
7 push:
8 tags: [ "*" ]
9
10jobs:
11 android:
12 runs-on: ubuntu-latest
13 steps:
14 - uses: actions/checkout@v3
15 with:
16 submodules: recursive
17 fetch-depth: 0
18 - name: Set up JDK 17
19 uses: actions/setup-java@v3
20 with:
21 java-version: '17'
22 distribution: 'temurin'
23 - name: Set up cache
24 uses: actions/cache@v3
25 with:
26 path: |
27 ~/.gradle/caches
28 ~/.gradle/wrapper
29 ~/.ccache
30 key: ${{ runner.os }}-android-${{ github.sha }}
31 restore-keys: |
32 ${{ runner.os }}-android-
33 - name: Query tag name
34 uses: olegtarasov/get-tag@v2.1.2
35 id: tagName
36 - name: Install dependencies
37 run: |
38 sudo apt-get update
39 sudo apt-get install -y ccache apksigner glslang-dev glslang-tools
40 - name: Build
41 run: ./.ci/scripts/android/build.sh
42 - name: Copy and sign artifacts
43 env:
44 ANDROID_KEYSTORE_B64: ${{ secrets.ANDROID_KEYSTORE_B64 }}
45 ANDROID_KEY_ALIAS: ${{ secrets.ANDROID_KEY_ALIAS }}
46 ANDROID_KEYSTORE_PASS: ${{ secrets.ANDROID_KEYSTORE_PASS }}
47 run: ./.ci/scripts/android/upload.sh
48 - name: Upload
49 uses: actions/upload-artifact@v3
50 with:
51 name: android
52 path: artifacts/
53 # release steps
54 release-android:
55 runs-on: ubuntu-latest
56 needs: [android]
57 if: ${{ startsWith(github.ref, 'refs/tags/') }}
58 permissions:
59 contents: write
60 steps:
61 - uses: actions/download-artifact@v3
62 - name: Query tag name
63 uses: olegtarasov/get-tag@v2.1.2
64 id: tagName
65 - name: Create release
66 uses: actions/create-release@v1
67 env:
68 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
69 with:
70 tag_name: ${{ steps.tagName.outputs.tag }}
71 release_name: ${{ steps.tagName.outputs.tag }}
72 draft: false
73 prerelease: false
74 - name: Upload artifacts
75 uses: alexellis/upload-assets@0.2.3
76 env:
77 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
78 with:
79 asset_paths: '["./**/*.apk","./**/*.aab"]'
diff --git a/.github/workflows/android-merge.js b/.github/workflows/android-merge.js
new file mode 100644
index 000000000..7e02dc9e5
--- /dev/null
+++ b/.github/workflows/android-merge.js
@@ -0,0 +1,218 @@
1// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-2.0-or-later
3
4// Note: This is a GitHub Actions script
5// It is not meant to be executed directly on your machine without modifications
6
7const fs = require("fs");
8// which label to check for changes
9const CHANGE_LABEL = 'android-merge';
10// how far back in time should we consider the changes are "recent"? (default: 24 hours)
11const DETECTION_TIME_FRAME = (parseInt(process.env.DETECTION_TIME_FRAME)) || (24 * 3600 * 1000);
12
13async function checkBaseChanges(github, context) {
14 // query the commit date of the latest commit on this branch
15 const query = `query($owner:String!, $name:String!, $ref:String!) {
16 repository(name:$name, owner:$owner) {
17 ref(qualifiedName:$ref) {
18 target {
19 ... on Commit { id pushedDate oid }
20 }
21 }
22 }
23 }`;
24 const variables = {
25 owner: context.repo.owner,
26 name: context.repo.repo,
27 ref: 'refs/heads/master',
28 };
29 const result = await github.graphql(query, variables);
30 const pushedAt = result.repository.ref.target.pushedDate;
31 console.log(`Last commit pushed at ${pushedAt}.`);
32 const delta = new Date() - new Date(pushedAt);
33 if (delta <= DETECTION_TIME_FRAME) {
34 console.info('New changes detected, triggering a new build.');
35 return true;
36 }
37 console.info('No new changes detected.');
38 return false;
39}
40
41async function checkAndroidChanges(github, context) {
42 if (checkBaseChanges(github, context)) return true;
43 const query = `query($owner:String!, $name:String!, $label:String!) {
44 repository(name:$name, owner:$owner) {
45 pullRequests(labels: [$label], states: OPEN, first: 100) {
46 nodes { number headRepository { pushedAt } }
47 }
48 }
49 }`;
50 const variables = {
51 owner: context.repo.owner,
52 name: context.repo.repo,
53 label: CHANGE_LABEL,
54 };
55 const result = await github.graphql(query, variables);
56 const pulls = result.repository.pullRequests.nodes;
57 for (let i = 0; i < pulls.length; i++) {
58 let pull = pulls[i];
59 if (new Date() - new Date(pull.headRepository.pushedAt) <= DETECTION_TIME_FRAME) {
60 console.info(`${pull.number} updated at ${pull.headRepository.pushedAt}`);
61 return true;
62 }
63 }
64 console.info("No changes detected in any tagged pull requests.");
65 return false;
66}
67
68async function tagAndPush(github, owner, repo, execa, commit=false) {
69 let altToken = process.env.ALT_GITHUB_TOKEN;
70 if (!altToken) {
71 throw `Please set ALT_GITHUB_TOKEN environment variable. This token should have write access to ${owner}/${repo}.`;
72 }
73 const query = `query ($owner:String!, $name:String!) {
74 repository(name:$name, owner:$owner) {
75 refs(refPrefix: "refs/tags/", orderBy: {field: TAG_COMMIT_DATE, direction: DESC}, first: 10) {
76 nodes { name }
77 }
78 }
79 }`;
80 const variables = {
81 owner: owner,
82 name: repo,
83 };
84 const tags = await github.graphql(query, variables);
85 const tagList = tags.repository.refs.nodes;
86 const lastTag = tagList[0] ? tagList[0].name : 'dummy-0';
87 const tagNumber = /\w+-(\d+)/.exec(lastTag)[1] | 0;
88 const channel = repo.split('-')[1];
89 const newTag = `${channel}-${tagNumber + 1}`;
90 console.log(`New tag: ${newTag}`);
91 if (commit) {
92 let channelName = channel[0].toUpperCase() + channel.slice(1);
93 console.info(`Committing pending commit as ${channelName} #${tagNumber + 1}`);
94 await execa("git", ['commit', '-m', `${channelName} #${tagNumber + 1}`]);
95 }
96 console.info('Pushing tags to GitHub ...');
97 await execa("git", ['tag', newTag]);
98 await execa("git", ['remote', 'add', 'target', `https://${altToken}@github.com/${owner}/${repo}.git`]);
99 await execa("git", ['push', 'target', 'master', '-f']);
100 await execa("git", ['push', 'target', 'master', '--tags']);
101 console.info('Successfully pushed new changes.');
102}
103
104async function generateReadme(pulls, context, mergeResults, execa) {
105 let baseUrl = `https://github.com/${context.repo.owner}/${context.repo.repo}/`;
106 let output =
107 "| Pull Request | Commit | Title | Author | Merged? |\n|----|----|----|----|----|\n";
108 for (let pull of pulls) {
109 let pr = pull.number;
110 let result = mergeResults[pr];
111 output += `| [${pr}](${baseUrl}/pull/${pr}) | [\`${result.rev || "N/A"}\`](${baseUrl}/pull/${pr}/files) | ${pull.title} | [${pull.author.login}](https://github.com/${pull.author.login}/) | ${result.success ? "Yes" : "No"} |\n`;
112 }
113 output +=
114 "\n\nEnd of merge log. You can find the original README.md below the break.\n\n-----\n\n";
115 output += fs.readFileSync("./README.md");
116 fs.writeFileSync("./README.md", output);
117 await execa("git", ["add", "README.md"]);
118}
119
120async function fetchPullRequests(pulls, repoUrl, execa) {
121 console.log("::group::Fetch pull requests");
122 for (let pull of pulls) {
123 let pr = pull.number;
124 console.info(`Fetching PR ${pr} ...`);
125 await execa("git", [
126 "fetch",
127 "-f",
128 "--no-recurse-submodules",
129 repoUrl,
130 `pull/${pr}/head:pr-${pr}`,
131 ]);
132 }
133 console.log("::endgroup::");
134}
135
136async function mergePullRequests(pulls, execa) {
137 let mergeResults = {};
138 console.log("::group::Merge pull requests");
139 await execa("git", ["config", "--global", "user.name", "yuzubot"]);
140 await execa("git", [
141 "config",
142 "--global",
143 "user.email",
144 "yuzu\x40yuzu-emu\x2eorg", // prevent email harvesters from scraping the address
145 ]);
146 let hasFailed = false;
147 for (let pull of pulls) {
148 let pr = pull.number;
149 console.info(`Merging PR ${pr} ...`);
150 try {
151 const process1 = execa("git", [
152 "merge",
153 "--squash",
154 "--no-edit",
155 `pr-${pr}`,
156 ]);
157 process1.stdout.pipe(process.stdout);
158 await process1;
159
160 const process2 = execa("git", ["commit", "-m", `Merge PR ${pr}`]);
161 process2.stdout.pipe(process.stdout);
162 await process2;
163
164 const process3 = await execa("git", ["rev-parse", "--short", `pr-${pr}`]);
165 mergeResults[pr] = {
166 success: true,
167 rev: process3.stdout,
168 };
169 } catch (err) {
170 console.log(
171 `::error title=#${pr} not merged::Failed to merge pull request: ${pr}: ${err}`
172 );
173 mergeResults[pr] = { success: false };
174 hasFailed = true;
175 await execa("git", ["reset", "--hard"]);
176 }
177 }
178 console.log("::endgroup::");
179 if (hasFailed) {
180 throw 'There are merge failures. Aborting!';
181 }
182 return mergeResults;
183}
184
185async function mergebot(github, context, execa) {
186 const query = `query ($owner:String!, $name:String!, $label:String!) {
187 repository(name:$name, owner:$owner) {
188 pullRequests(labels: [$label], states: OPEN, first: 100) {
189 nodes {
190 number title author { login }
191 }
192 }
193 }
194 }`;
195 const variables = {
196 owner: context.repo.owner,
197 name: context.repo.repo,
198 label: CHANGE_LABEL,
199 };
200 const result = await github.graphql(query, variables);
201 const pulls = result.repository.pullRequests.nodes;
202 let displayList = [];
203 for (let i = 0; i < pulls.length; i++) {
204 let pull = pulls[i];
205 displayList.push({ PR: pull.number, Title: pull.title });
206 }
207 console.info("The following pull requests will be merged:");
208 console.table(displayList);
209 await fetchPullRequests(pulls, "https://github.com/yuzu-emu/yuzu", execa);
210 const mergeResults = await mergePullRequests(pulls, execa);
211 await generateReadme(pulls, context, mergeResults, execa);
212 await tagAndPush(github, context.repo.owner, `${context.repo.repo}-android`, execa, true);
213}
214
215module.exports.mergebot = mergebot;
216module.exports.checkAndroidChanges = checkAndroidChanges;
217module.exports.tagAndPush = tagAndPush;
218module.exports.checkBaseChanges = checkBaseChanges;
diff --git a/.github/workflows/android-publish.yml b/.github/workflows/android-publish.yml
new file mode 100644
index 000000000..8f46fcf74
--- /dev/null
+++ b/.github/workflows/android-publish.yml
@@ -0,0 +1,57 @@
1# SPDX-FileCopyrightText: 2023 yuzu Emulator Project
2# SPDX-License-Identifier: GPL-2.0-or-later
3
4name: yuzu-android-publish
5
6on:
7 schedule:
8 - cron: '37 0 * * *'
9 workflow_dispatch:
10 inputs:
11 android:
12 description: 'Whether to trigger an Android build (true/false/auto)'
13 required: false
14 default: 'true'
15
16jobs:
17 android:
18 runs-on: ubuntu-latest
19 if: ${{ github.event.inputs.android != 'false' && github.repository == 'yuzu-emu/yuzu' }}
20 steps:
21 # this checkout is required to make sure the GitHub Actions scripts are available
22 - uses: actions/checkout@v3
23 name: Pre-checkout
24 with:
25 submodules: false
26 - uses: actions/github-script@v6
27 id: check-changes
28 name: 'Check for new changes'
29 env:
30 # 24 hours
31 DETECTION_TIME_FRAME: 86400000
32 with:
33 script: |
34 if (context.payload.inputs && context.payload.inputs.android === 'true') return true;
35 const checkAndroidChanges = require('./.github/workflows/android-merge.js').checkAndroidChanges;
36 return checkAndroidChanges(github, context);
37 - run: npm install execa@5
38 if: ${{ steps.check-changes.outputs.result == 'true' }}
39 - uses: actions/checkout@v3
40 name: Checkout
41 if: ${{ steps.check-changes.outputs.result == 'true' }}
42 with:
43 path: 'yuzu-merge'
44 fetch-depth: 0
45 submodules: true
46 token: ${{ secrets.ALT_GITHUB_TOKEN }}
47 - uses: actions/github-script@v5
48 name: 'Check and merge Android changes'
49 if: ${{ steps.check-changes.outputs.result == 'true' }}
50 env:
51 ALT_GITHUB_TOKEN: ${{ secrets.ALT_GITHUB_TOKEN }}
52 with:
53 script: |
54 const execa = require("execa");
55 const mergebot = require('./.github/workflows/android-merge.js').mergebot;
56 process.chdir('${{ github.workspace }}/yuzu-merge');
57 mergebot(github, context, execa);
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml
index bd4141f56..b5d338199 100644
--- a/.github/workflows/verify.yml
+++ b/.github/workflows/verify.yml
@@ -129,11 +129,12 @@ jobs:
129 - uses: actions/checkout@v3 129 - uses: actions/checkout@v3
130 with: 130 with:
131 submodules: recursive 131 submodules: recursive
132 fetch-depth: 0
132 - name: set up JDK 17 133 - name: set up JDK 17
133 uses: actions/setup-java@v3 134 uses: actions/setup-java@v3
134 with: 135 with:
135 java-version: '17' 136 java-version: '17'
136 distribution: 'adopt' 137 distribution: 'temurin'
137 - name: Set up cache 138 - name: Set up cache
138 uses: actions/cache@v3 139 uses: actions/cache@v3
139 with: 140 with:
@@ -151,7 +152,6 @@ jobs:
151 run: | 152 run: |
152 sudo apt-get update 153 sudo apt-get update
153 sudo apt-get install -y ccache apksigner glslang-dev glslang-tools 154 sudo apt-get install -y ccache apksigner glslang-dev glslang-tools
154 git -C ./externals/vcpkg/ fetch --all --unshallow
155 - name: Build 155 - name: Build
156 run: ./.ci/scripts/android/build.sh 156 run: ./.ci/scripts/android/build.sh
157 - name: Copy and sign artifacts 157 - name: Copy and sign artifacts
diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml
index 51d949d65..742685fb0 100644
--- a/src/android/app/src/main/AndroidManifest.xml
+++ b/src/android/app/src/main/AndroidManifest.xml
@@ -54,6 +54,7 @@ SPDX-License-Identifier: GPL-3.0-or-later
54 <activity 54 <activity
55 android:name="org.yuzu.yuzu_emu.activities.EmulationActivity" 55 android:name="org.yuzu.yuzu_emu.activities.EmulationActivity"
56 android:theme="@style/Theme.Yuzu.Main" 56 android:theme="@style/Theme.Yuzu.Main"
57 android:launchMode="singleTop"
57 android:screenOrientation="userLandscape" 58 android:screenOrientation="userLandscape"
58 android:supportsPictureInPicture="true" 59 android:supportsPictureInPicture="true"
59 android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode" 60 android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|uiMode"
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
index ae665ed2e..7461fb093 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/activities/EmulationActivity.kt
@@ -34,11 +34,14 @@ import androidx.core.view.WindowCompat
34import androidx.core.view.WindowInsetsCompat 34import androidx.core.view.WindowInsetsCompat
35import androidx.core.view.WindowInsetsControllerCompat 35import androidx.core.view.WindowInsetsControllerCompat
36import androidx.navigation.fragment.NavHostFragment 36import androidx.navigation.fragment.NavHostFragment
37import androidx.preference.PreferenceManager
37import org.yuzu.yuzu_emu.NativeLibrary 38import org.yuzu.yuzu_emu.NativeLibrary
38import org.yuzu.yuzu_emu.R 39import org.yuzu.yuzu_emu.R
40import org.yuzu.yuzu_emu.YuzuApplication
39import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding 41import org.yuzu.yuzu_emu.databinding.ActivityEmulationBinding
40import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting 42import org.yuzu.yuzu_emu.features.settings.model.BooleanSetting
41import org.yuzu.yuzu_emu.features.settings.model.IntSetting 43import org.yuzu.yuzu_emu.features.settings.model.IntSetting
44import org.yuzu.yuzu_emu.features.settings.model.Settings
42import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel 45import org.yuzu.yuzu_emu.features.settings.model.SettingsViewModel
43import org.yuzu.yuzu_emu.model.Game 46import org.yuzu.yuzu_emu.model.Game
44import org.yuzu.yuzu_emu.utils.ControllerMappingHelper 47import org.yuzu.yuzu_emu.utils.ControllerMappingHelper
@@ -47,6 +50,7 @@ import org.yuzu.yuzu_emu.utils.InputHandler
47import org.yuzu.yuzu_emu.utils.MemoryUtil 50import org.yuzu.yuzu_emu.utils.MemoryUtil
48import org.yuzu.yuzu_emu.utils.NfcReader 51import org.yuzu.yuzu_emu.utils.NfcReader
49import org.yuzu.yuzu_emu.utils.ThemeHelper 52import org.yuzu.yuzu_emu.utils.ThemeHelper
53import java.text.NumberFormat
50import kotlin.math.roundToInt 54import kotlin.math.roundToInt
51 55
52class EmulationActivity : AppCompatActivity(), SensorEventListener { 56class EmulationActivity : AppCompatActivity(), SensorEventListener {
@@ -106,17 +110,26 @@ class EmulationActivity : AppCompatActivity(), SensorEventListener {
106 inputHandler = InputHandler() 110 inputHandler = InputHandler()
107 inputHandler.initialize() 111 inputHandler.initialize()
108 112
109 val memoryUtil = MemoryUtil(this) 113 val preferences = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
110 if (memoryUtil.isLessThan(8, MemoryUtil.Gb)) { 114 if (!preferences.getBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, false)) {
111 Toast.makeText( 115 if (MemoryUtil.isLessThan(MemoryUtil.REQUIRED_MEMORY, MemoryUtil.Gb)) {
112 this, 116 Toast.makeText(
113 getString( 117 this,
114 R.string.device_memory_inadequate, 118 getString(
115 memoryUtil.getDeviceRAM(), 119 R.string.device_memory_inadequate,
116 "8 ${getString(R.string.memory_gigabyte)}" 120 MemoryUtil.getDeviceRAM(),
117 ), 121 getString(
118 Toast.LENGTH_LONG 122 R.string.memory_formatted,
119 ).show() 123 NumberFormat.getInstance().format(MemoryUtil.REQUIRED_MEMORY),
124 getString(R.string.memory_gigabyte)
125 )
126 ),
127 Toast.LENGTH_LONG
128 ).show()
129 preferences.edit()
130 .putBoolean(Settings.PREF_MEMORY_WARNING_SHOWN, true)
131 .apply()
132 }
120 } 133 }
121 134
122 // Start a foreground service to prevent the app from getting killed in the background 135 // Start a foreground service to prevent the app from getting killed in the background
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
index 88afb2223..a6251bafd 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/Settings.kt
@@ -110,25 +110,38 @@ class Settings {
110 const val SECTION_THEME = "Theme" 110 const val SECTION_THEME = "Theme"
111 const val SECTION_DEBUG = "Debug" 111 const val SECTION_DEBUG = "Debug"
112 112
113 const val PREF_OVERLAY_INIT = "OverlayInit" 113 const val PREF_MEMORY_WARNING_SHOWN = "MemoryWarningShown"
114
115 const val PREF_OVERLAY_VERSION = "OverlayVersion"
116 const val PREF_LANDSCAPE_OVERLAY_VERSION = "LandscapeOverlayVersion"
117 const val PREF_PORTRAIT_OVERLAY_VERSION = "PortraitOverlayVersion"
118 const val PREF_FOLDABLE_OVERLAY_VERSION = "FoldableOverlayVersion"
119 val overlayLayoutPrefs = listOf(
120 PREF_LANDSCAPE_OVERLAY_VERSION,
121 PREF_PORTRAIT_OVERLAY_VERSION,
122 PREF_FOLDABLE_OVERLAY_VERSION
123 )
124
114 const val PREF_CONTROL_SCALE = "controlScale" 125 const val PREF_CONTROL_SCALE = "controlScale"
115 const val PREF_CONTROL_OPACITY = "controlOpacity" 126 const val PREF_CONTROL_OPACITY = "controlOpacity"
116 const val PREF_TOUCH_ENABLED = "isTouchEnabled" 127 const val PREF_TOUCH_ENABLED = "isTouchEnabled"
117 const val PREF_BUTTON_TOGGLE_0 = "buttonToggle0" 128 const val PREF_BUTTON_A = "buttonToggle0"
118 const val PREF_BUTTON_TOGGLE_1 = "buttonToggle1" 129 const val PREF_BUTTON_B = "buttonToggle1"
119 const val PREF_BUTTON_TOGGLE_2 = "buttonToggle2" 130 const val PREF_BUTTON_X = "buttonToggle2"
120 const val PREF_BUTTON_TOGGLE_3 = "buttonToggle3" 131 const val PREF_BUTTON_Y = "buttonToggle3"
121 const val PREF_BUTTON_TOGGLE_4 = "buttonToggle4" 132 const val PREF_BUTTON_L = "buttonToggle4"
122 const val PREF_BUTTON_TOGGLE_5 = "buttonToggle5" 133 const val PREF_BUTTON_R = "buttonToggle5"
123 const val PREF_BUTTON_TOGGLE_6 = "buttonToggle6" 134 const val PREF_BUTTON_ZL = "buttonToggle6"
124 const val PREF_BUTTON_TOGGLE_7 = "buttonToggle7" 135 const val PREF_BUTTON_ZR = "buttonToggle7"
125 const val PREF_BUTTON_TOGGLE_8 = "buttonToggle8" 136 const val PREF_BUTTON_PLUS = "buttonToggle8"
126 const val PREF_BUTTON_TOGGLE_9 = "buttonToggle9" 137 const val PREF_BUTTON_MINUS = "buttonToggle9"
127 const val PREF_BUTTON_TOGGLE_10 = "buttonToggle10" 138 const val PREF_BUTTON_DPAD = "buttonToggle10"
128 const val PREF_BUTTON_TOGGLE_11 = "buttonToggle11" 139 const val PREF_STICK_L = "buttonToggle11"
129 const val PREF_BUTTON_TOGGLE_12 = "buttonToggle12" 140 const val PREF_STICK_R = "buttonToggle12"
130 const val PREF_BUTTON_TOGGLE_13 = "buttonToggle13" 141 const val PREF_BUTTON_STICK_L = "buttonToggle13"
131 const val PREF_BUTTON_TOGGLE_14 = "buttonToggle14" 142 const val PREF_BUTTON_STICK_R = "buttonToggle14"
143 const val PREF_BUTTON_HOME = "buttonToggle15"
144 const val PREF_BUTTON_SCREENSHOT = "buttonToggle16"
132 145
133 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter" 146 const val PREF_MENU_SETTINGS_JOYSTICK_REL_CENTER = "EmulationMenuSettings_JoystickRelCenter"
134 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable" 147 const val PREF_MENU_SETTINGS_DPAD_SLIDE = "EmulationMenuSettings_DpadSlideEnable"
@@ -143,6 +156,30 @@ class Settings {
143 156
144 private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap() 157 private val configFileSectionsMap: MutableMap<String, List<String>> = HashMap()
145 158
159 val overlayPreferences = listOf(
160 PREF_OVERLAY_VERSION,
161 PREF_CONTROL_SCALE,
162 PREF_CONTROL_OPACITY,
163 PREF_TOUCH_ENABLED,
164 PREF_BUTTON_A,
165 PREF_BUTTON_B,
166 PREF_BUTTON_X,
167 PREF_BUTTON_Y,
168 PREF_BUTTON_L,
169 PREF_BUTTON_R,
170 PREF_BUTTON_ZL,
171 PREF_BUTTON_ZR,
172 PREF_BUTTON_PLUS,
173 PREF_BUTTON_MINUS,
174 PREF_BUTTON_DPAD,
175 PREF_STICK_L,
176 PREF_STICK_R,
177 PREF_BUTTON_HOME,
178 PREF_BUTTON_SCREENSHOT,
179 PREF_BUTTON_STICK_L,
180 PREF_BUTTON_STICK_R
181 )
182
146 const val LayoutOption_Unspecified = 0 183 const val LayoutOption_Unspecified = 0
147 const val LayoutOption_MobilePortrait = 4 184 const val LayoutOption_MobilePortrait = 4
148 const val LayoutOption_MobileLandscape = 5 185 const val LayoutOption_MobileLandscape = 5
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
index 09976db62..0e7c1ba88 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
@@ -212,9 +212,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
212 } 212 }
213 if (!isInFoldableLayout) { 213 if (!isInFoldableLayout) {
214 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) { 214 if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
215 binding.surfaceInputOverlay.orientation = InputOverlay.PORTRAIT 215 binding.surfaceInputOverlay.layout = InputOverlay.PORTRAIT
216 } else { 216 } else {
217 binding.surfaceInputOverlay.orientation = InputOverlay.LANDSCAPE 217 binding.surfaceInputOverlay.layout = InputOverlay.LANDSCAPE
218 } 218 }
219 } 219 }
220 if (!binding.surfaceInputOverlay.isInEditMode) { 220 if (!binding.surfaceInputOverlay.isInEditMode) {
@@ -260,7 +260,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
260 .remove(Settings.PREF_CONTROL_SCALE) 260 .remove(Settings.PREF_CONTROL_SCALE)
261 .remove(Settings.PREF_CONTROL_OPACITY) 261 .remove(Settings.PREF_CONTROL_OPACITY)
262 .apply() 262 .apply()
263 binding.surfaceInputOverlay.post { binding.surfaceInputOverlay.resetButtonPlacement() } 263 binding.surfaceInputOverlay.post {
264 binding.surfaceInputOverlay.resetLayoutVisibilityAndPlacement()
265 }
264 } 266 }
265 267
266 private fun updateShowFpsOverlay() { 268 private fun updateShowFpsOverlay() {
@@ -337,7 +339,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
337 binding.inGameMenu.layoutParams.height = it.bounds.bottom 339 binding.inGameMenu.layoutParams.height = it.bounds.bottom
338 340
339 isInFoldableLayout = true 341 isInFoldableLayout = true
340 binding.surfaceInputOverlay.orientation = InputOverlay.FOLDABLE 342 binding.surfaceInputOverlay.layout = InputOverlay.FOLDABLE
341 refreshInputOverlay() 343 refreshInputOverlay()
342 } 344 }
343 } 345 }
@@ -410,9 +412,9 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
410 R.id.menu_toggle_controls -> { 412 R.id.menu_toggle_controls -> {
411 val preferences = 413 val preferences =
412 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 414 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
413 val optionsArray = BooleanArray(15) 415 val optionsArray = BooleanArray(Settings.overlayPreferences.size)
414 for (i in 0..14) { 416 Settings.overlayPreferences.forEachIndexed { i, _ ->
415 optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 13) 417 optionsArray[i] = preferences.getBoolean("buttonToggle$i", i < 15)
416 } 418 }
417 419
418 val dialog = MaterialAlertDialogBuilder(requireContext()) 420 val dialog = MaterialAlertDialogBuilder(requireContext())
@@ -436,7 +438,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
436 dialog.getButton(AlertDialog.BUTTON_NEUTRAL) 438 dialog.getButton(AlertDialog.BUTTON_NEUTRAL)
437 .setOnClickListener { 439 .setOnClickListener {
438 val isChecked = !optionsArray[0] 440 val isChecked = !optionsArray[0]
439 for (i in 0..14) { 441 Settings.overlayPreferences.forEachIndexed { i, _ ->
440 optionsArray[i] = isChecked 442 optionsArray[i] = isChecked
441 dialog.listView.setItemChecked(i, isChecked) 443 dialog.listView.setItemChecked(i, isChecked)
442 preferences.edit() 444 preferences.edit()
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
index 6251ec783..c055c2e35 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlay.kt
@@ -51,15 +51,23 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
51 51
52 private lateinit var windowInsets: WindowInsets 52 private lateinit var windowInsets: WindowInsets
53 53
54 var orientation = LANDSCAPE 54 var layout = LANDSCAPE
55 55
56 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { 56 override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
57 super.onLayout(changed, left, top, right, bottom) 57 super.onLayout(changed, left, top, right, bottom)
58 58
59 windowInsets = rootWindowInsets 59 windowInsets = rootWindowInsets
60 60
61 if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { 61 val overlayVersion = preferences.getInt(Settings.PREF_OVERLAY_VERSION, 0)
62 defaultOverlay() 62 if (overlayVersion != OVERLAY_VERSION) {
63 resetAllLayouts()
64 } else {
65 val layoutIndex = overlayLayouts.indexOf(layout)
66 val currentLayoutVersion =
67 preferences.getInt(Settings.overlayLayoutPrefs[layoutIndex], 0)
68 if (currentLayoutVersion != overlayLayoutVersions[layoutIndex]) {
69 resetCurrentLayout()
70 }
63 } 71 }
64 72
65 // Load the controls. 73 // Load the controls.
@@ -266,10 +274,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
266 MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) { 274 MotionEvent.ACTION_POINTER_UP -> if (buttonBeingConfigured === button) {
267 // Persist button position by saving new place. 275 // Persist button position by saving new place.
268 saveControlPosition( 276 saveControlPosition(
269 buttonBeingConfigured!!.buttonId, 277 buttonBeingConfigured!!.prefId,
270 buttonBeingConfigured!!.bounds.centerX(), 278 buttonBeingConfigured!!.bounds.centerX(),
271 buttonBeingConfigured!!.bounds.centerY(), 279 buttonBeingConfigured!!.bounds.centerY(),
272 orientation 280 layout
273 ) 281 )
274 buttonBeingConfigured = null 282 buttonBeingConfigured = null
275 } 283 }
@@ -299,10 +307,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
299 MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) { 307 MotionEvent.ACTION_POINTER_UP -> if (dpadBeingConfigured === dpad) {
300 // Persist button position by saving new place. 308 // Persist button position by saving new place.
301 saveControlPosition( 309 saveControlPosition(
302 dpadBeingConfigured!!.upId, 310 Settings.PREF_BUTTON_DPAD,
303 dpadBeingConfigured!!.bounds.centerX(), 311 dpadBeingConfigured!!.bounds.centerX(),
304 dpadBeingConfigured!!.bounds.centerY(), 312 dpadBeingConfigured!!.bounds.centerY(),
305 orientation 313 layout
306 ) 314 )
307 dpadBeingConfigured = null 315 dpadBeingConfigured = null
308 } 316 }
@@ -330,10 +338,10 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
330 MotionEvent.ACTION_UP, 338 MotionEvent.ACTION_UP,
331 MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) { 339 MotionEvent.ACTION_POINTER_UP -> if (joystickBeingConfigured != null) {
332 saveControlPosition( 340 saveControlPosition(
333 joystickBeingConfigured!!.buttonId, 341 joystickBeingConfigured!!.prefId,
334 joystickBeingConfigured!!.bounds.centerX(), 342 joystickBeingConfigured!!.bounds.centerX(),
335 joystickBeingConfigured!!.bounds.centerY(), 343 joystickBeingConfigured!!.bounds.centerY(),
336 orientation 344 layout
337 ) 345 )
338 joystickBeingConfigured = null 346 joystickBeingConfigured = null
339 } 347 }
@@ -343,9 +351,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
343 return true 351 return true
344 } 352 }
345 353
346 private fun addOverlayControls(orientation: String) { 354 private fun addOverlayControls(layout: String) {
347 val windowSize = getSafeScreenSize(context) 355 val windowSize = getSafeScreenSize(context)
348 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_0, true)) { 356 if (preferences.getBoolean(Settings.PREF_BUTTON_A, true)) {
349 overlayButtons.add( 357 overlayButtons.add(
350 initializeOverlayButton( 358 initializeOverlayButton(
351 context, 359 context,
@@ -353,11 +361,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
353 R.drawable.facebutton_a, 361 R.drawable.facebutton_a,
354 R.drawable.facebutton_a_depressed, 362 R.drawable.facebutton_a_depressed,
355 ButtonType.BUTTON_A, 363 ButtonType.BUTTON_A,
356 orientation 364 Settings.PREF_BUTTON_A,
365 layout
357 ) 366 )
358 ) 367 )
359 } 368 }
360 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_1, true)) { 369 if (preferences.getBoolean(Settings.PREF_BUTTON_B, true)) {
361 overlayButtons.add( 370 overlayButtons.add(
362 initializeOverlayButton( 371 initializeOverlayButton(
363 context, 372 context,
@@ -365,11 +374,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
365 R.drawable.facebutton_b, 374 R.drawable.facebutton_b,
366 R.drawable.facebutton_b_depressed, 375 R.drawable.facebutton_b_depressed,
367 ButtonType.BUTTON_B, 376 ButtonType.BUTTON_B,
368 orientation 377 Settings.PREF_BUTTON_B,
378 layout
369 ) 379 )
370 ) 380 )
371 } 381 }
372 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_2, true)) { 382 if (preferences.getBoolean(Settings.PREF_BUTTON_X, true)) {
373 overlayButtons.add( 383 overlayButtons.add(
374 initializeOverlayButton( 384 initializeOverlayButton(
375 context, 385 context,
@@ -377,11 +387,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
377 R.drawable.facebutton_x, 387 R.drawable.facebutton_x,
378 R.drawable.facebutton_x_depressed, 388 R.drawable.facebutton_x_depressed,
379 ButtonType.BUTTON_X, 389 ButtonType.BUTTON_X,
380 orientation 390 Settings.PREF_BUTTON_X,
391 layout
381 ) 392 )
382 ) 393 )
383 } 394 }
384 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_3, true)) { 395 if (preferences.getBoolean(Settings.PREF_BUTTON_Y, true)) {
385 overlayButtons.add( 396 overlayButtons.add(
386 initializeOverlayButton( 397 initializeOverlayButton(
387 context, 398 context,
@@ -389,11 +400,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
389 R.drawable.facebutton_y, 400 R.drawable.facebutton_y,
390 R.drawable.facebutton_y_depressed, 401 R.drawable.facebutton_y_depressed,
391 ButtonType.BUTTON_Y, 402 ButtonType.BUTTON_Y,
392 orientation 403 Settings.PREF_BUTTON_Y,
404 layout
393 ) 405 )
394 ) 406 )
395 } 407 }
396 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_4, true)) { 408 if (preferences.getBoolean(Settings.PREF_BUTTON_L, true)) {
397 overlayButtons.add( 409 overlayButtons.add(
398 initializeOverlayButton( 410 initializeOverlayButton(
399 context, 411 context,
@@ -401,11 +413,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
401 R.drawable.l_shoulder, 413 R.drawable.l_shoulder,
402 R.drawable.l_shoulder_depressed, 414 R.drawable.l_shoulder_depressed,
403 ButtonType.TRIGGER_L, 415 ButtonType.TRIGGER_L,
404 orientation 416 Settings.PREF_BUTTON_L,
417 layout
405 ) 418 )
406 ) 419 )
407 } 420 }
408 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_5, true)) { 421 if (preferences.getBoolean(Settings.PREF_BUTTON_R, true)) {
409 overlayButtons.add( 422 overlayButtons.add(
410 initializeOverlayButton( 423 initializeOverlayButton(
411 context, 424 context,
@@ -413,11 +426,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
413 R.drawable.r_shoulder, 426 R.drawable.r_shoulder,
414 R.drawable.r_shoulder_depressed, 427 R.drawable.r_shoulder_depressed,
415 ButtonType.TRIGGER_R, 428 ButtonType.TRIGGER_R,
416 orientation 429 Settings.PREF_BUTTON_R,
430 layout
417 ) 431 )
418 ) 432 )
419 } 433 }
420 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_6, true)) { 434 if (preferences.getBoolean(Settings.PREF_BUTTON_ZL, true)) {
421 overlayButtons.add( 435 overlayButtons.add(
422 initializeOverlayButton( 436 initializeOverlayButton(
423 context, 437 context,
@@ -425,11 +439,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
425 R.drawable.zl_trigger, 439 R.drawable.zl_trigger,
426 R.drawable.zl_trigger_depressed, 440 R.drawable.zl_trigger_depressed,
427 ButtonType.TRIGGER_ZL, 441 ButtonType.TRIGGER_ZL,
428 orientation 442 Settings.PREF_BUTTON_ZL,
443 layout
429 ) 444 )
430 ) 445 )
431 } 446 }
432 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_7, true)) { 447 if (preferences.getBoolean(Settings.PREF_BUTTON_ZR, true)) {
433 overlayButtons.add( 448 overlayButtons.add(
434 initializeOverlayButton( 449 initializeOverlayButton(
435 context, 450 context,
@@ -437,11 +452,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
437 R.drawable.zr_trigger, 452 R.drawable.zr_trigger,
438 R.drawable.zr_trigger_depressed, 453 R.drawable.zr_trigger_depressed,
439 ButtonType.TRIGGER_ZR, 454 ButtonType.TRIGGER_ZR,
440 orientation 455 Settings.PREF_BUTTON_ZR,
456 layout
441 ) 457 )
442 ) 458 )
443 } 459 }
444 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_8, true)) { 460 if (preferences.getBoolean(Settings.PREF_BUTTON_PLUS, true)) {
445 overlayButtons.add( 461 overlayButtons.add(
446 initializeOverlayButton( 462 initializeOverlayButton(
447 context, 463 context,
@@ -449,11 +465,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
449 R.drawable.facebutton_plus, 465 R.drawable.facebutton_plus,
450 R.drawable.facebutton_plus_depressed, 466 R.drawable.facebutton_plus_depressed,
451 ButtonType.BUTTON_PLUS, 467 ButtonType.BUTTON_PLUS,
452 orientation 468 Settings.PREF_BUTTON_PLUS,
469 layout
453 ) 470 )
454 ) 471 )
455 } 472 }
456 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_9, true)) { 473 if (preferences.getBoolean(Settings.PREF_BUTTON_MINUS, true)) {
457 overlayButtons.add( 474 overlayButtons.add(
458 initializeOverlayButton( 475 initializeOverlayButton(
459 context, 476 context,
@@ -461,11 +478,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
461 R.drawable.facebutton_minus, 478 R.drawable.facebutton_minus,
462 R.drawable.facebutton_minus_depressed, 479 R.drawable.facebutton_minus_depressed,
463 ButtonType.BUTTON_MINUS, 480 ButtonType.BUTTON_MINUS,
464 orientation 481 Settings.PREF_BUTTON_MINUS,
482 layout
465 ) 483 )
466 ) 484 )
467 } 485 }
468 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_10, true)) { 486 if (preferences.getBoolean(Settings.PREF_BUTTON_DPAD, true)) {
469 overlayDpads.add( 487 overlayDpads.add(
470 initializeOverlayDpad( 488 initializeOverlayDpad(
471 context, 489 context,
@@ -473,11 +491,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
473 R.drawable.dpad_standard, 491 R.drawable.dpad_standard,
474 R.drawable.dpad_standard_cardinal_depressed, 492 R.drawable.dpad_standard_cardinal_depressed,
475 R.drawable.dpad_standard_diagonal_depressed, 493 R.drawable.dpad_standard_diagonal_depressed,
476 orientation 494 layout
477 ) 495 )
478 ) 496 )
479 } 497 }
480 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_11, true)) { 498 if (preferences.getBoolean(Settings.PREF_STICK_L, true)) {
481 overlayJoysticks.add( 499 overlayJoysticks.add(
482 initializeOverlayJoystick( 500 initializeOverlayJoystick(
483 context, 501 context,
@@ -487,11 +505,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
487 R.drawable.joystick_depressed, 505 R.drawable.joystick_depressed,
488 StickType.STICK_L, 506 StickType.STICK_L,
489 ButtonType.STICK_L, 507 ButtonType.STICK_L,
490 orientation 508 Settings.PREF_STICK_L,
509 layout
491 ) 510 )
492 ) 511 )
493 } 512 }
494 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_12, true)) { 513 if (preferences.getBoolean(Settings.PREF_STICK_R, true)) {
495 overlayJoysticks.add( 514 overlayJoysticks.add(
496 initializeOverlayJoystick( 515 initializeOverlayJoystick(
497 context, 516 context,
@@ -501,11 +520,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
501 R.drawable.joystick_depressed, 520 R.drawable.joystick_depressed,
502 StickType.STICK_R, 521 StickType.STICK_R,
503 ButtonType.STICK_R, 522 ButtonType.STICK_R,
504 orientation 523 Settings.PREF_STICK_R,
524 layout
505 ) 525 )
506 ) 526 )
507 } 527 }
508 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_13, false)) { 528 if (preferences.getBoolean(Settings.PREF_BUTTON_HOME, false)) {
509 overlayButtons.add( 529 overlayButtons.add(
510 initializeOverlayButton( 530 initializeOverlayButton(
511 context, 531 context,
@@ -513,11 +533,12 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
513 R.drawable.facebutton_home, 533 R.drawable.facebutton_home,
514 R.drawable.facebutton_home_depressed, 534 R.drawable.facebutton_home_depressed,
515 ButtonType.BUTTON_HOME, 535 ButtonType.BUTTON_HOME,
516 orientation 536 Settings.PREF_BUTTON_HOME,
537 layout
517 ) 538 )
518 ) 539 )
519 } 540 }
520 if (preferences.getBoolean(Settings.PREF_BUTTON_TOGGLE_14, false)) { 541 if (preferences.getBoolean(Settings.PREF_BUTTON_SCREENSHOT, false)) {
521 overlayButtons.add( 542 overlayButtons.add(
522 initializeOverlayButton( 543 initializeOverlayButton(
523 context, 544 context,
@@ -525,7 +546,34 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
525 R.drawable.facebutton_screenshot, 546 R.drawable.facebutton_screenshot,
526 R.drawable.facebutton_screenshot_depressed, 547 R.drawable.facebutton_screenshot_depressed,
527 ButtonType.BUTTON_CAPTURE, 548 ButtonType.BUTTON_CAPTURE,
528 orientation 549 Settings.PREF_BUTTON_SCREENSHOT,
550 layout
551 )
552 )
553 }
554 if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_L, true)) {
555 overlayButtons.add(
556 initializeOverlayButton(
557 context,
558 windowSize,
559 R.drawable.button_l3,
560 R.drawable.button_l3_depressed,
561 ButtonType.STICK_L,
562 Settings.PREF_BUTTON_STICK_L,
563 layout
564 )
565 )
566 }
567 if (preferences.getBoolean(Settings.PREF_BUTTON_STICK_R, true)) {
568 overlayButtons.add(
569 initializeOverlayButton(
570 context,
571 windowSize,
572 R.drawable.button_r3,
573 R.drawable.button_r3_depressed,
574 ButtonType.STICK_R,
575 Settings.PREF_BUTTON_STICK_R,
576 layout
529 ) 577 )
530 ) 578 )
531 } 579 }
@@ -539,18 +587,18 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
539 587
540 // Add all the enabled overlay items back to the HashSet. 588 // Add all the enabled overlay items back to the HashSet.
541 if (EmulationMenuSettings.showOverlay) { 589 if (EmulationMenuSettings.showOverlay) {
542 addOverlayControls(orientation) 590 addOverlayControls(layout)
543 } 591 }
544 invalidate() 592 invalidate()
545 } 593 }
546 594
547 private fun saveControlPosition(sharedPrefsId: Int, x: Int, y: Int, orientation: String) { 595 private fun saveControlPosition(prefId: String, x: Int, y: Int, layout: String) {
548 val windowSize = getSafeScreenSize(context) 596 val windowSize = getSafeScreenSize(context)
549 val min = windowSize.first 597 val min = windowSize.first
550 val max = windowSize.second 598 val max = windowSize.second
551 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit() 599 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext).edit()
552 .putFloat("$sharedPrefsId-X$orientation", (x - min.x).toFloat() / max.x) 600 .putFloat("$prefId-X$layout", (x - min.x).toFloat() / max.x)
553 .putFloat("$sharedPrefsId-Y$orientation", (y - min.y).toFloat() / max.y) 601 .putFloat("$prefId-Y$layout", (y - min.y).toFloat() / max.y)
554 .apply() 602 .apply()
555 } 603 }
556 604
@@ -558,19 +606,31 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
558 inEditMode = editMode 606 inEditMode = editMode
559 } 607 }
560 608
561 private fun defaultOverlay() { 609 private fun resetCurrentLayout() {
562 if (!preferences.getBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", false)) { 610 defaultOverlayByLayout(layout)
563 defaultOverlayByLayout(orientation) 611 val layoutIndex = overlayLayouts.indexOf(layout)
564 }
565
566 resetButtonPlacement()
567 preferences.edit() 612 preferences.edit()
568 .putBoolean("${Settings.PREF_OVERLAY_INIT}$orientation", true) 613 .putInt(Settings.overlayLayoutPrefs[layoutIndex], overlayLayoutVersions[layoutIndex])
569 .apply() 614 .apply()
570 } 615 }
571 616
572 fun resetButtonPlacement() { 617 private fun resetAllLayouts() {
573 defaultOverlayByLayout(orientation) 618 val editor = preferences.edit()
619 overlayLayouts.forEachIndexed { i, layout ->
620 defaultOverlayByLayout(layout)
621 editor.putInt(Settings.overlayLayoutPrefs[i], overlayLayoutVersions[i])
622 }
623 editor.putInt(Settings.PREF_OVERLAY_VERSION, OVERLAY_VERSION)
624 editor.apply()
625 }
626
627 fun resetLayoutVisibilityAndPlacement() {
628 defaultOverlayByLayout(layout)
629 val editor = preferences.edit()
630 Settings.overlayPreferences.forEachIndexed { _, pref ->
631 editor.remove(pref)
632 }
633 editor.apply()
574 refreshControls() 634 refreshControls()
575 } 635 }
576 636
@@ -604,7 +664,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
604 R.integer.SWITCH_STICK_R_X, 664 R.integer.SWITCH_STICK_R_X,
605 R.integer.SWITCH_STICK_R_Y, 665 R.integer.SWITCH_STICK_R_Y,
606 R.integer.SWITCH_STICK_L_X, 666 R.integer.SWITCH_STICK_L_X,
607 R.integer.SWITCH_STICK_L_Y 667 R.integer.SWITCH_STICK_L_Y,
668 R.integer.SWITCH_BUTTON_STICK_L_X,
669 R.integer.SWITCH_BUTTON_STICK_L_Y,
670 R.integer.SWITCH_BUTTON_STICK_R_X,
671 R.integer.SWITCH_BUTTON_STICK_R_Y
608 ) 672 )
609 673
610 private val portraitResources = arrayOf( 674 private val portraitResources = arrayOf(
@@ -637,7 +701,11 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
637 R.integer.SWITCH_STICK_R_X_PORTRAIT, 701 R.integer.SWITCH_STICK_R_X_PORTRAIT,
638 R.integer.SWITCH_STICK_R_Y_PORTRAIT, 702 R.integer.SWITCH_STICK_R_Y_PORTRAIT,
639 R.integer.SWITCH_STICK_L_X_PORTRAIT, 703 R.integer.SWITCH_STICK_L_X_PORTRAIT,
640 R.integer.SWITCH_STICK_L_Y_PORTRAIT 704 R.integer.SWITCH_STICK_L_Y_PORTRAIT,
705 R.integer.SWITCH_BUTTON_STICK_L_X_PORTRAIT,
706 R.integer.SWITCH_BUTTON_STICK_L_Y_PORTRAIT,
707 R.integer.SWITCH_BUTTON_STICK_R_X_PORTRAIT,
708 R.integer.SWITCH_BUTTON_STICK_R_Y_PORTRAIT
641 ) 709 )
642 710
643 private val foldableResources = arrayOf( 711 private val foldableResources = arrayOf(
@@ -670,139 +738,159 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
670 R.integer.SWITCH_STICK_R_X_FOLDABLE, 738 R.integer.SWITCH_STICK_R_X_FOLDABLE,
671 R.integer.SWITCH_STICK_R_Y_FOLDABLE, 739 R.integer.SWITCH_STICK_R_Y_FOLDABLE,
672 R.integer.SWITCH_STICK_L_X_FOLDABLE, 740 R.integer.SWITCH_STICK_L_X_FOLDABLE,
673 R.integer.SWITCH_STICK_L_Y_FOLDABLE 741 R.integer.SWITCH_STICK_L_Y_FOLDABLE,
742 R.integer.SWITCH_BUTTON_STICK_L_X_FOLDABLE,
743 R.integer.SWITCH_BUTTON_STICK_L_Y_FOLDABLE,
744 R.integer.SWITCH_BUTTON_STICK_R_X_FOLDABLE,
745 R.integer.SWITCH_BUTTON_STICK_R_Y_FOLDABLE
674 ) 746 )
675 747
676 private fun getResourceValue(orientation: String, position: Int): Float { 748 private fun getResourceValue(layout: String, position: Int): Float {
677 return when (orientation) { 749 return when (layout) {
678 PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000 750 PORTRAIT -> resources.getInteger(portraitResources[position]).toFloat() / 1000
679 FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000 751 FOLDABLE -> resources.getInteger(foldableResources[position]).toFloat() / 1000
680 else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000 752 else -> resources.getInteger(landscapeResources[position]).toFloat() / 1000
681 } 753 }
682 } 754 }
683 755
684 private fun defaultOverlayByLayout(orientation: String) { 756 private fun defaultOverlayByLayout(layout: String) {
685 // Each value represents the position of the button in relation to the screen size without insets. 757 // Each value represents the position of the button in relation to the screen size without insets.
686 preferences.edit() 758 preferences.edit()
687 .putFloat( 759 .putFloat(
688 ButtonType.BUTTON_A.toString() + "-X$orientation", 760 "${Settings.PREF_BUTTON_A}-X$layout",
689 getResourceValue(orientation, 0) 761 getResourceValue(layout, 0)
762 )
763 .putFloat(
764 "${Settings.PREF_BUTTON_A}-Y$layout",
765 getResourceValue(layout, 1)
766 )
767 .putFloat(
768 "${Settings.PREF_BUTTON_B}-X$layout",
769 getResourceValue(layout, 2)
770 )
771 .putFloat(
772 "${Settings.PREF_BUTTON_B}-Y$layout",
773 getResourceValue(layout, 3)
774 )
775 .putFloat(
776 "${Settings.PREF_BUTTON_X}-X$layout",
777 getResourceValue(layout, 4)
690 ) 778 )
691 .putFloat( 779 .putFloat(
692 ButtonType.BUTTON_A.toString() + "-Y$orientation", 780 "${Settings.PREF_BUTTON_X}-Y$layout",
693 getResourceValue(orientation, 1) 781 getResourceValue(layout, 5)
694 ) 782 )
695 .putFloat( 783 .putFloat(
696 ButtonType.BUTTON_B.toString() + "-X$orientation", 784 "${Settings.PREF_BUTTON_Y}-X$layout",
697 getResourceValue(orientation, 2) 785 getResourceValue(layout, 6)
698 ) 786 )
699 .putFloat( 787 .putFloat(
700 ButtonType.BUTTON_B.toString() + "-Y$orientation", 788 "${Settings.PREF_BUTTON_Y}-Y$layout",
701 getResourceValue(orientation, 3) 789 getResourceValue(layout, 7)
702 ) 790 )
703 .putFloat( 791 .putFloat(
704 ButtonType.BUTTON_X.toString() + "-X$orientation", 792 "${Settings.PREF_BUTTON_ZL}-X$layout",
705 getResourceValue(orientation, 4) 793 getResourceValue(layout, 8)
706 ) 794 )
707 .putFloat( 795 .putFloat(
708 ButtonType.BUTTON_X.toString() + "-Y$orientation", 796 "${Settings.PREF_BUTTON_ZL}-Y$layout",
709 getResourceValue(orientation, 5) 797 getResourceValue(layout, 9)
710 ) 798 )
711 .putFloat( 799 .putFloat(
712 ButtonType.BUTTON_Y.toString() + "-X$orientation", 800 "${Settings.PREF_BUTTON_ZR}-X$layout",
713 getResourceValue(orientation, 6) 801 getResourceValue(layout, 10)
714 ) 802 )
715 .putFloat( 803 .putFloat(
716 ButtonType.BUTTON_Y.toString() + "-Y$orientation", 804 "${Settings.PREF_BUTTON_ZR}-Y$layout",
717 getResourceValue(orientation, 7) 805 getResourceValue(layout, 11)
718 ) 806 )
719 .putFloat( 807 .putFloat(
720 ButtonType.TRIGGER_ZL.toString() + "-X$orientation", 808 "${Settings.PREF_BUTTON_DPAD}-X$layout",
721 getResourceValue(orientation, 8) 809 getResourceValue(layout, 12)
722 ) 810 )
723 .putFloat( 811 .putFloat(
724 ButtonType.TRIGGER_ZL.toString() + "-Y$orientation", 812 "${Settings.PREF_BUTTON_DPAD}-Y$layout",
725 getResourceValue(orientation, 9) 813 getResourceValue(layout, 13)
726 ) 814 )
727 .putFloat( 815 .putFloat(
728 ButtonType.TRIGGER_ZR.toString() + "-X$orientation", 816 "${Settings.PREF_BUTTON_L}-X$layout",
729 getResourceValue(orientation, 10) 817 getResourceValue(layout, 14)
730 ) 818 )
731 .putFloat( 819 .putFloat(
732 ButtonType.TRIGGER_ZR.toString() + "-Y$orientation", 820 "${Settings.PREF_BUTTON_L}-Y$layout",
733 getResourceValue(orientation, 11) 821 getResourceValue(layout, 15)
734 ) 822 )
735 .putFloat( 823 .putFloat(
736 ButtonType.DPAD_UP.toString() + "-X$orientation", 824 "${Settings.PREF_BUTTON_R}-X$layout",
737 getResourceValue(orientation, 12) 825 getResourceValue(layout, 16)
738 ) 826 )
739 .putFloat( 827 .putFloat(
740 ButtonType.DPAD_UP.toString() + "-Y$orientation", 828 "${Settings.PREF_BUTTON_R}-Y$layout",
741 getResourceValue(orientation, 13) 829 getResourceValue(layout, 17)
742 ) 830 )
743 .putFloat( 831 .putFloat(
744 ButtonType.TRIGGER_L.toString() + "-X$orientation", 832 "${Settings.PREF_BUTTON_PLUS}-X$layout",
745 getResourceValue(orientation, 14) 833 getResourceValue(layout, 18)
746 ) 834 )
747 .putFloat( 835 .putFloat(
748 ButtonType.TRIGGER_L.toString() + "-Y$orientation", 836 "${Settings.PREF_BUTTON_PLUS}-Y$layout",
749 getResourceValue(orientation, 15) 837 getResourceValue(layout, 19)
750 ) 838 )
751 .putFloat( 839 .putFloat(
752 ButtonType.TRIGGER_R.toString() + "-X$orientation", 840 "${Settings.PREF_BUTTON_MINUS}-X$layout",
753 getResourceValue(orientation, 16) 841 getResourceValue(layout, 20)
754 ) 842 )
755 .putFloat( 843 .putFloat(
756 ButtonType.TRIGGER_R.toString() + "-Y$orientation", 844 "${Settings.PREF_BUTTON_MINUS}-Y$layout",
757 getResourceValue(orientation, 17) 845 getResourceValue(layout, 21)
758 ) 846 )
759 .putFloat( 847 .putFloat(
760 ButtonType.BUTTON_PLUS.toString() + "-X$orientation", 848 "${Settings.PREF_BUTTON_HOME}-X$layout",
761 getResourceValue(orientation, 18) 849 getResourceValue(layout, 22)
762 ) 850 )
763 .putFloat( 851 .putFloat(
764 ButtonType.BUTTON_PLUS.toString() + "-Y$orientation", 852 "${Settings.PREF_BUTTON_HOME}-Y$layout",
765 getResourceValue(orientation, 19) 853 getResourceValue(layout, 23)
766 ) 854 )
767 .putFloat( 855 .putFloat(
768 ButtonType.BUTTON_MINUS.toString() + "-X$orientation", 856 "${Settings.PREF_BUTTON_SCREENSHOT}-X$layout",
769 getResourceValue(orientation, 20) 857 getResourceValue(layout, 24)
770 ) 858 )
771 .putFloat( 859 .putFloat(
772 ButtonType.BUTTON_MINUS.toString() + "-Y$orientation", 860 "${Settings.PREF_BUTTON_SCREENSHOT}-Y$layout",
773 getResourceValue(orientation, 21) 861 getResourceValue(layout, 25)
774 ) 862 )
775 .putFloat( 863 .putFloat(
776 ButtonType.BUTTON_HOME.toString() + "-X$orientation", 864 "${Settings.PREF_STICK_R}-X$layout",
777 getResourceValue(orientation, 22) 865 getResourceValue(layout, 26)
778 ) 866 )
779 .putFloat( 867 .putFloat(
780 ButtonType.BUTTON_HOME.toString() + "-Y$orientation", 868 "${Settings.PREF_STICK_R}-Y$layout",
781 getResourceValue(orientation, 23) 869 getResourceValue(layout, 27)
782 ) 870 )
783 .putFloat( 871 .putFloat(
784 ButtonType.BUTTON_CAPTURE.toString() + "-X$orientation", 872 "${Settings.PREF_STICK_L}-X$layout",
785 getResourceValue(orientation, 24) 873 getResourceValue(layout, 28)
786 ) 874 )
787 .putFloat( 875 .putFloat(
788 ButtonType.BUTTON_CAPTURE.toString() + "-Y$orientation", 876 "${Settings.PREF_STICK_L}-Y$layout",
789 getResourceValue(orientation, 25) 877 getResourceValue(layout, 29)
790 ) 878 )
791 .putFloat( 879 .putFloat(
792 ButtonType.STICK_R.toString() + "-X$orientation", 880 "${Settings.PREF_BUTTON_STICK_L}-X$layout",
793 getResourceValue(orientation, 26) 881 getResourceValue(layout, 30)
794 ) 882 )
795 .putFloat( 883 .putFloat(
796 ButtonType.STICK_R.toString() + "-Y$orientation", 884 "${Settings.PREF_BUTTON_STICK_L}-Y$layout",
797 getResourceValue(orientation, 27) 885 getResourceValue(layout, 31)
798 ) 886 )
799 .putFloat( 887 .putFloat(
800 ButtonType.STICK_L.toString() + "-X$orientation", 888 "${Settings.PREF_BUTTON_STICK_R}-X$layout",
801 getResourceValue(orientation, 28) 889 getResourceValue(layout, 32)
802 ) 890 )
803 .putFloat( 891 .putFloat(
804 ButtonType.STICK_L.toString() + "-Y$orientation", 892 "${Settings.PREF_BUTTON_STICK_R}-Y$layout",
805 getResourceValue(orientation, 29) 893 getResourceValue(layout, 33)
806 ) 894 )
807 .apply() 895 .apply()
808 } 896 }
@@ -812,12 +900,30 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
812 } 900 }
813 901
814 companion object { 902 companion object {
903 // Increase this number every time there is a breaking change to every overlay layout
904 const val OVERLAY_VERSION = 1
905
906 // Increase the corresponding layout version number whenever that layout has a breaking change
907 private const val LANDSCAPE_OVERLAY_VERSION = 1
908 private const val PORTRAIT_OVERLAY_VERSION = 1
909 private const val FOLDABLE_OVERLAY_VERSION = 1
910 val overlayLayoutVersions = listOf(
911 LANDSCAPE_OVERLAY_VERSION,
912 PORTRAIT_OVERLAY_VERSION,
913 FOLDABLE_OVERLAY_VERSION
914 )
915
815 private val preferences: SharedPreferences = 916 private val preferences: SharedPreferences =
816 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 917 PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
817 918
818 const val LANDSCAPE = "" 919 const val LANDSCAPE = "_Landscape"
819 const val PORTRAIT = "_Portrait" 920 const val PORTRAIT = "_Portrait"
820 const val FOLDABLE = "_Foldable" 921 const val FOLDABLE = "_Foldable"
922 val overlayLayouts = listOf(
923 LANDSCAPE,
924 PORTRAIT,
925 FOLDABLE
926 )
821 927
822 /** 928 /**
823 * Resizes a [Bitmap] by a given scale factor 929 * Resizes a [Bitmap] by a given scale factor
@@ -948,6 +1054,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
948 * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State). 1054 * @param defaultResId The resource ID of the [Drawable] to get the [Bitmap] of (Default State).
949 * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State). 1055 * @param pressedResId The resource ID of the [Drawable] to get the [Bitmap] of (Pressed State).
950 * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents. 1056 * @param buttonId Identifier for determining what type of button the initialized InputOverlayDrawableButton represents.
1057 * @param prefId Identifier for determining where a button appears on screen.
1058 * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
951 * @return An [InputOverlayDrawableButton] with the correct drawing bounds set. 1059 * @return An [InputOverlayDrawableButton] with the correct drawing bounds set.
952 */ 1060 */
953 private fun initializeOverlayButton( 1061 private fun initializeOverlayButton(
@@ -956,7 +1064,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
956 defaultResId: Int, 1064 defaultResId: Int,
957 pressedResId: Int, 1065 pressedResId: Int,
958 buttonId: Int, 1066 buttonId: Int,
959 orientation: String 1067 prefId: String,
1068 layout: String
960 ): InputOverlayDrawableButton { 1069 ): InputOverlayDrawableButton {
961 // Resources handle for fetching the initial Drawable resource. 1070 // Resources handle for fetching the initial Drawable resource.
962 val res = context.resources 1071 val res = context.resources
@@ -964,17 +1073,20 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
964 // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton. 1073 // SharedPreference to retrieve the X and Y coordinates for the InputOverlayDrawableButton.
965 val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext) 1074 val sPrefs = PreferenceManager.getDefaultSharedPreferences(YuzuApplication.appContext)
966 1075
967 // Decide scale based on button ID and user preference 1076 // Decide scale based on button preference ID and user preference
968 var scale: Float = when (buttonId) { 1077 var scale: Float = when (prefId) {
969 ButtonType.BUTTON_HOME, 1078 Settings.PREF_BUTTON_HOME,
970 ButtonType.BUTTON_CAPTURE, 1079 Settings.PREF_BUTTON_SCREENSHOT,
971 ButtonType.BUTTON_PLUS, 1080 Settings.PREF_BUTTON_PLUS,
972 ButtonType.BUTTON_MINUS -> 0.07f 1081 Settings.PREF_BUTTON_MINUS -> 0.07f
973 1082
974 ButtonType.TRIGGER_L, 1083 Settings.PREF_BUTTON_L,
975 ButtonType.TRIGGER_R, 1084 Settings.PREF_BUTTON_R,
976 ButtonType.TRIGGER_ZL, 1085 Settings.PREF_BUTTON_ZL,
977 ButtonType.TRIGGER_ZR -> 0.26f 1086 Settings.PREF_BUTTON_ZR -> 0.26f
1087
1088 Settings.PREF_BUTTON_STICK_L,
1089 Settings.PREF_BUTTON_STICK_R -> 0.155f
978 1090
979 else -> 0.11f 1091 else -> 0.11f
980 } 1092 }
@@ -984,8 +1096,13 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
984 // Initialize the InputOverlayDrawableButton. 1096 // Initialize the InputOverlayDrawableButton.
985 val defaultStateBitmap = getBitmap(context, defaultResId, scale) 1097 val defaultStateBitmap = getBitmap(context, defaultResId, scale)
986 val pressedStateBitmap = getBitmap(context, pressedResId, scale) 1098 val pressedStateBitmap = getBitmap(context, pressedResId, scale)
987 val overlayDrawable = 1099 val overlayDrawable = InputOverlayDrawableButton(
988 InputOverlayDrawableButton(res, defaultStateBitmap, pressedStateBitmap, buttonId) 1100 res,
1101 defaultStateBitmap,
1102 pressedStateBitmap,
1103 buttonId,
1104 prefId
1105 )
989 1106
990 // Get the minimum and maximum coordinates of the screen where the button can be placed. 1107 // Get the minimum and maximum coordinates of the screen where the button can be placed.
991 val min = windowSize.first 1108 val min = windowSize.first
@@ -993,8 +1110,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
993 1110
994 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. 1111 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
995 // These were set in the input overlay configuration menu. 1112 // These were set in the input overlay configuration menu.
996 val xKey = "$buttonId-X$orientation" 1113 val xKey = "$prefId-X$layout"
997 val yKey = "$buttonId-Y$orientation" 1114 val yKey = "$prefId-Y$layout"
998 val drawableXPercent = sPrefs.getFloat(xKey, 0f) 1115 val drawableXPercent = sPrefs.getFloat(xKey, 0f)
999 val drawableYPercent = sPrefs.getFloat(yKey, 0f) 1116 val drawableYPercent = sPrefs.getFloat(yKey, 0f)
1000 val drawableX = (drawableXPercent * max.x + min.x).toInt() 1117 val drawableX = (drawableXPercent * max.x + min.x).toInt()
@@ -1029,7 +1146,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1029 * @param defaultResId The [Bitmap] resource ID of the default state. 1146 * @param defaultResId The [Bitmap] resource ID of the default state.
1030 * @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction. 1147 * @param pressedOneDirectionResId The [Bitmap] resource ID of the pressed state in one direction.
1031 * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions. 1148 * @param pressedTwoDirectionsResId The [Bitmap] resource ID of the pressed state in two directions.
1032 * @return the initialized [InputOverlayDrawableDpad] 1149 * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
1150 * @return The initialized [InputOverlayDrawableDpad]
1033 */ 1151 */
1034 private fun initializeOverlayDpad( 1152 private fun initializeOverlayDpad(
1035 context: Context, 1153 context: Context,
@@ -1037,7 +1155,7 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1037 defaultResId: Int, 1155 defaultResId: Int,
1038 pressedOneDirectionResId: Int, 1156 pressedOneDirectionResId: Int,
1039 pressedTwoDirectionsResId: Int, 1157 pressedTwoDirectionsResId: Int,
1040 orientation: String 1158 layout: String
1041 ): InputOverlayDrawableDpad { 1159 ): InputOverlayDrawableDpad {
1042 // Resources handle for fetching the initial Drawable resource. 1160 // Resources handle for fetching the initial Drawable resource.
1043 val res = context.resources 1161 val res = context.resources
@@ -1074,8 +1192,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1074 1192
1075 // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay. 1193 // The X and Y coordinates of the InputOverlayDrawableDpad on the InputOverlay.
1076 // These were set in the input overlay configuration menu. 1194 // These were set in the input overlay configuration menu.
1077 val drawableXPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-X$orientation", 0f) 1195 val drawableXPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-X$layout", 0f)
1078 val drawableYPercent = sPrefs.getFloat("${ButtonType.DPAD_UP}-Y$orientation", 0f) 1196 val drawableYPercent = sPrefs.getFloat("${Settings.PREF_BUTTON_DPAD}-Y$layout", 0f)
1079 val drawableX = (drawableXPercent * max.x + min.x).toInt() 1197 val drawableX = (drawableXPercent * max.x + min.x).toInt()
1080 val drawableY = (drawableYPercent * max.y + min.y).toInt() 1198 val drawableY = (drawableYPercent * max.y + min.y).toInt()
1081 val width = overlayDrawable.width 1199 val width = overlayDrawable.width
@@ -1107,7 +1225,9 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1107 * @param pressedResInner Resource ID for the pressed inner image of the joystick. 1225 * @param pressedResInner Resource ID for the pressed inner image of the joystick.
1108 * @param joystick Identifier for which joystick this is. 1226 * @param joystick Identifier for which joystick this is.
1109 * @param button Identifier for which joystick button this is. 1227 * @param button Identifier for which joystick button this is.
1110 * @return the initialized [InputOverlayDrawableJoystick]. 1228 * @param prefId Identifier for determining where a button appears on screen.
1229 * @param layout The current screen layout as determined by [LANDSCAPE], [PORTRAIT], or [FOLDABLE].
1230 * @return The initialized [InputOverlayDrawableJoystick].
1111 */ 1231 */
1112 private fun initializeOverlayJoystick( 1232 private fun initializeOverlayJoystick(
1113 context: Context, 1233 context: Context,
@@ -1117,7 +1237,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1117 pressedResInner: Int, 1237 pressedResInner: Int,
1118 joystick: Int, 1238 joystick: Int,
1119 button: Int, 1239 button: Int,
1120 orientation: String 1240 prefId: String,
1241 layout: String
1121 ): InputOverlayDrawableJoystick { 1242 ): InputOverlayDrawableJoystick {
1122 // Resources handle for fetching the initial Drawable resource. 1243 // Resources handle for fetching the initial Drawable resource.
1123 val res = context.resources 1244 val res = context.resources
@@ -1141,8 +1262,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1141 1262
1142 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay. 1263 // The X and Y coordinates of the InputOverlayDrawableButton on the InputOverlay.
1143 // These were set in the input overlay configuration menu. 1264 // These were set in the input overlay configuration menu.
1144 val drawableXPercent = sPrefs.getFloat("$button-X$orientation", 0f) 1265 val drawableXPercent = sPrefs.getFloat("$prefId-X$layout", 0f)
1145 val drawableYPercent = sPrefs.getFloat("$button-Y$orientation", 0f) 1266 val drawableYPercent = sPrefs.getFloat("$prefId-Y$layout", 0f)
1146 val drawableX = (drawableXPercent * max.x + min.x).toInt() 1267 val drawableX = (drawableXPercent * max.x + min.x).toInt()
1147 val drawableY = (drawableYPercent * max.y + min.y).toInt() 1268 val drawableY = (drawableYPercent * max.y + min.y).toInt()
1148 val outerScale = 1.66f 1269 val outerScale = 1.66f
@@ -1168,7 +1289,8 @@ class InputOverlay(context: Context, attrs: AttributeSet?) :
1168 outerRect, 1289 outerRect,
1169 innerRect, 1290 innerRect,
1170 joystick, 1291 joystick,
1171 button 1292 button,
1293 prefId
1172 ) 1294 )
1173 1295
1174 // Need to set the image's position 1296 // Need to set the image's position
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
index 4a93e0b14..2c28dda88 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableButton.kt
@@ -24,7 +24,8 @@ class InputOverlayDrawableButton(
24 res: Resources, 24 res: Resources,
25 defaultStateBitmap: Bitmap, 25 defaultStateBitmap: Bitmap,
26 pressedStateBitmap: Bitmap, 26 pressedStateBitmap: Bitmap,
27 val buttonId: Int 27 val buttonId: Int,
28 val prefId: String
28) { 29) {
29 // The ID value what motion event is tracking 30 // The ID value what motion event is tracking
30 var trackId: Int 31 var trackId: Int
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
index fb48f584d..518b1e783 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/overlay/InputOverlayDrawableJoystick.kt
@@ -37,7 +37,8 @@ class InputOverlayDrawableJoystick(
37 rectOuter: Rect, 37 rectOuter: Rect,
38 rectInner: Rect, 38 rectInner: Rect,
39 val joystickId: Int, 39 val joystickId: Int,
40 val buttonId: Int 40 val buttonId: Int,
41 val prefId: String
41) { 42) {
42 // The ID value what motion event is tracking 43 // The ID value what motion event is tracking
43 var trackId = -1 44 var trackId = -1
diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
index 18e5fa0b0..aa4a5539a 100644
--- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
+++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/MemoryUtil.kt
@@ -5,35 +5,101 @@ package org.yuzu.yuzu_emu.utils
5 5
6import android.app.ActivityManager 6import android.app.ActivityManager
7import android.content.Context 7import android.content.Context
8import android.os.Build
8import org.yuzu.yuzu_emu.R 9import org.yuzu.yuzu_emu.R
10import org.yuzu.yuzu_emu.YuzuApplication
9import java.util.Locale 11import java.util.Locale
12import kotlin.math.ceil
10 13
11class MemoryUtil(val context: Context) { 14object MemoryUtil {
15 private val context get() = YuzuApplication.appContext
12 16
13 private val Long.floatForm: String 17 private val Float.hundredths: String
14 get() = String.format(Locale.ROOT, "%.2f", this.toDouble()) 18 get() = String.format(Locale.ROOT, "%.2f", this)
15 19
16 private fun bytesToSizeUnit(size: Long): String { 20 // Required total system memory
17 return when { 21 const val REQUIRED_MEMORY = 8
18 size < Kb -> "${size.floatForm} ${context.getString(R.string.memory_byte)}" 22
19 size < Mb -> "${(size / Kb).floatForm} ${context.getString(R.string.memory_kilobyte)}" 23 const val Kb: Float = 1024F
20 size < Gb -> "${(size / Mb).floatForm} ${context.getString(R.string.memory_megabyte)}" 24 const val Mb = Kb * 1024
21 size < Tb -> "${(size / Gb).floatForm} ${context.getString(R.string.memory_gigabyte)}" 25 const val Gb = Mb * 1024
22 size < Pb -> "${(size / Tb).floatForm} ${context.getString(R.string.memory_terabyte)}" 26 const val Tb = Gb * 1024
23 size < Eb -> "${(size / Pb).floatForm} ${context.getString(R.string.memory_petabyte)}" 27 const val Pb = Tb * 1024
24 else -> "${(size / Eb).floatForm} ${context.getString(R.string.memory_exabyte)}" 28 const val Eb = Pb * 1024
29
30 private fun bytesToSizeUnit(size: Float): String =
31 when {
32 size < Kb -> {
33 context.getString(
34 R.string.memory_formatted,
35 size.hundredths,
36 context.getString(R.string.memory_byte)
37 )
38 }
39 size < Mb -> {
40 context.getString(
41 R.string.memory_formatted,
42 (size / Kb).hundredths,
43 context.getString(R.string.memory_kilobyte)
44 )
45 }
46 size < Gb -> {
47 context.getString(
48 R.string.memory_formatted,
49 (size / Mb).hundredths,
50 context.getString(R.string.memory_megabyte)
51 )
52 }
53 size < Tb -> {
54 context.getString(
55 R.string.memory_formatted,
56 (size / Gb).hundredths,
57 context.getString(R.string.memory_gigabyte)
58 )
59 }
60 size < Pb -> {
61 context.getString(
62 R.string.memory_formatted,
63 (size / Tb).hundredths,
64 context.getString(R.string.memory_terabyte)
65 )
66 }
67 size < Eb -> {
68 context.getString(
69 R.string.memory_formatted,
70 (size / Pb).hundredths,
71 context.getString(R.string.memory_petabyte)
72 )
73 }
74 else -> {
75 context.getString(
76 R.string.memory_formatted,
77 (size / Eb).hundredths,
78 context.getString(R.string.memory_exabyte)
79 )
80 }
25 } 81 }
26 }
27 82
28 private val totalMemory = 83 // Devices are unlikely to have 0.5GB increments of memory so we'll just round up to account for
29 with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) { 84 // the potential error created by memInfo.totalMem
85 private val totalMemory: Float
86 get() {
30 val memInfo = ActivityManager.MemoryInfo() 87 val memInfo = ActivityManager.MemoryInfo()
31 getMemoryInfo(memInfo) 88 with(context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager) {
32 memInfo.totalMem 89 getMemoryInfo(memInfo)
90 }
91
92 return ceil(
93 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {
94 memInfo.advertisedMem.toFloat()
95 } else {
96 memInfo.totalMem.toFloat()
97 }
98 )
33 } 99 }
34 100
35 fun isLessThan(minimum: Int, size: Long): Boolean { 101 fun isLessThan(minimum: Int, size: Float): Boolean =
36 return when (size) { 102 when (size) {
37 Kb -> totalMemory < Mb && totalMemory < minimum 103 Kb -> totalMemory < Mb && totalMemory < minimum
38 Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum 104 Mb -> totalMemory < Gb && (totalMemory / Mb) < minimum
39 Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum 105 Gb -> totalMemory < Tb && (totalMemory / Gb) < minimum
@@ -42,18 +108,6 @@ class MemoryUtil(val context: Context) {
42 Eb -> totalMemory / Eb < minimum 108 Eb -> totalMemory / Eb < minimum
43 else -> totalMemory < Kb && totalMemory < minimum 109 else -> totalMemory < Kb && totalMemory < minimum
44 } 110 }
45 } 111
46 112 fun getDeviceRAM(): String = bytesToSizeUnit(totalMemory)
47 fun getDeviceRAM(): String {
48 return bytesToSizeUnit(totalMemory)
49 }
50
51 companion object {
52 const val Kb: Long = 1024
53 const val Mb = Kb * 1024
54 const val Gb = Mb * 1024
55 const val Tb = Gb * 1024
56 const val Pb = Tb * 1024
57 const val Eb = Pb * 1024
58 }
59} 113}
diff --git a/src/android/app/src/main/res/drawable/button_l3.xml b/src/android/app/src/main/res/drawable/button_l3.xml
new file mode 100644
index 000000000..0cb28836e
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_l3.xml
@@ -0,0 +1,128 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:aapt="http://schemas.android.com/aapt"
3 android:width="34.963dp"
4 android:height="37.265dp"
5 android:viewportWidth="34.963"
6 android:viewportHeight="37.265">
7 <path
8 android:fillAlpha="0.5"
9 android:pathData="M19.451,19.024A3.498,3.498 0,0 0,21.165 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L20.327,16.481L20.327,15.7L20.901,15.7c0.757,0 1.714,-0.392 1.714,-1.302C22.621,13.785 22.224,13.229 21.271,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C23.967,19.27 23.017,20.346 21.165,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
10 android:strokeAlpha="0.6">
11 <aapt:attr name="android:fillColor">
12 <gradient
13 android:endX="21.568"
14 android:endY="33.938"
15 android:startX="21.568"
16 android:startY="16.14"
17 android:type="linear">
18 <item
19 android:color="#FFC3C4C5"
20 android:offset="0" />
21 <item
22 android:color="#FFC5C6C6"
23 android:offset="0.03" />
24 <item
25 android:color="#FFC7C7C7"
26 android:offset="0.19" />
27 <item
28 android:color="#DBB5B5B5"
29 android:offset="0.44" />
30 <item
31 android:color="#7F878787"
32 android:offset="1" />
33 </gradient>
34 </aapt:attr>
35 </path>
36 <path
37 android:fillAlpha="0.5"
38 android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
39 android:strokeAlpha="0.6">
40 <aapt:attr name="android:fillColor">
41 <gradient
42 android:endX="17.395"
43 android:endY="18.74"
44 android:startX="17.395"
45 android:startY="-1.296"
46 android:type="linear">
47 <item
48 android:color="#FFC3C4C5"
49 android:offset="0" />
50 <item
51 android:color="#FFC5C6C6"
52 android:offset="0.03" />
53 <item
54 android:color="#FFC7C7C7"
55 android:offset="0.19" />
56 <item
57 android:color="#DBB5B5B5"
58 android:offset="0.44" />
59 <item
60 android:color="#7F878787"
61 android:offset="1" />
62 </gradient>
63 </aapt:attr>
64 </path>
65 <path
66 android:fillAlpha="0.5"
67 android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
68 android:strokeAlpha="0.6">
69 <aapt:attr name="android:fillColor">
70 <gradient
71 android:centerX="17.477"
72 android:centerY="19.92"
73 android:gradientRadius="17.201"
74 android:type="radial">
75 <item
76 android:color="#FFC3C4C5"
77 android:offset="0.58" />
78 <item
79 android:color="#FFC6C6C6"
80 android:offset="0.84" />
81 <item
82 android:color="#FFC7C7C7"
83 android:offset="0.88" />
84 <item
85 android:color="#FFC2C2C2"
86 android:offset="0.91" />
87 <item
88 android:color="#FFB5B5B5"
89 android:offset="0.94" />
90 <item
91 android:color="#FF9E9E9E"
92 android:offset="0.98" />
93 <item
94 android:color="#FF8F8F8F"
95 android:offset="1" />
96 </gradient>
97 </aapt:attr>
98 </path>
99 <path
100 android:fillAlpha="0.5"
101 android:pathData="m12.516,12.729l2,0l0,13.822l6.615,0l0,1.68L12.516,28.231Z"
102 android:strokeAlpha="0.6">
103 <aapt:attr name="android:fillColor">
104 <gradient
105 android:endX="16.829"
106 android:endY="46.882"
107 android:startX="16.829"
108 android:startY="20.479"
109 android:type="linear">
110 <item
111 android:color="#FFC3C4C5"
112 android:offset="0" />
113 <item
114 android:color="#FFC5C6C6"
115 android:offset="0.03" />
116 <item
117 android:color="#FFC7C7C7"
118 android:offset="0.19" />
119 <item
120 android:color="#DBB5B5B5"
121 android:offset="0.44" />
122 <item
123 android:color="#7F878787"
124 android:offset="1" />
125 </gradient>
126 </aapt:attr>
127 </path>
128</vector>
diff --git a/src/android/app/src/main/res/drawable/button_l3_depressed.xml b/src/android/app/src/main/res/drawable/button_l3_depressed.xml
new file mode 100644
index 000000000..b078dedc9
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_l3_depressed.xml
@@ -0,0 +1,75 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:aapt="http://schemas.android.com/aapt"
3 android:width="34.963dp"
4 android:height="37.265dp"
5 android:viewportWidth="34.963"
6 android:viewportHeight="37.265">
7 <path
8 android:fillAlpha="0.3"
9 android:fillColor="#151515"
10 android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
11 android:strokeAlpha="0.3" />
12 <path
13 android:fillAlpha="0.6"
14 android:fillColor="#151515"
15 android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
16 android:strokeAlpha="0.6" />
17 <path
18 android:fillAlpha="0.6"
19 android:pathData="M19.451,19.024A3.498,3.498 0,0 0,21.165 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L20.327,16.481L20.327,15.7L20.901,15.7c0.757,0 1.714,-0.392 1.714,-1.302C22.621,13.785 22.224,13.229 21.271,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C23.967,19.27 23.017,20.346 21.165,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
20 android:strokeAlpha="0.6">
21 <aapt:attr name="android:fillColor">
22 <gradient
23 android:endX="21.568"
24 android:endY="33.938"
25 android:startX="21.568"
26 android:startY="16.14"
27 android:type="linear">
28 <item
29 android:color="#FFC3C4C5"
30 android:offset="0" />
31 <item
32 android:color="#FFC5C6C6"
33 android:offset="0.03" />
34 <item
35 android:color="#FFC7C7C7"
36 android:offset="0.19" />
37 <item
38 android:color="#DBB5B5B5"
39 android:offset="0.44" />
40 <item
41 android:color="#7F878787"
42 android:offset="1" />
43 </gradient>
44 </aapt:attr>
45 </path>
46 <path
47 android:fillAlpha="0.6"
48 android:pathData="m12.516,12.729l2,0l0,13.822l6.615,0l0,1.68L12.516,28.231Z"
49 android:strokeAlpha="0.6">
50 <aapt:attr name="android:fillColor">
51 <gradient
52 android:endX="16.829"
53 android:endY="46.882"
54 android:startX="16.829"
55 android:startY="20.479"
56 android:type="linear">
57 <item
58 android:color="#FFC3C4C5"
59 android:offset="0" />
60 <item
61 android:color="#FFC5C6C6"
62 android:offset="0.03" />
63 <item
64 android:color="#FFC7C7C7"
65 android:offset="0.19" />
66 <item
67 android:color="#DBB5B5B5"
68 android:offset="0.44" />
69 <item
70 android:color="#7F878787"
71 android:offset="1" />
72 </gradient>
73 </aapt:attr>
74 </path>
75</vector>
diff --git a/src/android/app/src/main/res/drawable/button_r3.xml b/src/android/app/src/main/res/drawable/button_r3.xml
new file mode 100644
index 000000000..5c6864e26
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_r3.xml
@@ -0,0 +1,128 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:aapt="http://schemas.android.com/aapt"
3 android:width="34.963dp"
4 android:height="37.265dp"
5 android:viewportWidth="34.963"
6 android:viewportHeight="37.265">
7 <path
8 android:fillAlpha="0.5"
9 android:pathData="m10.781,12.65a19.579,19.579 0,0 1,3.596 -0.302c2.003,0 3.294,0.368 4.199,1.185a3.622,3.622 0,0 1,1.14 2.757c0,1.916 -1.206,3.175 -2.733,3.704l0,0.063c1.119,0.386 1.786,1.421 2.117,2.929 0.474,2.024 0.818,3.424 1.119,3.982l-1.924,0c-0.238,-0.407 -0.561,-1.656 -0.968,-3.466 -0.431,-2.003 -1.206,-2.757 -2.91,-2.82l-1.762,0l0,6.286l-1.873,0zM12.654,19.264l1.916,0c2.003,0 3.273,-1.098 3.273,-2.757 0,-1.873 -1.357,-2.691 -3.336,-2.712a7.649,7.649 0,0 0,-1.852 0.172z"
10 android:strokeAlpha="0.6">
11 <aapt:attr name="android:fillColor">
12 <gradient
13 android:endX="15.506"
14 android:endY="48.977"
15 android:startX="15.506"
16 android:startY="19.659"
17 android:type="linear">
18 <item
19 android:color="#FFC3C4C5"
20 android:offset="0" />
21 <item
22 android:color="#FFC5C6C6"
23 android:offset="0.03" />
24 <item
25 android:color="#FFC7C7C7"
26 android:offset="0.19" />
27 <item
28 android:color="#DBB5B5B5"
29 android:offset="0.44" />
30 <item
31 android:color="#7F878787"
32 android:offset="1" />
33 </gradient>
34 </aapt:attr>
35 </path>
36 <path
37 android:fillAlpha="0.5"
38 android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
39 android:strokeAlpha="0.6">
40 <aapt:attr name="android:fillColor">
41 <gradient
42 android:endX="17.395"
43 android:endY="18.74"
44 android:startX="17.395"
45 android:startY="-1.296"
46 android:type="linear">
47 <item
48 android:color="#FFC3C4C5"
49 android:offset="0" />
50 <item
51 android:color="#FFC5C6C6"
52 android:offset="0.03" />
53 <item
54 android:color="#FFC7C7C7"
55 android:offset="0.19" />
56 <item
57 android:color="#DBB5B5B5"
58 android:offset="0.44" />
59 <item
60 android:color="#7F878787"
61 android:offset="1" />
62 </gradient>
63 </aapt:attr>
64 </path>
65 <path
66 android:fillAlpha="0.5"
67 android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
68 android:strokeAlpha="0.6">
69 <aapt:attr name="android:fillColor">
70 <gradient
71 android:centerX="17.477"
72 android:centerY="19.92"
73 android:gradientRadius="17.201"
74 android:type="radial">
75 <item
76 android:color="#FFC3C4C5"
77 android:offset="0.58" />
78 <item
79 android:color="#FFC6C6C6"
80 android:offset="0.84" />
81 <item
82 android:color="#FFC7C7C7"
83 android:offset="0.88" />
84 <item
85 android:color="#FFC2C2C2"
86 android:offset="0.91" />
87 <item
88 android:color="#FFB5B5B5"
89 android:offset="0.94" />
90 <item
91 android:color="#FF9E9E9E"
92 android:offset="0.98" />
93 <item
94 android:color="#FF8F8F8F"
95 android:offset="1" />
96 </gradient>
97 </aapt:attr>
98 </path>
99 <path
100 android:fillAlpha="0.5"
101 android:pathData="M21.832,19.024A3.498,3.498 0,0 0,23.547 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L22.708,16.481L22.708,15.7L23.282,15.7c0.757,0 1.714,-0.392 1.714,-1.302C25.002,13.785 24.605,13.229 23.652,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C26.349,19.27 25.399,20.346 23.547,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
102 android:strokeAlpha="0.6">
103 <aapt:attr name="android:fillColor">
104 <gradient
105 android:endX="23.949"
106 android:endY="33.938"
107 android:startX="23.949"
108 android:startY="16.14"
109 android:type="linear">
110 <item
111 android:color="#FFC3C4C5"
112 android:offset="0" />
113 <item
114 android:color="#FFC5C6C6"
115 android:offset="0.03" />
116 <item
117 android:color="#FFC7C7C7"
118 android:offset="0.19" />
119 <item
120 android:color="#DBB5B5B5"
121 android:offset="0.44" />
122 <item
123 android:color="#7F878787"
124 android:offset="1" />
125 </gradient>
126 </aapt:attr>
127 </path>
128</vector>
diff --git a/src/android/app/src/main/res/drawable/button_r3_depressed.xml b/src/android/app/src/main/res/drawable/button_r3_depressed.xml
new file mode 100644
index 000000000..20f480179
--- /dev/null
+++ b/src/android/app/src/main/res/drawable/button_r3_depressed.xml
@@ -0,0 +1,75 @@
1<vector xmlns:android="http://schemas.android.com/apk/res/android"
2 xmlns:aapt="http://schemas.android.com/aapt"
3 android:width="34.963dp"
4 android:height="37.265dp"
5 android:viewportWidth="34.963"
6 android:viewportHeight="37.265">
7 <path
8 android:fillAlpha="0.3"
9 android:fillColor="#151515"
10 android:pathData="M16.062,9.353 L9.624,3.405A1.963,1.963 0,0 1,10.955 0l12.88,0a1.963,1.963 135,0 1,1.323 3.405L18.726,9.353a1.961,1.961 135,0 1,-2.664 0z"
11 android:strokeAlpha="0.3" />
12 <path
13 android:fillAlpha="0.6"
14 android:fillColor="#151515"
15 android:pathData="m25.79,5.657l0,0a2.09,2.09 45,0 0,0.23 3.262c3.522,2.402 4.762,5.927 4.741,10.52A13.279,13.279 135,0 1,4.206 19.365c0,-4.516 0.931,-7.71 4.374,-10.107a2.098,2.098 0,0 0,0.233 -3.265l0,0a2.101,2.101 135,0 0,-2.646 -0.169C1.433,9.133 -0.266,13.941 0.033,20.233a17.468,17.468 0,0 0,34.925 -0.868c0,-6.006 -1.971,-10.771 -6.585,-13.917a2.088,2.088 45,0 0,-2.582 0.209z"
16 android:strokeAlpha="0.6" />
17 <path
18 android:fillAlpha="0.6"
19 android:pathData="m10.781,12.65a19.579,19.579 0,0 1,3.596 -0.302c2.003,0 3.294,0.368 4.199,1.185a3.622,3.622 0,0 1,1.14 2.757c0,1.916 -1.206,3.175 -2.733,3.704l0,0.063c1.119,0.386 1.786,1.421 2.117,2.929 0.474,2.024 0.818,3.424 1.119,3.982l-1.924,0c-0.238,-0.407 -0.561,-1.656 -0.968,-3.466 -0.431,-2.003 -1.206,-2.757 -2.91,-2.82l-1.762,0l0,6.286l-1.873,0zM12.654,19.264l1.916,0c2.003,0 3.273,-1.098 3.273,-2.757 0,-1.873 -1.357,-2.691 -3.336,-2.712a7.649,7.649 0,0 0,-1.852 0.172z"
20 android:strokeAlpha="0.6">
21 <aapt:attr name="android:fillColor">
22 <gradient
23 android:endX="15.506"
24 android:endY="48.977"
25 android:startX="15.506"
26 android:startY="19.659"
27 android:type="linear">
28 <item
29 android:color="#FFC3C4C5"
30 android:offset="0" />
31 <item
32 android:color="#FFC5C6C6"
33 android:offset="0.03" />
34 <item
35 android:color="#FFC7C7C7"
36 android:offset="0.19" />
37 <item
38 android:color="#DBB5B5B5"
39 android:offset="0.44" />
40 <item
41 android:color="#7F878787"
42 android:offset="1" />
43 </gradient>
44 </aapt:attr>
45 </path>
46 <path
47 android:fillAlpha="0.6"
48 android:pathData="M21.832,19.024A3.498,3.498 0,0 0,23.547 19.508c1.336,0 1.749,-0.852 1.738,-1.49 0,-1.077 -0.982,-1.537 -1.987,-1.537L22.708,16.481L22.708,15.7L23.282,15.7c0.757,0 1.714,-0.392 1.714,-1.302C25.002,13.785 24.605,13.229 23.652,13.229a2.834,2.834 0,0 0,-1.537 0.529l-0.265,-0.757a3.662,3.662 0,0 1,2.008 -0.59c1.513,0 2.201,0.897 2.201,1.834 0,0.794 -0.474,1.466 -1.421,1.807l0,0.024c0.947,0.19 1.714,0.9 1.714,1.976C26.349,19.27 25.399,20.346 23.547,20.346a3.929,3.929 135,0 1,-1.998 -0.529z"
49 android:strokeAlpha="0.6">
50 <aapt:attr name="android:fillColor">
51 <gradient
52 android:endX="23.949"
53 android:endY="33.938"
54 android:startX="23.949"
55 android:startY="16.14"
56 android:type="linear">
57 <item
58 android:color="#FFC3C4C5"
59 android:offset="0" />
60 <item
61 android:color="#FFC5C6C6"
62 android:offset="0.03" />
63 <item
64 android:color="#FFC7C7C7"
65 android:offset="0.19" />
66 <item
67 android:color="#DBB5B5B5"
68 android:offset="0.44" />
69 <item
70 android:color="#7F878787"
71 android:offset="1" />
72 </gradient>
73 </aapt:attr>
74 </path>
75</vector>
diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml
index 6d092f7a9..200b99185 100644
--- a/src/android/app/src/main/res/values/arrays.xml
+++ b/src/android/app/src/main/res/values/arrays.xml
@@ -205,6 +205,8 @@
205 <item>@string/gamepad_d_pad</item> 205 <item>@string/gamepad_d_pad</item>
206 <item>@string/gamepad_left_stick</item> 206 <item>@string/gamepad_left_stick</item>
207 <item>@string/gamepad_right_stick</item> 207 <item>@string/gamepad_right_stick</item>
208 <item>L3</item>
209 <item>R3</item>
208 <item>@string/gamepad_home</item> 210 <item>@string/gamepad_home</item>
209 <item>@string/gamepad_screenshot</item> 211 <item>@string/gamepad_screenshot</item>
210 </string-array> 212 </string-array>
diff --git a/src/android/app/src/main/res/values/integers.xml b/src/android/app/src/main/res/values/integers.xml
index 2e93b408c..5e39bc7d9 100644
--- a/src/android/app/src/main/res/values/integers.xml
+++ b/src/android/app/src/main/res/values/integers.xml
@@ -33,6 +33,10 @@
33 <integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer> 33 <integer name="SWITCH_BUTTON_CAPTURE_Y">950</integer>
34 <integer name="SWITCH_BUTTON_DPAD_X">260</integer> 34 <integer name="SWITCH_BUTTON_DPAD_X">260</integer>
35 <integer name="SWITCH_BUTTON_DPAD_Y">790</integer> 35 <integer name="SWITCH_BUTTON_DPAD_Y">790</integer>
36 <integer name="SWITCH_BUTTON_STICK_L_X">870</integer>
37 <integer name="SWITCH_BUTTON_STICK_L_Y">400</integer>
38 <integer name="SWITCH_BUTTON_STICK_R_X">960</integer>
39 <integer name="SWITCH_BUTTON_STICK_R_Y">430</integer>
36 40
37 <!-- Default SWITCH portrait layout --> 41 <!-- Default SWITCH portrait layout -->
38 <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer> 42 <integer name="SWITCH_BUTTON_A_X_PORTRAIT">840</integer>
@@ -65,6 +69,10 @@
65 <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer> 69 <integer name="SWITCH_BUTTON_CAPTURE_Y_PORTRAIT">950</integer>
66 <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer> 70 <integer name="SWITCH_BUTTON_DPAD_X_PORTRAIT">240</integer>
67 <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer> 71 <integer name="SWITCH_BUTTON_DPAD_Y_PORTRAIT">840</integer>
72 <integer name="SWITCH_BUTTON_STICK_L_X_PORTRAIT">730</integer>
73 <integer name="SWITCH_BUTTON_STICK_L_Y_PORTRAIT">510</integer>
74 <integer name="SWITCH_BUTTON_STICK_R_X_PORTRAIT">900</integer>
75 <integer name="SWITCH_BUTTON_STICK_R_Y_PORTRAIT">540</integer>
68 76
69 <!-- Default SWITCH foldable layout --> 77 <!-- Default SWITCH foldable layout -->
70 <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer> 78 <integer name="SWITCH_BUTTON_A_X_FOLDABLE">840</integer>
@@ -97,5 +105,9 @@
97 <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer> 105 <integer name="SWITCH_BUTTON_CAPTURE_Y_FOLDABLE">470</integer>
98 <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer> 106 <integer name="SWITCH_BUTTON_DPAD_X_FOLDABLE">240</integer>
99 <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer> 107 <integer name="SWITCH_BUTTON_DPAD_Y_FOLDABLE">390</integer>
108 <integer name="SWITCH_BUTTON_STICK_L_X_FOLDABLE">550</integer>
109 <integer name="SWITCH_BUTTON_STICK_L_Y_FOLDABLE">210</integer>
110 <integer name="SWITCH_BUTTON_STICK_R_X_FOLDABLE">550</integer>
111 <integer name="SWITCH_BUTTON_STICK_R_Y_FOLDABLE">280</integer>
100 112
101</resources> 113</resources>
diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml
index af7450619..b3c737979 100644
--- a/src/android/app/src/main/res/values/strings.xml
+++ b/src/android/app/src/main/res/values/strings.xml
@@ -273,6 +273,7 @@
273 <string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string> 273 <string name="fatal_error_message">A fatal error occurred. Check the log for details.\nContinuing emulation may result in crashes and bugs.</string>
274 <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string> 274 <string name="performance_warning">Turning off this setting will significantly reduce emulation performance! For the best experience, it is recommended that you leave this setting enabled.</string>
275 <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string> 275 <string name="device_memory_inadequate">Device RAM: %1$s\nRecommended: %2$s</string>
276 <string name="memory_formatted">%1$s %2$s</string>
276 277
277 <!-- Region Names --> 278 <!-- Region Names -->
278 <string name="region_japan">Japan</string> 279 <string name="region_japan">Japan</string>
diff --git a/src/core/core.cpp b/src/core/core.cpp
index b74fd0a58..9e3eb3795 100644
--- a/src/core/core.cpp
+++ b/src/core/core.cpp
@@ -27,6 +27,7 @@
27#include "core/file_sys/savedata_factory.h" 27#include "core/file_sys/savedata_factory.h"
28#include "core/file_sys/vfs_concat.h" 28#include "core/file_sys/vfs_concat.h"
29#include "core/file_sys/vfs_real.h" 29#include "core/file_sys/vfs_real.h"
30#include "core/gpu_dirty_memory_manager.h"
30#include "core/hid/hid_core.h" 31#include "core/hid/hid_core.h"
31#include "core/hle/kernel/k_memory_manager.h" 32#include "core/hle/kernel/k_memory_manager.h"
32#include "core/hle/kernel/k_process.h" 33#include "core/hle/kernel/k_process.h"
@@ -130,7 +131,10 @@ FileSys::VirtualFile GetGameFileFromPath(const FileSys::VirtualFilesystem& vfs,
130struct System::Impl { 131struct System::Impl {
131 explicit Impl(System& system) 132 explicit Impl(System& system)
132 : kernel{system}, fs_controller{system}, memory{system}, hid_core{}, room_network{}, 133 : kernel{system}, fs_controller{system}, memory{system}, hid_core{}, room_network{},
133 cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system} {} 134 cpu_manager{system}, reporter{system}, applet_manager{system}, time_manager{system},
135 gpu_dirty_memory_write_manager{} {
136 memory.SetGPUDirtyManagers(gpu_dirty_memory_write_manager);
137 }
134 138
135 void Initialize(System& system) { 139 void Initialize(System& system) {
136 device_memory = std::make_unique<Core::DeviceMemory>(); 140 device_memory = std::make_unique<Core::DeviceMemory>();
@@ -234,6 +238,8 @@ struct System::Impl {
234 // Setting changes may require a full system reinitialization (e.g., disabling multicore). 238 // Setting changes may require a full system reinitialization (e.g., disabling multicore).
235 ReinitializeIfNecessary(system); 239 ReinitializeIfNecessary(system);
236 240
241 memory.SetGPUDirtyManagers(gpu_dirty_memory_write_manager);
242
237 kernel.Initialize(); 243 kernel.Initialize();
238 cpu_manager.Initialize(); 244 cpu_manager.Initialize();
239 245
@@ -540,6 +546,9 @@ struct System::Impl {
540 546
541 std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{}; 547 std::array<u64, Core::Hardware::NUM_CPU_CORES> dynarmic_ticks{};
542 std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{}; 548 std::array<MicroProfileToken, Core::Hardware::NUM_CPU_CORES> microprofile_cpu{};
549
550 std::array<Core::GPUDirtyMemoryManager, Core::Hardware::NUM_CPU_CORES>
551 gpu_dirty_memory_write_manager{};
543}; 552};
544 553
545System::System() : impl{std::make_unique<Impl>(*this)} {} 554System::System() : impl{std::make_unique<Impl>(*this)} {}
@@ -629,10 +638,31 @@ void System::PrepareReschedule(const u32 core_index) {
629 impl->kernel.PrepareReschedule(core_index); 638 impl->kernel.PrepareReschedule(core_index);
630} 639}
631 640
641Core::GPUDirtyMemoryManager& System::CurrentGPUDirtyMemoryManager() {
642 const std::size_t core = impl->kernel.GetCurrentHostThreadID();
643 return impl->gpu_dirty_memory_write_manager[core < Core::Hardware::NUM_CPU_CORES
644 ? core
645 : Core::Hardware::NUM_CPU_CORES - 1];
646}
647
648/// Provides a constant reference to the current gou dirty memory manager.
649const Core::GPUDirtyMemoryManager& System::CurrentGPUDirtyMemoryManager() const {
650 const std::size_t core = impl->kernel.GetCurrentHostThreadID();
651 return impl->gpu_dirty_memory_write_manager[core < Core::Hardware::NUM_CPU_CORES
652 ? core
653 : Core::Hardware::NUM_CPU_CORES - 1];
654}
655
632size_t System::GetCurrentHostThreadID() const { 656size_t System::GetCurrentHostThreadID() const {
633 return impl->kernel.GetCurrentHostThreadID(); 657 return impl->kernel.GetCurrentHostThreadID();
634} 658}
635 659
660void System::GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback) {
661 for (auto& manager : impl->gpu_dirty_memory_write_manager) {
662 manager.Gather(callback);
663 }
664}
665
636PerfStatsResults System::GetAndResetPerfStats() { 666PerfStatsResults System::GetAndResetPerfStats() {
637 return impl->GetAndResetPerfStats(); 667 return impl->GetAndResetPerfStats();
638} 668}
diff --git a/src/core/core.h b/src/core/core.h
index 93afc9303..14b2f7785 100644
--- a/src/core/core.h
+++ b/src/core/core.h
@@ -108,9 +108,10 @@ class CpuManager;
108class Debugger; 108class Debugger;
109class DeviceMemory; 109class DeviceMemory;
110class ExclusiveMonitor; 110class ExclusiveMonitor;
111class SpeedLimiter; 111class GPUDirtyMemoryManager;
112class PerfStats; 112class PerfStats;
113class Reporter; 113class Reporter;
114class SpeedLimiter;
114class TelemetrySession; 115class TelemetrySession;
115 116
116struct PerfStatsResults; 117struct PerfStatsResults;
@@ -225,6 +226,14 @@ public:
225 /// Prepare the core emulation for a reschedule 226 /// Prepare the core emulation for a reschedule
226 void PrepareReschedule(u32 core_index); 227 void PrepareReschedule(u32 core_index);
227 228
229 /// Provides a reference to the gou dirty memory manager.
230 [[nodiscard]] Core::GPUDirtyMemoryManager& CurrentGPUDirtyMemoryManager();
231
232 /// Provides a constant reference to the current gou dirty memory manager.
233 [[nodiscard]] const Core::GPUDirtyMemoryManager& CurrentGPUDirtyMemoryManager() const;
234
235 void GatherGPUDirtyMemory(std::function<void(VAddr, size_t)>& callback);
236
228 [[nodiscard]] size_t GetCurrentHostThreadID() const; 237 [[nodiscard]] size_t GetCurrentHostThreadID() const;
229 238
230 /// Gets and resets core performance statistics 239 /// Gets and resets core performance statistics
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp
index 4f0a3f8ea..e6112a3c9 100644
--- a/src/core/core_timing.cpp
+++ b/src/core/core_timing.cpp
@@ -253,9 +253,6 @@ void CoreTiming::ThreadLoop() {
253 auto wait_time = *next_time - GetGlobalTimeNs().count(); 253 auto wait_time = *next_time - GetGlobalTimeNs().count();
254 if (wait_time > 0) { 254 if (wait_time > 0) {
255#ifdef _WIN32 255#ifdef _WIN32
256 const auto timer_resolution_ns =
257 Common::Windows::GetCurrentTimerResolution().count();
258
259 while (!paused && !event.IsSet() && wait_time > 0) { 256 while (!paused && !event.IsSet() && wait_time > 0) {
260 wait_time = *next_time - GetGlobalTimeNs().count(); 257 wait_time = *next_time - GetGlobalTimeNs().count();
261 258
@@ -316,4 +313,10 @@ std::chrono::microseconds CoreTiming::GetGlobalTimeUs() const {
316 return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)}; 313 return std::chrono::microseconds{Common::WallClock::CPUTickToUS(cpu_ticks)};
317} 314}
318 315
316#ifdef _WIN32
317void CoreTiming::SetTimerResolutionNs(std::chrono::nanoseconds ns) {
318 timer_resolution_ns = ns.count();
319}
320#endif
321
319} // namespace Core::Timing 322} // namespace Core::Timing
diff --git a/src/core/core_timing.h b/src/core/core_timing.h
index 10db1de55..5bca1c78d 100644
--- a/src/core/core_timing.h
+++ b/src/core/core_timing.h
@@ -131,6 +131,10 @@ public:
131 /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. 131 /// Checks for events manually and returns time in nanoseconds for next event, threadsafe.
132 std::optional<s64> Advance(); 132 std::optional<s64> Advance();
133 133
134#ifdef _WIN32
135 void SetTimerResolutionNs(std::chrono::nanoseconds ns);
136#endif
137
134private: 138private:
135 struct Event; 139 struct Event;
136 140
@@ -143,6 +147,10 @@ private:
143 147
144 s64 global_timer = 0; 148 s64 global_timer = 0;
145 149
150#ifdef _WIN32
151 s64 timer_resolution_ns;
152#endif
153
146 // The queue is a min-heap using std::make_heap/push_heap/pop_heap. 154 // The queue is a min-heap using std::make_heap/push_heap/pop_heap.
147 // We don't use std::priority_queue because we need to be able to serialize, unserialize and 155 // We don't use std::priority_queue because we need to be able to serialize, unserialize and
148 // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't 156 // erase arbitrary events (RemoveEvent()) regardless of the queue order. These aren't
diff --git a/src/core/file_sys/fsmitm_romfsbuild.cpp b/src/core/file_sys/fsmitm_romfsbuild.cpp
index 1ff83c08c..e39c7b62b 100644
--- a/src/core/file_sys/fsmitm_romfsbuild.cpp
+++ b/src/core/file_sys/fsmitm_romfsbuild.cpp
@@ -105,19 +105,11 @@ static u64 romfs_get_hash_table_count(u64 num_entries) {
105 return count; 105 return count;
106} 106}
107 107
108void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext_dir, 108void RomFSBuildContext::VisitDirectory(VirtualDir romfs_dir, VirtualDir ext_dir,
109 std::shared_ptr<RomFSBuildDirectoryContext> parent) { 109 std::shared_ptr<RomFSBuildDirectoryContext> parent) {
110 std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs; 110 std::vector<std::shared_ptr<RomFSBuildDirectoryContext>> child_dirs;
111 111
112 VirtualDir dir; 112 const auto entries = romfs_dir->GetEntries();
113
114 if (parent->path_len == 0) {
115 dir = root_romfs;
116 } else {
117 dir = root_romfs->GetDirectoryRelative(parent->path);
118 }
119
120 const auto entries = dir->GetEntries();
121 113
122 for (const auto& kv : entries) { 114 for (const auto& kv : entries) {
123 if (kv.second == VfsEntryType::Directory) { 115 if (kv.second == VfsEntryType::Directory) {
@@ -127,7 +119,7 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext_dir
127 child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size()); 119 child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
128 child->path = parent->path + "/" + kv.first; 120 child->path = parent->path + "/" + kv.first;
129 121
130 if (ext_dir != nullptr && ext_dir->GetFileRelative(child->path + ".stub") != nullptr) { 122 if (ext_dir != nullptr && ext_dir->GetFile(kv.first + ".stub") != nullptr) {
131 continue; 123 continue;
132 } 124 }
133 125
@@ -144,17 +136,17 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext_dir
144 child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size()); 136 child->path_len = child->cur_path_ofs + static_cast<u32>(kv.first.size());
145 child->path = parent->path + "/" + kv.first; 137 child->path = parent->path + "/" + kv.first;
146 138
147 if (ext_dir != nullptr && ext_dir->GetFileRelative(child->path + ".stub") != nullptr) { 139 if (ext_dir != nullptr && ext_dir->GetFile(kv.first + ".stub") != nullptr) {
148 continue; 140 continue;
149 } 141 }
150 142
151 // Sanity check on path_len 143 // Sanity check on path_len
152 ASSERT(child->path_len < FS_MAX_PATH); 144 ASSERT(child->path_len < FS_MAX_PATH);
153 145
154 child->source = root_romfs->GetFileRelative(child->path); 146 child->source = romfs_dir->GetFile(kv.first);
155 147
156 if (ext_dir != nullptr) { 148 if (ext_dir != nullptr) {
157 if (const auto ips = ext_dir->GetFileRelative(child->path + ".ips")) { 149 if (const auto ips = ext_dir->GetFile(kv.first + ".ips")) {
158 if (auto patched = PatchIPS(child->source, ips)) { 150 if (auto patched = PatchIPS(child->source, ips)) {
159 child->source = std::move(patched); 151 child->source = std::move(patched);
160 } 152 }
@@ -168,23 +160,27 @@ void RomFSBuildContext::VisitDirectory(VirtualDir root_romfs, VirtualDir ext_dir
168 } 160 }
169 161
170 for (auto& child : child_dirs) { 162 for (auto& child : child_dirs) {
171 this->VisitDirectory(root_romfs, ext_dir, child); 163 auto subdir_name = std::string_view(child->path).substr(child->cur_path_ofs);
164 auto child_romfs_dir = romfs_dir->GetSubdirectory(subdir_name);
165 auto child_ext_dir = ext_dir != nullptr ? ext_dir->GetSubdirectory(subdir_name) : nullptr;
166 this->VisitDirectory(child_romfs_dir, child_ext_dir, child);
172 } 167 }
173} 168}
174 169
175bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, 170bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
176 std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) { 171 std::shared_ptr<RomFSBuildDirectoryContext> dir_ctx) {
177 // Check whether it's already in the known directories. 172 // Check whether it's already in the known directories.
178 const auto existing = directories.find(dir_ctx->path); 173 const auto [it, is_new] = directories.emplace(dir_ctx->path, nullptr);
179 if (existing != directories.end()) 174 if (!is_new) {
180 return false; 175 return false;
176 }
181 177
182 // Add a new directory. 178 // Add a new directory.
183 num_dirs++; 179 num_dirs++;
184 dir_table_size += 180 dir_table_size +=
185 sizeof(RomFSDirectoryEntry) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4); 181 sizeof(RomFSDirectoryEntry) + Common::AlignUp(dir_ctx->path_len - dir_ctx->cur_path_ofs, 4);
186 dir_ctx->parent = parent_dir_ctx; 182 dir_ctx->parent = parent_dir_ctx;
187 directories.emplace(dir_ctx->path, dir_ctx); 183 it->second = dir_ctx;
188 184
189 return true; 185 return true;
190} 186}
@@ -192,8 +188,8 @@ bool RomFSBuildContext::AddDirectory(std::shared_ptr<RomFSBuildDirectoryContext>
192bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx, 188bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> parent_dir_ctx,
193 std::shared_ptr<RomFSBuildFileContext> file_ctx) { 189 std::shared_ptr<RomFSBuildFileContext> file_ctx) {
194 // Check whether it's already in the known files. 190 // Check whether it's already in the known files.
195 const auto existing = files.find(file_ctx->path); 191 const auto [it, is_new] = files.emplace(file_ctx->path, nullptr);
196 if (existing != files.end()) { 192 if (!is_new) {
197 return false; 193 return false;
198 } 194 }
199 195
@@ -202,7 +198,7 @@ bool RomFSBuildContext::AddFile(std::shared_ptr<RomFSBuildDirectoryContext> pare
202 file_table_size += 198 file_table_size +=
203 sizeof(RomFSFileEntry) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4); 199 sizeof(RomFSFileEntry) + Common::AlignUp(file_ctx->path_len - file_ctx->cur_path_ofs, 4);
204 file_ctx->parent = parent_dir_ctx; 200 file_ctx->parent = parent_dir_ctx;
205 files.emplace(file_ctx->path, file_ctx); 201 it->second = file_ctx;
206 202
207 return true; 203 return true;
208} 204}
diff --git a/src/core/gpu_dirty_memory_manager.h b/src/core/gpu_dirty_memory_manager.h
new file mode 100644
index 000000000..9687531e8
--- /dev/null
+++ b/src/core/gpu_dirty_memory_manager.h
@@ -0,0 +1,122 @@
1// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
2// SPDX-License-Identifier: GPL-3.0-or-later
3
4#pragma once
5
6#include <atomic>
7#include <bit>
8#include <functional>
9#include <mutex>
10#include <utility>
11#include <vector>
12
13#include "core/memory.h"
14
15namespace Core {
16
17class GPUDirtyMemoryManager {
18public:
19 GPUDirtyMemoryManager() : current{default_transform} {
20 back_buffer.reserve(256);
21 front_buffer.reserve(256);
22 }
23
24 ~GPUDirtyMemoryManager() = default;
25
26 void Collect(VAddr address, size_t size) {
27 TransformAddress t = BuildTransform(address, size);
28 TransformAddress tmp, original;
29 do {
30 tmp = current.load(std::memory_order_acquire);
31 original = tmp;
32 if (tmp.address != t.address) {
33 if (IsValid(tmp.address)) {
34 std::scoped_lock lk(guard);
35 back_buffer.emplace_back(tmp);
36 current.exchange(t, std::memory_order_relaxed);
37 return;
38 }
39 tmp.address = t.address;
40 tmp.mask = 0;
41 }
42 if ((tmp.mask | t.mask) == tmp.mask) {
43 return;
44 }
45 tmp.mask |= t.mask;
46 } while (!current.compare_exchange_weak(original, tmp, std::memory_order_release,
47 std::memory_order_relaxed));
48 }
49
50 void Gather(std::function<void(VAddr, size_t)>& callback) {
51 {
52 std::scoped_lock lk(guard);
53 TransformAddress t = current.exchange(default_transform, std::memory_order_relaxed);
54 front_buffer.swap(back_buffer);
55 if (IsValid(t.address)) {
56 front_buffer.emplace_back(t);
57 }
58 }
59 for (auto& transform : front_buffer) {
60 size_t offset = 0;
61 u64 mask = transform.mask;
62 while (mask != 0) {
63 const size_t empty_bits = std::countr_zero(mask);
64 offset += empty_bits << align_bits;
65 mask = mask >> empty_bits;
66
67 const size_t continuous_bits = std::countr_one(mask);
68 callback((static_cast<VAddr>(transform.address) << page_bits) + offset,
69 continuous_bits << align_bits);
70 mask = continuous_bits < align_size ? (mask >> continuous_bits) : 0;
71 offset += continuous_bits << align_bits;
72 }
73 }
74 front_buffer.clear();
75 }
76
77private:
78 struct alignas(8) TransformAddress {
79 u32 address;
80 u32 mask;
81 };
82
83 constexpr static size_t page_bits = Memory::YUZU_PAGEBITS - 1;
84 constexpr static size_t page_size = 1ULL << page_bits;
85 constexpr static size_t page_mask = page_size - 1;
86
87 constexpr static size_t align_bits = 6U;
88 constexpr static size_t align_size = 1U << align_bits;
89 constexpr static size_t align_mask = align_size - 1;
90 constexpr static TransformAddress default_transform = {.address = ~0U, .mask = 0U};
91
92 bool IsValid(VAddr address) {
93 return address < (1ULL << 39);
94 }
95
96 template <typename T>
97 T CreateMask(size_t top_bit, size_t minor_bit) {
98 T mask = ~T(0);
99 mask <<= (sizeof(T) * 8 - top_bit);
100 mask >>= (sizeof(T) * 8 - top_bit);
101 mask >>= minor_bit;
102 mask <<= minor_bit;
103 return mask;
104 }
105
106 TransformAddress BuildTransform(VAddr address, size_t size) {
107 const size_t minor_address = address & page_mask;
108 const size_t minor_bit = minor_address >> align_bits;
109 const size_t top_bit = (minor_address + size + align_mask) >> align_bits;
110 TransformAddress result{};
111 result.address = static_cast<u32>(address >> page_bits);
112 result.mask = CreateMask<u32>(top_bit, minor_bit);
113 return result;
114 }
115
116 std::atomic<TransformAddress> current{};
117 std::mutex guard;
118 std::vector<TransformAddress> back_buffer;
119 std::vector<TransformAddress> front_buffer;
120};
121
122} // namespace Core
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp
index 1ebc32c1e..94bd656fe 100644
--- a/src/core/hid/emulated_controller.cpp
+++ b/src/core/hid/emulated_controller.cpp
@@ -1243,10 +1243,12 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
1243 auto& nfc_output_device = output_devices[3]; 1243 auto& nfc_output_device = output_devices[3];
1244 1244
1245 if (device_index == EmulatedDeviceIndex::LeftIndex) { 1245 if (device_index == EmulatedDeviceIndex::LeftIndex) {
1246 controller.left_polling_mode = polling_mode;
1246 return left_output_device->SetPollingMode(polling_mode); 1247 return left_output_device->SetPollingMode(polling_mode);
1247 } 1248 }
1248 1249
1249 if (device_index == EmulatedDeviceIndex::RightIndex) { 1250 if (device_index == EmulatedDeviceIndex::RightIndex) {
1251 controller.right_polling_mode = polling_mode;
1250 const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode); 1252 const auto virtual_nfc_result = nfc_output_device->SetPollingMode(polling_mode);
1251 const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode); 1253 const auto mapped_nfc_result = right_output_device->SetPollingMode(polling_mode);
1252 1254
@@ -1261,12 +1263,22 @@ Common::Input::DriverResult EmulatedController::SetPollingMode(
1261 return mapped_nfc_result; 1263 return mapped_nfc_result;
1262 } 1264 }
1263 1265
1266 controller.left_polling_mode = polling_mode;
1267 controller.right_polling_mode = polling_mode;
1264 left_output_device->SetPollingMode(polling_mode); 1268 left_output_device->SetPollingMode(polling_mode);
1265 right_output_device->SetPollingMode(polling_mode); 1269 right_output_device->SetPollingMode(polling_mode);
1266 nfc_output_device->SetPollingMode(polling_mode); 1270 nfc_output_device->SetPollingMode(polling_mode);
1267 return Common::Input::DriverResult::Success; 1271 return Common::Input::DriverResult::Success;
1268} 1272}
1269 1273
1274Common::Input::PollingMode EmulatedController::GetPollingMode(
1275 EmulatedDeviceIndex device_index) const {
1276 if (device_index == EmulatedDeviceIndex::LeftIndex) {
1277 return controller.left_polling_mode;
1278 }
1279 return controller.right_polling_mode;
1280}
1281
1270bool EmulatedController::SetCameraFormat( 1282bool EmulatedController::SetCameraFormat(
1271 Core::IrSensor::ImageTransferProcessorFormat camera_format) { 1283 Core::IrSensor::ImageTransferProcessorFormat camera_format) {
1272 LOG_INFO(Service_HID, "Set camera format {}", camera_format); 1284 LOG_INFO(Service_HID, "Set camera format {}", camera_format);
diff --git a/src/core/hid/emulated_controller.h b/src/core/hid/emulated_controller.h
index d511e5fac..88d77db8d 100644
--- a/src/core/hid/emulated_controller.h
+++ b/src/core/hid/emulated_controller.h
@@ -143,6 +143,8 @@ struct ControllerStatus {
143 CameraState camera_state{}; 143 CameraState camera_state{};
144 RingSensorForce ring_analog_state{}; 144 RingSensorForce ring_analog_state{};
145 NfcState nfc_state{}; 145 NfcState nfc_state{};
146 Common::Input::PollingMode left_polling_mode{};
147 Common::Input::PollingMode right_polling_mode{};
146}; 148};
147 149
148enum class ControllerTriggerType { 150enum class ControllerTriggerType {
@@ -370,6 +372,12 @@ public:
370 */ 372 */
371 Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index, 373 Common::Input::DriverResult SetPollingMode(EmulatedDeviceIndex device_index,
372 Common::Input::PollingMode polling_mode); 374 Common::Input::PollingMode polling_mode);
375 /**
376 * Get the current polling mode from a controller
377 * @param device_index index of the controller to set the polling mode
378 * @return current polling mode
379 */
380 Common::Input::PollingMode GetPollingMode(EmulatedDeviceIndex device_index) const;
373 381
374 /** 382 /**
375 * Sets the desired camera format to be polled from a controller 383 * Sets the desired camera format to be polled from a controller
diff --git a/src/core/hle/kernel/k_thread.h b/src/core/hle/kernel/k_thread.h
index dd662b3f8..d178c2453 100644
--- a/src/core/hle/kernel/k_thread.h
+++ b/src/core/hle/kernel/k_thread.h
@@ -338,6 +338,15 @@ public:
338 return m_parent != nullptr; 338 return m_parent != nullptr;
339 } 339 }
340 340
341 std::span<KSynchronizationObject*> GetSynchronizationObjectBuffer() {
342 return m_sync_object_buffer.sync_objects;
343 }
344
345 std::span<Handle> GetHandleBuffer() {
346 return {m_sync_object_buffer.handles.data() + Svc::ArgumentHandleCountMax,
347 Svc::ArgumentHandleCountMax};
348 }
349
341 u16 GetUserDisableCount() const; 350 u16 GetUserDisableCount() const;
342 void SetInterruptFlag(); 351 void SetInterruptFlag();
343 void ClearInterruptFlag(); 352 void ClearInterruptFlag();
@@ -855,6 +864,7 @@ private:
855 u32* m_light_ipc_data{}; 864 u32* m_light_ipc_data{};
856 KProcessAddress m_tls_address{}; 865 KProcessAddress m_tls_address{};
857 KLightLock m_activity_pause_lock; 866 KLightLock m_activity_pause_lock;
867 SyncObjectBuffer m_sync_object_buffer{};
858 s64 m_schedule_count{}; 868 s64 m_schedule_count{};
859 s64 m_last_scheduled_tick{}; 869 s64 m_last_scheduled_tick{};
860 std::array<QueueEntry, Core::Hardware::NUM_CPU_CORES> m_per_core_priority_queue_entry{}; 870 std::array<QueueEntry, Core::Hardware::NUM_CPU_CORES> m_per_core_priority_queue_entry{};
diff --git a/src/core/hle/kernel/svc/svc_ipc.cpp b/src/core/hle/kernel/svc/svc_ipc.cpp
index 60247df2e..bb94f6934 100644
--- a/src/core/hle/kernel/svc/svc_ipc.cpp
+++ b/src/core/hle/kernel/svc/svc_ipc.cpp
@@ -38,22 +38,31 @@ Result SendAsyncRequestWithUserBuffer(Core::System& system, Handle* out_event_ha
38 38
39Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_addr, s32 num_handles, 39Result ReplyAndReceive(Core::System& system, s32* out_index, uint64_t handles_addr, s32 num_handles,
40 Handle reply_target, s64 timeout_ns) { 40 Handle reply_target, s64 timeout_ns) {
41 // Ensure number of handles is valid.
42 R_UNLESS(0 <= num_handles && num_handles <= ArgumentHandleCountMax, ResultOutOfRange);
43
44 // Get the synchronization context.
41 auto& kernel = system.Kernel(); 45 auto& kernel = system.Kernel();
42 auto& handle_table = GetCurrentProcess(kernel).GetHandleTable(); 46 auto& handle_table = GetCurrentProcess(kernel).GetHandleTable();
43 47 auto objs = GetCurrentThread(kernel).GetSynchronizationObjectBuffer();
44 R_UNLESS(0 <= num_handles && num_handles <= ArgumentHandleCountMax, ResultOutOfRange); 48 auto handles = GetCurrentThread(kernel).GetHandleBuffer();
45 R_UNLESS(GetCurrentMemory(kernel).IsValidVirtualAddressRange( 49
46 handles_addr, static_cast<u64>(sizeof(Handle) * num_handles)), 50 // Copy user handles.
47 ResultInvalidPointer); 51 if (num_handles > 0) {
48 52 // Ensure we can try to get the handles.
49 std::array<Handle, Svc::ArgumentHandleCountMax> handles; 53 R_UNLESS(GetCurrentMemory(kernel).IsValidVirtualAddressRange(
50 GetCurrentMemory(kernel).ReadBlock(handles_addr, handles.data(), sizeof(Handle) * num_handles); 54 handles_addr, static_cast<u64>(sizeof(Handle) * num_handles)),
51 55 ResultInvalidPointer);
52 // Convert handle list to object table. 56
53 std::array<KSynchronizationObject*, Svc::ArgumentHandleCountMax> objs; 57 // Get the handles.
54 R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles.data(), 58 GetCurrentMemory(kernel).ReadBlock(handles_addr, handles.data(),
55 num_handles), 59 sizeof(Handle) * num_handles);
56 ResultInvalidHandle); 60
61 // Convert the handles to objects.
62 R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(
63 objs.data(), handles.data(), num_handles),
64 ResultInvalidHandle);
65 }
57 66
58 // Ensure handles are closed when we're done. 67 // Ensure handles are closed when we're done.
59 SCOPE_EXIT({ 68 SCOPE_EXIT({
diff --git a/src/core/hle/kernel/svc/svc_synchronization.cpp b/src/core/hle/kernel/svc/svc_synchronization.cpp
index 53df5bcd8..f02d03f30 100644
--- a/src/core/hle/kernel/svc/svc_synchronization.cpp
+++ b/src/core/hle/kernel/svc/svc_synchronization.cpp
@@ -47,21 +47,35 @@ Result ResetSignal(Core::System& system, Handle handle) {
47 R_THROW(ResultInvalidHandle); 47 R_THROW(ResultInvalidHandle);
48} 48}
49 49
50static Result WaitSynchronization(Core::System& system, int32_t* out_index, const Handle* handles, 50/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
51 int32_t num_handles, int64_t timeout_ns) { 51Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_handles,
52 int32_t num_handles, int64_t timeout_ns) {
53 LOG_TRACE(Kernel_SVC, "called user_handles={:#x}, num_handles={}, timeout_ns={}", user_handles,
54 num_handles, timeout_ns);
55
52 // Ensure number of handles is valid. 56 // Ensure number of handles is valid.
53 R_UNLESS(0 <= num_handles && num_handles <= Svc::ArgumentHandleCountMax, ResultOutOfRange); 57 R_UNLESS(0 <= num_handles && num_handles <= Svc::ArgumentHandleCountMax, ResultOutOfRange);
54 58
55 // Get the synchronization context. 59 // Get the synchronization context.
56 auto& kernel = system.Kernel(); 60 auto& kernel = system.Kernel();
57 auto& handle_table = GetCurrentProcess(kernel).GetHandleTable(); 61 auto& handle_table = GetCurrentProcess(kernel).GetHandleTable();
58 std::array<KSynchronizationObject*, Svc::ArgumentHandleCountMax> objs; 62 auto objs = GetCurrentThread(kernel).GetSynchronizationObjectBuffer();
63 auto handles = GetCurrentThread(kernel).GetHandleBuffer();
59 64
60 // Copy user handles. 65 // Copy user handles.
61 if (num_handles > 0) { 66 if (num_handles > 0) {
67 // Ensure we can try to get the handles.
68 R_UNLESS(GetCurrentMemory(kernel).IsValidVirtualAddressRange(
69 user_handles, static_cast<u64>(sizeof(Handle) * num_handles)),
70 ResultInvalidPointer);
71
72 // Get the handles.
73 GetCurrentMemory(kernel).ReadBlock(user_handles, handles.data(),
74 sizeof(Handle) * num_handles);
75
62 // Convert the handles to objects. 76 // Convert the handles to objects.
63 R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(objs.data(), handles, 77 R_UNLESS(handle_table.GetMultipleObjects<KSynchronizationObject>(
64 num_handles), 78 objs.data(), handles.data(), num_handles),
65 ResultInvalidHandle); 79 ResultInvalidHandle);
66 } 80 }
67 81
@@ -80,23 +94,6 @@ static Result WaitSynchronization(Core::System& system, int32_t* out_index, cons
80 R_RETURN(res); 94 R_RETURN(res);
81} 95}
82 96
83/// Wait for the given handles to synchronize, timeout after the specified nanoseconds
84Result WaitSynchronization(Core::System& system, int32_t* out_index, u64 user_handles,
85 int32_t num_handles, int64_t timeout_ns) {
86 LOG_TRACE(Kernel_SVC, "called user_handles={:#x}, num_handles={}, timeout_ns={}", user_handles,
87 num_handles, timeout_ns);
88
89 // Ensure number of handles is valid.
90 R_UNLESS(0 <= num_handles && num_handles <= Svc::ArgumentHandleCountMax, ResultOutOfRange);
91 std::array<Handle, Svc::ArgumentHandleCountMax> handles;
92 if (num_handles > 0) {
93 GetCurrentMemory(system.Kernel())
94 .ReadBlock(user_handles, handles.data(), num_handles * sizeof(Handle));
95 }
96
97 R_RETURN(WaitSynchronization(system, out_index, handles.data(), num_handles, timeout_ns));
98}
99
100/// Resumes a thread waiting on WaitSynchronization 97/// Resumes a thread waiting on WaitSynchronization
101Result CancelSynchronization(Core::System& system, Handle handle) { 98Result CancelSynchronization(Core::System& system, Handle handle) {
102 LOG_TRACE(Kernel_SVC, "called handle=0x{:X}", handle); 99 LOG_TRACE(Kernel_SVC, "called handle=0x{:X}", handle);
diff --git a/src/core/hle/service/nfc/common/device.cpp b/src/core/hle/service/nfc/common/device.cpp
index 5bf289818..2d633b03f 100644
--- a/src/core/hle/service/nfc/common/device.cpp
+++ b/src/core/hle/service/nfc/common/device.cpp
@@ -66,10 +66,6 @@ NfcDevice::~NfcDevice() {
66}; 66};
67 67
68void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) { 68void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
69 if (!is_initalized) {
70 return;
71 }
72
73 if (type == Core::HID::ControllerTriggerType::Connected) { 69 if (type == Core::HID::ControllerTriggerType::Connected) {
74 Initialize(); 70 Initialize();
75 availability_change_event->Signal(); 71 availability_change_event->Signal();
@@ -77,12 +73,12 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
77 } 73 }
78 74
79 if (type == Core::HID::ControllerTriggerType::Disconnected) { 75 if (type == Core::HID::ControllerTriggerType::Disconnected) {
80 device_state = DeviceState::Unavailable; 76 Finalize();
81 availability_change_event->Signal(); 77 availability_change_event->Signal();
82 return; 78 return;
83 } 79 }
84 80
85 if (type != Core::HID::ControllerTriggerType::Nfc) { 81 if (!is_initalized) {
86 return; 82 return;
87 } 83 }
88 84
@@ -90,6 +86,17 @@ void NfcDevice::NpadUpdate(Core::HID::ControllerTriggerType type) {
90 return; 86 return;
91 } 87 }
92 88
89 // Ensure nfc mode is always active
90 if (npad_device->GetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex) ==
91 Common::Input::PollingMode::Active) {
92 npad_device->SetPollingMode(Core::HID::EmulatedDeviceIndex::RightIndex,
93 Common::Input::PollingMode::NFC);
94 }
95
96 if (type != Core::HID::ControllerTriggerType::Nfc) {
97 return;
98 }
99
93 const auto nfc_status = npad_device->GetNfc(); 100 const auto nfc_status = npad_device->GetNfc();
94 switch (nfc_status.state) { 101 switch (nfc_status.state) {
95 case Common::Input::NfcState::NewAmiibo: 102 case Common::Input::NfcState::NewAmiibo:
@@ -207,11 +214,14 @@ void NfcDevice::Initialize() {
207} 214}
208 215
209void NfcDevice::Finalize() { 216void NfcDevice::Finalize() {
210 if (device_state == DeviceState::TagMounted) { 217 if (npad_device->IsConnected()) {
211 Unmount(); 218 if (device_state == DeviceState::TagMounted) {
212 } 219 Unmount();
213 if (device_state == DeviceState::SearchingForTag || device_state == DeviceState::TagRemoved) { 220 }
214 StopDetection(); 221 if (device_state == DeviceState::SearchingForTag ||
222 device_state == DeviceState::TagRemoved) {
223 StopDetection();
224 }
215 } 225 }
216 226
217 if (device_state != DeviceState::Unavailable) { 227 if (device_state != DeviceState::Unavailable) {
diff --git a/src/core/memory.cpp b/src/core/memory.cpp
index 514ba0d66..257406f09 100644
--- a/src/core/memory.cpp
+++ b/src/core/memory.cpp
@@ -3,6 +3,7 @@
3 3
4#include <algorithm> 4#include <algorithm>
5#include <cstring> 5#include <cstring>
6#include <span>
6 7
7#include "common/assert.h" 8#include "common/assert.h"
8#include "common/atomic_ops.h" 9#include "common/atomic_ops.h"
@@ -13,6 +14,7 @@
13#include "common/swap.h" 14#include "common/swap.h"
14#include "core/core.h" 15#include "core/core.h"
15#include "core/device_memory.h" 16#include "core/device_memory.h"
17#include "core/gpu_dirty_memory_manager.h"
16#include "core/hardware_properties.h" 18#include "core/hardware_properties.h"
17#include "core/hle/kernel/k_page_table.h" 19#include "core/hle/kernel/k_page_table.h"
18#include "core/hle/kernel/k_process.h" 20#include "core/hle/kernel/k_process.h"
@@ -678,7 +680,7 @@ struct Memory::Impl {
678 LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8, 680 LOG_ERROR(HW_Memory, "Unmapped Write{} @ 0x{:016X} = 0x{:016X}", sizeof(T) * 8,
679 GetInteger(vaddr), static_cast<u64>(data)); 681 GetInteger(vaddr), static_cast<u64>(data));
680 }, 682 },
681 [&]() { system.GPU().InvalidateRegion(GetInteger(vaddr), sizeof(T)); }); 683 [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(T)); });
682 if (ptr) { 684 if (ptr) {
683 std::memcpy(ptr, &data, sizeof(T)); 685 std::memcpy(ptr, &data, sizeof(T));
684 } 686 }
@@ -692,7 +694,7 @@ struct Memory::Impl {
692 LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ 0x{:016X} = 0x{:016X}", 694 LOG_ERROR(HW_Memory, "Unmapped WriteExclusive{} @ 0x{:016X} = 0x{:016X}",
693 sizeof(T) * 8, GetInteger(vaddr), static_cast<u64>(data)); 695 sizeof(T) * 8, GetInteger(vaddr), static_cast<u64>(data));
694 }, 696 },
695 [&]() { system.GPU().InvalidateRegion(GetInteger(vaddr), sizeof(T)); }); 697 [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(T)); });
696 if (ptr) { 698 if (ptr) {
697 const auto volatile_pointer = reinterpret_cast<volatile T*>(ptr); 699 const auto volatile_pointer = reinterpret_cast<volatile T*>(ptr);
698 return Common::AtomicCompareAndSwap(volatile_pointer, data, expected); 700 return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
@@ -707,7 +709,7 @@ struct Memory::Impl {
707 LOG_ERROR(HW_Memory, "Unmapped WriteExclusive128 @ 0x{:016X} = 0x{:016X}{:016X}", 709 LOG_ERROR(HW_Memory, "Unmapped WriteExclusive128 @ 0x{:016X} = 0x{:016X}{:016X}",
708 GetInteger(vaddr), static_cast<u64>(data[1]), static_cast<u64>(data[0])); 710 GetInteger(vaddr), static_cast<u64>(data[1]), static_cast<u64>(data[0]));
709 }, 711 },
710 [&]() { system.GPU().InvalidateRegion(GetInteger(vaddr), sizeof(u128)); }); 712 [&]() { HandleRasterizerWrite(GetInteger(vaddr), sizeof(u128)); });
711 if (ptr) { 713 if (ptr) {
712 const auto volatile_pointer = reinterpret_cast<volatile u64*>(ptr); 714 const auto volatile_pointer = reinterpret_cast<volatile u64*>(ptr);
713 return Common::AtomicCompareAndSwap(volatile_pointer, data, expected); 715 return Common::AtomicCompareAndSwap(volatile_pointer, data, expected);
@@ -717,7 +719,7 @@ struct Memory::Impl {
717 719
718 void HandleRasterizerDownload(VAddr address, size_t size) { 720 void HandleRasterizerDownload(VAddr address, size_t size) {
719 const size_t core = system.GetCurrentHostThreadID(); 721 const size_t core = system.GetCurrentHostThreadID();
720 auto& current_area = rasterizer_areas[core]; 722 auto& current_area = rasterizer_read_areas[core];
721 const VAddr end_address = address + size; 723 const VAddr end_address = address + size;
722 if (current_area.start_address <= address && end_address <= current_area.end_address) 724 if (current_area.start_address <= address && end_address <= current_area.end_address)
723 [[likely]] { 725 [[likely]] {
@@ -726,9 +728,31 @@ struct Memory::Impl {
726 current_area = system.GPU().OnCPURead(address, size); 728 current_area = system.GPU().OnCPURead(address, size);
727 } 729 }
728 730
729 Common::PageTable* current_page_table = nullptr; 731 void HandleRasterizerWrite(VAddr address, size_t size) {
730 std::array<VideoCore::RasterizerDownloadArea, Core::Hardware::NUM_CPU_CORES> rasterizer_areas{}; 732 const size_t core = system.GetCurrentHostThreadID();
733 auto& current_area = rasterizer_write_areas[core];
734 VAddr subaddress = address >> YUZU_PAGEBITS;
735 bool do_collection = current_area.last_address == subaddress;
736 if (!do_collection) [[unlikely]] {
737 do_collection = system.GPU().OnCPUWrite(address, size);
738 if (!do_collection) {
739 return;
740 }
741 current_area.last_address = subaddress;
742 }
743 gpu_dirty_managers[core].Collect(address, size);
744 }
745
746 struct GPUDirtyState {
747 VAddr last_address;
748 };
749
731 Core::System& system; 750 Core::System& system;
751 Common::PageTable* current_page_table = nullptr;
752 std::array<VideoCore::RasterizerDownloadArea, Core::Hardware::NUM_CPU_CORES>
753 rasterizer_read_areas{};
754 std::array<GPUDirtyState, Core::Hardware::NUM_CPU_CORES> rasterizer_write_areas{};
755 std::span<Core::GPUDirtyMemoryManager> gpu_dirty_managers;
732}; 756};
733 757
734Memory::Memory(Core::System& system_) : system{system_} { 758Memory::Memory(Core::System& system_) : system{system_} {
@@ -876,6 +900,10 @@ void Memory::ZeroBlock(Common::ProcessAddress dest_addr, const std::size_t size)
876 impl->ZeroBlock(*system.ApplicationProcess(), dest_addr, size); 900 impl->ZeroBlock(*system.ApplicationProcess(), dest_addr, size);
877} 901}
878 902
903void Memory::SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers) {
904 impl->gpu_dirty_managers = managers;
905}
906
879Result Memory::InvalidateDataCache(Common::ProcessAddress dest_addr, const std::size_t size) { 907Result Memory::InvalidateDataCache(Common::ProcessAddress dest_addr, const std::size_t size) {
880 return impl->InvalidateDataCache(*system.ApplicationProcess(), dest_addr, size); 908 return impl->InvalidateDataCache(*system.ApplicationProcess(), dest_addr, size);
881} 909}
diff --git a/src/core/memory.h b/src/core/memory.h
index 72a0be813..ea01824f8 100644
--- a/src/core/memory.h
+++ b/src/core/memory.h
@@ -5,6 +5,7 @@
5 5
6#include <cstddef> 6#include <cstddef>
7#include <memory> 7#include <memory>
8#include <span>
8#include <string> 9#include <string>
9#include "common/typed_address.h" 10#include "common/typed_address.h"
10#include "core/hle/result.h" 11#include "core/hle/result.h"
@@ -15,7 +16,8 @@ struct PageTable;
15 16
16namespace Core { 17namespace Core {
17class System; 18class System;
18} 19class GPUDirtyMemoryManager;
20} // namespace Core
19 21
20namespace Kernel { 22namespace Kernel {
21class PhysicalMemory; 23class PhysicalMemory;
@@ -458,6 +460,8 @@ public:
458 */ 460 */
459 void MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug); 461 void MarkRegionDebug(Common::ProcessAddress vaddr, u64 size, bool debug);
460 462
463 void SetGPUDirtyManagers(std::span<Core::GPUDirtyMemoryManager> managers);
464
461private: 465private:
462 Core::System& system; 466 Core::System& system;
463 467
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h
index 58a45ab67..b5ed3380f 100644
--- a/src/video_core/buffer_cache/buffer_cache.h
+++ b/src/video_core/buffer_cache/buffer_cache.h
@@ -115,7 +115,34 @@ void BufferCache<P>::WriteMemory(VAddr cpu_addr, u64 size) {
115 115
116template <class P> 116template <class P>
117void BufferCache<P>::CachedWriteMemory(VAddr cpu_addr, u64 size) { 117void BufferCache<P>::CachedWriteMemory(VAddr cpu_addr, u64 size) {
118 memory_tracker.CachedCpuWrite(cpu_addr, size); 118 const bool is_dirty = IsRegionRegistered(cpu_addr, size);
119 if (!is_dirty) {
120 return;
121 }
122 VAddr aligned_start = Common::AlignDown(cpu_addr, YUZU_PAGESIZE);
123 VAddr aligned_end = Common::AlignUp(cpu_addr + size, YUZU_PAGESIZE);
124 if (!IsRegionGpuModified(aligned_start, aligned_end - aligned_start)) {
125 WriteMemory(cpu_addr, size);
126 return;
127 }
128
129 tmp_buffer.resize_destructive(size);
130 cpu_memory.ReadBlockUnsafe(cpu_addr, tmp_buffer.data(), size);
131
132 InlineMemoryImplementation(cpu_addr, size, tmp_buffer);
133}
134
135template <class P>
136bool BufferCache<P>::OnCPUWrite(VAddr cpu_addr, u64 size) {
137 const bool is_dirty = IsRegionRegistered(cpu_addr, size);
138 if (!is_dirty) {
139 return false;
140 }
141 if (memory_tracker.IsRegionGpuModified(cpu_addr, size)) {
142 return true;
143 }
144 WriteMemory(cpu_addr, size);
145 return false;
119} 146}
120 147
121template <class P> 148template <class P>
@@ -1553,6 +1580,14 @@ bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size,
1553 return false; 1580 return false;
1554 } 1581 }
1555 1582
1583 InlineMemoryImplementation(dest_address, copy_size, inlined_buffer);
1584
1585 return true;
1586}
1587
1588template <class P>
1589void BufferCache<P>::InlineMemoryImplementation(VAddr dest_address, size_t copy_size,
1590 std::span<const u8> inlined_buffer) {
1556 const IntervalType subtract_interval{dest_address, dest_address + copy_size}; 1591 const IntervalType subtract_interval{dest_address, dest_address + copy_size};
1557 ClearDownload(subtract_interval); 1592 ClearDownload(subtract_interval);
1558 common_ranges.subtract(subtract_interval); 1593 common_ranges.subtract(subtract_interval);
@@ -1574,8 +1609,6 @@ bool BufferCache<P>::InlineMemory(VAddr dest_address, size_t copy_size,
1574 } else { 1609 } else {
1575 buffer.ImmediateUpload(buffer.Offset(dest_address), inlined_buffer.first(copy_size)); 1610 buffer.ImmediateUpload(buffer.Offset(dest_address), inlined_buffer.first(copy_size));
1576 } 1611 }
1577
1578 return true;
1579} 1612}
1580 1613
1581template <class P> 1614template <class P>
diff --git a/src/video_core/buffer_cache/buffer_cache_base.h b/src/video_core/buffer_cache/buffer_cache_base.h
index fe6068cfe..460fc7551 100644
--- a/src/video_core/buffer_cache/buffer_cache_base.h
+++ b/src/video_core/buffer_cache/buffer_cache_base.h
@@ -245,6 +245,8 @@ public:
245 245
246 void CachedWriteMemory(VAddr cpu_addr, u64 size); 246 void CachedWriteMemory(VAddr cpu_addr, u64 size);
247 247
248 bool OnCPUWrite(VAddr cpu_addr, u64 size);
249
248 void DownloadMemory(VAddr cpu_addr, u64 size); 250 void DownloadMemory(VAddr cpu_addr, u64 size);
249 251
250 std::optional<VideoCore::RasterizerDownloadArea> GetFlushArea(VAddr cpu_addr, u64 size); 252 std::optional<VideoCore::RasterizerDownloadArea> GetFlushArea(VAddr cpu_addr, u64 size);
@@ -543,6 +545,9 @@ private:
543 545
544 void ClearDownload(IntervalType subtract_interval); 546 void ClearDownload(IntervalType subtract_interval);
545 547
548 void InlineMemoryImplementation(VAddr dest_address, size_t copy_size,
549 std::span<const u8> inlined_buffer);
550
546 VideoCore::RasterizerInterface& rasterizer; 551 VideoCore::RasterizerInterface& rasterizer;
547 Core::Memory::Memory& cpu_memory; 552 Core::Memory::Memory& cpu_memory;
548 553
diff --git a/src/video_core/compatible_formats.cpp b/src/video_core/compatible_formats.cpp
index ab4f4d407..87d69ebc5 100644
--- a/src/video_core/compatible_formats.cpp
+++ b/src/video_core/compatible_formats.cpp
@@ -272,6 +272,9 @@ constexpr Table MakeNonNativeBgrCopyTable() {
272 272
273bool IsViewCompatible(PixelFormat format_a, PixelFormat format_b, bool broken_views, 273bool IsViewCompatible(PixelFormat format_a, PixelFormat format_b, bool broken_views,
274 bool native_bgr) { 274 bool native_bgr) {
275 if (format_a == format_b) {
276 return true;
277 }
275 if (broken_views) { 278 if (broken_views) {
276 // If format views are broken, only accept formats that are identical. 279 // If format views are broken, only accept formats that are identical.
277 return format_a == format_b; 280 return format_a == format_b;
@@ -282,6 +285,9 @@ bool IsViewCompatible(PixelFormat format_a, PixelFormat format_b, bool broken_vi
282} 285}
283 286
284bool IsCopyCompatible(PixelFormat format_a, PixelFormat format_b, bool native_bgr) { 287bool IsCopyCompatible(PixelFormat format_a, PixelFormat format_b, bool native_bgr) {
288 if (format_a == format_b) {
289 return true;
290 }
285 static constexpr Table BGR_TABLE = MakeNativeBgrCopyTable(); 291 static constexpr Table BGR_TABLE = MakeNativeBgrCopyTable();
286 static constexpr Table NO_BGR_TABLE = MakeNonNativeBgrCopyTable(); 292 static constexpr Table NO_BGR_TABLE = MakeNonNativeBgrCopyTable();
287 return IsSupported(native_bgr ? BGR_TABLE : NO_BGR_TABLE, format_a, format_b); 293 return IsSupported(native_bgr ? BGR_TABLE : NO_BGR_TABLE, format_a, format_b);
diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h
index 35d699bbf..ab20ff30f 100644
--- a/src/video_core/fence_manager.h
+++ b/src/video_core/fence_manager.h
@@ -69,7 +69,6 @@ public:
69 } 69 }
70 70
71 void SignalFence(std::function<void()>&& func) { 71 void SignalFence(std::function<void()>&& func) {
72 rasterizer.InvalidateGPUCache();
73 bool delay_fence = Settings::IsGPULevelHigh(); 72 bool delay_fence = Settings::IsGPULevelHigh();
74 if constexpr (!can_async_check) { 73 if constexpr (!can_async_check) {
75 TryReleasePendingFences<false>(); 74 TryReleasePendingFences<false>();
@@ -96,6 +95,7 @@ public:
96 guard.unlock(); 95 guard.unlock();
97 cv.notify_all(); 96 cv.notify_all();
98 } 97 }
98 rasterizer.InvalidateGPUCache();
99 } 99 }
100 100
101 void SignalSyncPoint(u32 value) { 101 void SignalSyncPoint(u32 value) {
diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp
index db385076d..c192e33b2 100644
--- a/src/video_core/gpu.cpp
+++ b/src/video_core/gpu.cpp
@@ -95,7 +95,9 @@ struct GPU::Impl {
95 95
96 /// Synchronizes CPU writes with Host GPU memory. 96 /// Synchronizes CPU writes with Host GPU memory.
97 void InvalidateGPUCache() { 97 void InvalidateGPUCache() {
98 rasterizer->InvalidateGPUCache(); 98 std::function<void(VAddr, size_t)> callback_writes(
99 [this](VAddr address, size_t size) { rasterizer->OnCacheInvalidation(address, size); });
100 system.GatherGPUDirtyMemory(callback_writes);
99 } 101 }
100 102
101 /// Signal the ending of command list. 103 /// Signal the ending of command list.
@@ -299,6 +301,10 @@ struct GPU::Impl {
299 gpu_thread.InvalidateRegion(addr, size); 301 gpu_thread.InvalidateRegion(addr, size);
300 } 302 }
301 303
304 bool OnCPUWrite(VAddr addr, u64 size) {
305 return rasterizer->OnCPUWrite(addr, size);
306 }
307
302 /// Notify rasterizer that any caches of the specified region should be flushed and invalidated 308 /// Notify rasterizer that any caches of the specified region should be flushed and invalidated
303 void FlushAndInvalidateRegion(VAddr addr, u64 size) { 309 void FlushAndInvalidateRegion(VAddr addr, u64 size) {
304 gpu_thread.FlushAndInvalidateRegion(addr, size); 310 gpu_thread.FlushAndInvalidateRegion(addr, size);
@@ -561,6 +567,10 @@ void GPU::InvalidateRegion(VAddr addr, u64 size) {
561 impl->InvalidateRegion(addr, size); 567 impl->InvalidateRegion(addr, size);
562} 568}
563 569
570bool GPU::OnCPUWrite(VAddr addr, u64 size) {
571 return impl->OnCPUWrite(addr, size);
572}
573
564void GPU::FlushAndInvalidateRegion(VAddr addr, u64 size) { 574void GPU::FlushAndInvalidateRegion(VAddr addr, u64 size) {
565 impl->FlushAndInvalidateRegion(addr, size); 575 impl->FlushAndInvalidateRegion(addr, size);
566} 576}
diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h
index e49c40cf2..ba2838b89 100644
--- a/src/video_core/gpu.h
+++ b/src/video_core/gpu.h
@@ -250,6 +250,10 @@ public:
250 /// Notify rasterizer that any caches of the specified region should be invalidated 250 /// Notify rasterizer that any caches of the specified region should be invalidated
251 void InvalidateRegion(VAddr addr, u64 size); 251 void InvalidateRegion(VAddr addr, u64 size);
252 252
253 /// Notify rasterizer that CPU is trying to write this area. It returns true if the area is
254 /// sensible, false otherwise
255 bool OnCPUWrite(VAddr addr, u64 size);
256
253 /// Notify rasterizer that any caches of the specified region should be flushed and invalidated 257 /// Notify rasterizer that any caches of the specified region should be flushed and invalidated
254 void FlushAndInvalidateRegion(VAddr addr, u64 size); 258 void FlushAndInvalidateRegion(VAddr addr, u64 size);
255 259
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp
index 889144f38..2f0f9f593 100644
--- a/src/video_core/gpu_thread.cpp
+++ b/src/video_core/gpu_thread.cpp
@@ -47,7 +47,7 @@ static void RunThread(std::stop_token stop_token, Core::System& system,
47 } else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) { 47 } else if (const auto* flush = std::get_if<FlushRegionCommand>(&next.data)) {
48 rasterizer->FlushRegion(flush->addr, flush->size); 48 rasterizer->FlushRegion(flush->addr, flush->size);
49 } else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) { 49 } else if (const auto* invalidate = std::get_if<InvalidateRegionCommand>(&next.data)) {
50 rasterizer->OnCPUWrite(invalidate->addr, invalidate->size); 50 rasterizer->OnCacheInvalidation(invalidate->addr, invalidate->size);
51 } else { 51 } else {
52 ASSERT(false); 52 ASSERT(false);
53 } 53 }
@@ -102,12 +102,12 @@ void ThreadManager::TickGPU() {
102} 102}
103 103
104void ThreadManager::InvalidateRegion(VAddr addr, u64 size) { 104void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
105 rasterizer->OnCPUWrite(addr, size); 105 rasterizer->OnCacheInvalidation(addr, size);
106} 106}
107 107
108void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) { 108void ThreadManager::FlushAndInvalidateRegion(VAddr addr, u64 size) {
109 // Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important 109 // Skip flush on asynch mode, as FlushAndInvalidateRegion is not used for anything too important
110 rasterizer->OnCPUWrite(addr, size); 110 rasterizer->OnCacheInvalidation(addr, size);
111} 111}
112 112
113u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) { 113u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) {
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h
index 7566a8c4e..cb8029a4f 100644
--- a/src/video_core/rasterizer_interface.h
+++ b/src/video_core/rasterizer_interface.h
@@ -109,7 +109,9 @@ public:
109 } 109 }
110 110
111 /// Notify rasterizer that any caches of the specified region are desync with guest 111 /// Notify rasterizer that any caches of the specified region are desync with guest
112 virtual void OnCPUWrite(VAddr addr, u64 size) = 0; 112 virtual void OnCacheInvalidation(VAddr addr, u64 size) = 0;
113
114 virtual bool OnCPUWrite(VAddr addr, u64 size) = 0;
113 115
114 /// Sync memory between guest and host. 116 /// Sync memory between guest and host.
115 virtual void InvalidateGPUCache() = 0; 117 virtual void InvalidateGPUCache() = 0;
diff --git a/src/video_core/renderer_null/null_rasterizer.cpp b/src/video_core/renderer_null/null_rasterizer.cpp
index bf2ce4c49..92ecf6682 100644
--- a/src/video_core/renderer_null/null_rasterizer.cpp
+++ b/src/video_core/renderer_null/null_rasterizer.cpp
@@ -47,7 +47,10 @@ bool RasterizerNull::MustFlushRegion(VAddr addr, u64 size, VideoCommon::CacheTyp
47 return false; 47 return false;
48} 48}
49void RasterizerNull::InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {} 49void RasterizerNull::InvalidateRegion(VAddr addr, u64 size, VideoCommon::CacheType) {}
50void RasterizerNull::OnCPUWrite(VAddr addr, u64 size) {} 50bool RasterizerNull::OnCPUWrite(VAddr addr, u64 size) {
51 return false;
52}
53void RasterizerNull::OnCacheInvalidation(VAddr addr, u64 size) {}
51VideoCore::RasterizerDownloadArea RasterizerNull::GetFlushArea(VAddr addr, u64 size) { 54VideoCore::RasterizerDownloadArea RasterizerNull::GetFlushArea(VAddr addr, u64 size) {
52 VideoCore::RasterizerDownloadArea new_area{ 55 VideoCore::RasterizerDownloadArea new_area{
53 .start_address = Common::AlignDown(addr, Core::Memory::YUZU_PAGESIZE), 56 .start_address = Common::AlignDown(addr, Core::Memory::YUZU_PAGESIZE),
diff --git a/src/video_core/renderer_null/null_rasterizer.h b/src/video_core/renderer_null/null_rasterizer.h
index a8d35d2c1..93b9a6971 100644
--- a/src/video_core/renderer_null/null_rasterizer.h
+++ b/src/video_core/renderer_null/null_rasterizer.h
@@ -53,7 +53,8 @@ public:
53 VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 53 VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
54 void InvalidateRegion(VAddr addr, u64 size, 54 void InvalidateRegion(VAddr addr, u64 size,
55 VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 55 VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
56 void OnCPUWrite(VAddr addr, u64 size) override; 56 void OnCacheInvalidation(VAddr addr, u64 size) override;
57 bool OnCPUWrite(VAddr addr, u64 size) override;
57 VideoCore::RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) override; 58 VideoCore::RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) override;
58 void InvalidateGPUCache() override; 59 void InvalidateGPUCache() override;
59 void UnmapMemory(VAddr addr, u64 size) override; 60 void UnmapMemory(VAddr addr, u64 size) override;
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp
index edf527f2d..aadd6967c 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.cpp
+++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp
@@ -485,12 +485,33 @@ void RasterizerOpenGL::InvalidateRegion(VAddr addr, u64 size, VideoCommon::Cache
485 } 485 }
486} 486}
487 487
488void RasterizerOpenGL::OnCPUWrite(VAddr addr, u64 size) { 488bool RasterizerOpenGL::OnCPUWrite(VAddr addr, u64 size) {
489 MICROPROFILE_SCOPE(OpenGL_CacheManagement);
490 if (addr == 0 || size == 0) {
491 return false;
492 }
493
494 {
495 std::scoped_lock lock{buffer_cache.mutex};
496 if (buffer_cache.OnCPUWrite(addr, size)) {
497 return true;
498 }
499 }
500
501 {
502 std::scoped_lock lock{texture_cache.mutex};
503 texture_cache.WriteMemory(addr, size);
504 }
505
506 shader_cache.InvalidateRegion(addr, size);
507 return false;
508}
509
510void RasterizerOpenGL::OnCacheInvalidation(VAddr addr, u64 size) {
489 MICROPROFILE_SCOPE(OpenGL_CacheManagement); 511 MICROPROFILE_SCOPE(OpenGL_CacheManagement);
490 if (addr == 0 || size == 0) { 512 if (addr == 0 || size == 0) {
491 return; 513 return;
492 } 514 }
493 shader_cache.OnCPUWrite(addr, size);
494 { 515 {
495 std::scoped_lock lock{texture_cache.mutex}; 516 std::scoped_lock lock{texture_cache.mutex};
496 texture_cache.WriteMemory(addr, size); 517 texture_cache.WriteMemory(addr, size);
@@ -499,15 +520,11 @@ void RasterizerOpenGL::OnCPUWrite(VAddr addr, u64 size) {
499 std::scoped_lock lock{buffer_cache.mutex}; 520 std::scoped_lock lock{buffer_cache.mutex};
500 buffer_cache.CachedWriteMemory(addr, size); 521 buffer_cache.CachedWriteMemory(addr, size);
501 } 522 }
523 shader_cache.InvalidateRegion(addr, size);
502} 524}
503 525
504void RasterizerOpenGL::InvalidateGPUCache() { 526void RasterizerOpenGL::InvalidateGPUCache() {
505 MICROPROFILE_SCOPE(OpenGL_CacheManagement); 527 gpu.InvalidateGPUCache();
506 shader_cache.SyncGuestHost();
507 {
508 std::scoped_lock lock{buffer_cache.mutex};
509 buffer_cache.FlushCachedWrites();
510 }
511} 528}
512 529
513void RasterizerOpenGL::UnmapMemory(VAddr addr, u64 size) { 530void RasterizerOpenGL::UnmapMemory(VAddr addr, u64 size) {
@@ -519,7 +536,7 @@ void RasterizerOpenGL::UnmapMemory(VAddr addr, u64 size) {
519 std::scoped_lock lock{buffer_cache.mutex}; 536 std::scoped_lock lock{buffer_cache.mutex};
520 buffer_cache.WriteMemory(addr, size); 537 buffer_cache.WriteMemory(addr, size);
521 } 538 }
522 shader_cache.OnCPUWrite(addr, size); 539 shader_cache.OnCacheInvalidation(addr, size);
523} 540}
524 541
525void RasterizerOpenGL::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) { 542void RasterizerOpenGL::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h
index a73ad15c1..8eda2ddba 100644
--- a/src/video_core/renderer_opengl/gl_rasterizer.h
+++ b/src/video_core/renderer_opengl/gl_rasterizer.h
@@ -98,7 +98,8 @@ public:
98 VideoCore::RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) override; 98 VideoCore::RasterizerDownloadArea GetFlushArea(VAddr addr, u64 size) override;
99 void InvalidateRegion(VAddr addr, u64 size, 99 void InvalidateRegion(VAddr addr, u64 size,
100 VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 100 VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
101 void OnCPUWrite(VAddr addr, u64 size) override; 101 void OnCacheInvalidation(VAddr addr, u64 size) override;
102 bool OnCPUWrite(VAddr addr, u64 size) override;
102 void InvalidateGPUCache() override; 103 void InvalidateGPUCache() override;
103 void UnmapMemory(VAddr addr, u64 size) override; 104 void UnmapMemory(VAddr addr, u64 size) override;
104 void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; 105 void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
index f7c0d939a..456bb040e 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp
@@ -566,11 +566,32 @@ void RasterizerVulkan::InnerInvalidation(std::span<const std::pair<VAddr, std::s
566 } 566 }
567} 567}
568 568
569void RasterizerVulkan::OnCPUWrite(VAddr addr, u64 size) { 569bool RasterizerVulkan::OnCPUWrite(VAddr addr, u64 size) {
570 if (addr == 0 || size == 0) {
571 return false;
572 }
573
574 {
575 std::scoped_lock lock{buffer_cache.mutex};
576 if (buffer_cache.OnCPUWrite(addr, size)) {
577 return true;
578 }
579 }
580
581 {
582 std::scoped_lock lock{texture_cache.mutex};
583 texture_cache.WriteMemory(addr, size);
584 }
585
586 pipeline_cache.InvalidateRegion(addr, size);
587 return false;
588}
589
590void RasterizerVulkan::OnCacheInvalidation(VAddr addr, u64 size) {
570 if (addr == 0 || size == 0) { 591 if (addr == 0 || size == 0) {
571 return; 592 return;
572 } 593 }
573 pipeline_cache.OnCPUWrite(addr, size); 594
574 { 595 {
575 std::scoped_lock lock{texture_cache.mutex}; 596 std::scoped_lock lock{texture_cache.mutex};
576 texture_cache.WriteMemory(addr, size); 597 texture_cache.WriteMemory(addr, size);
@@ -579,14 +600,11 @@ void RasterizerVulkan::OnCPUWrite(VAddr addr, u64 size) {
579 std::scoped_lock lock{buffer_cache.mutex}; 600 std::scoped_lock lock{buffer_cache.mutex};
580 buffer_cache.CachedWriteMemory(addr, size); 601 buffer_cache.CachedWriteMemory(addr, size);
581 } 602 }
603 pipeline_cache.InvalidateRegion(addr, size);
582} 604}
583 605
584void RasterizerVulkan::InvalidateGPUCache() { 606void RasterizerVulkan::InvalidateGPUCache() {
585 pipeline_cache.SyncGuestHost(); 607 gpu.InvalidateGPUCache();
586 {
587 std::scoped_lock lock{buffer_cache.mutex};
588 buffer_cache.FlushCachedWrites();
589 }
590} 608}
591 609
592void RasterizerVulkan::UnmapMemory(VAddr addr, u64 size) { 610void RasterizerVulkan::UnmapMemory(VAddr addr, u64 size) {
@@ -598,7 +616,7 @@ void RasterizerVulkan::UnmapMemory(VAddr addr, u64 size) {
598 std::scoped_lock lock{buffer_cache.mutex}; 616 std::scoped_lock lock{buffer_cache.mutex};
599 buffer_cache.WriteMemory(addr, size); 617 buffer_cache.WriteMemory(addr, size);
600 } 618 }
601 pipeline_cache.OnCPUWrite(addr, size); 619 pipeline_cache.OnCacheInvalidation(addr, size);
602} 620}
603 621
604void RasterizerVulkan::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) { 622void RasterizerVulkan::ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) {
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h
index b39710b3c..73257d964 100644
--- a/src/video_core/renderer_vulkan/vk_rasterizer.h
+++ b/src/video_core/renderer_vulkan/vk_rasterizer.h
@@ -96,7 +96,8 @@ public:
96 void InvalidateRegion(VAddr addr, u64 size, 96 void InvalidateRegion(VAddr addr, u64 size,
97 VideoCommon::CacheType which = VideoCommon::CacheType::All) override; 97 VideoCommon::CacheType which = VideoCommon::CacheType::All) override;
98 void InnerInvalidation(std::span<const std::pair<VAddr, std::size_t>> sequences) override; 98 void InnerInvalidation(std::span<const std::pair<VAddr, std::size_t>> sequences) override;
99 void OnCPUWrite(VAddr addr, u64 size) override; 99 void OnCacheInvalidation(VAddr addr, u64 size) override;
100 bool OnCPUWrite(VAddr addr, u64 size) override;
100 void InvalidateGPUCache() override; 101 void InvalidateGPUCache() override;
101 void UnmapMemory(VAddr addr, u64 size) override; 102 void UnmapMemory(VAddr addr, u64 size) override;
102 void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override; 103 void ModifyGPUMemory(size_t as_id, GPUVAddr addr, u64 size) override;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
index 8385b5509..3aac3cfab 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp
@@ -36,8 +36,10 @@ using VideoCommon::ImageFlagBits;
36using VideoCommon::ImageInfo; 36using VideoCommon::ImageInfo;
37using VideoCommon::ImageType; 37using VideoCommon::ImageType;
38using VideoCommon::SubresourceRange; 38using VideoCommon::SubresourceRange;
39using VideoCore::Surface::BytesPerBlock;
39using VideoCore::Surface::IsPixelFormatASTC; 40using VideoCore::Surface::IsPixelFormatASTC;
40using VideoCore::Surface::IsPixelFormatInteger; 41using VideoCore::Surface::IsPixelFormatInteger;
42using VideoCore::Surface::SurfaceType;
41 43
42namespace { 44namespace {
43constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) { 45constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
@@ -130,7 +132,7 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
130[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) { 132[[nodiscard]] VkImageCreateInfo MakeImageCreateInfo(const Device& device, const ImageInfo& info) {
131 const PixelFormat format = StorageFormat(info.format); 133 const PixelFormat format = StorageFormat(info.format);
132 const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format); 134 const auto format_info = MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, false, format);
133 VkImageCreateFlags flags = VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT; 135 VkImageCreateFlags flags{};
134 if (info.type == ImageType::e2D && info.resources.layers >= 6 && 136 if (info.type == ImageType::e2D && info.resources.layers >= 6 &&
135 info.size.width == info.size.height && !device.HasBrokenCubeImageCompability()) { 137 info.size.width == info.size.height && !device.HasBrokenCubeImageCompability()) {
136 flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; 138 flags |= VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT;
@@ -163,11 +165,24 @@ constexpr VkBorderColor ConvertBorderColor(const std::array<float, 4>& color) {
163} 165}
164 166
165[[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator, 167[[nodiscard]] vk::Image MakeImage(const Device& device, const MemoryAllocator& allocator,
166 const ImageInfo& info) { 168 const ImageInfo& info, std::span<const VkFormat> view_formats) {
167 if (info.type == ImageType::Buffer) { 169 if (info.type == ImageType::Buffer) {
168 return vk::Image{}; 170 return vk::Image{};
169 } 171 }
170 return allocator.CreateImage(MakeImageCreateInfo(device, info)); 172 VkImageCreateInfo image_ci = MakeImageCreateInfo(device, info);
173 const VkImageFormatListCreateInfo image_format_list = {
174 .sType = VK_STRUCTURE_TYPE_IMAGE_FORMAT_LIST_CREATE_INFO,
175 .pNext = nullptr,
176 .viewFormatCount = static_cast<u32>(view_formats.size()),
177 .pViewFormats = view_formats.data(),
178 };
179 if (view_formats.size() > 1) {
180 image_ci.flags |= VK_IMAGE_CREATE_MUTABLE_FORMAT_BIT;
181 if (device.IsKhrImageFormatListSupported()) {
182 image_ci.pNext = &image_format_list;
183 }
184 }
185 return allocator.CreateImage(image_ci);
171} 186}
172 187
173[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) { 188[[nodiscard]] VkImageAspectFlags ImageAspectMask(PixelFormat format) {
@@ -806,6 +821,23 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
806 astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool, 821 astc_decoder_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
807 compute_pass_descriptor_queue, memory_allocator); 822 compute_pass_descriptor_queue, memory_allocator);
808 } 823 }
824 if (!device.IsKhrImageFormatListSupported()) {
825 return;
826 }
827 for (size_t index_a = 0; index_a < VideoCore::Surface::MaxPixelFormat; index_a++) {
828 const auto image_format = static_cast<PixelFormat>(index_a);
829 if (IsPixelFormatASTC(image_format) && !device.IsOptimalAstcSupported()) {
830 view_formats[index_a].push_back(VK_FORMAT_A8B8G8R8_UNORM_PACK32);
831 }
832 for (size_t index_b = 0; index_b < VideoCore::Surface::MaxPixelFormat; index_b++) {
833 const auto view_format = static_cast<PixelFormat>(index_b);
834 if (VideoCore::Surface::IsViewCompatible(image_format, view_format, false, true)) {
835 const auto view_info =
836 MaxwellToVK::SurfaceFormat(device, FormatType::Optimal, true, view_format);
837 view_formats[index_a].push_back(view_info.format);
838 }
839 }
840 }
809} 841}
810 842
811void TextureCacheRuntime::Finish() { 843void TextureCacheRuntime::Finish() {
@@ -1265,8 +1297,8 @@ void TextureCacheRuntime::TickFrame() {}
1265Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_, 1297Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu_addr_,
1266 VAddr cpu_addr_) 1298 VAddr cpu_addr_)
1267 : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler}, 1299 : VideoCommon::ImageBase(info_, gpu_addr_, cpu_addr_), scheduler{&runtime_.scheduler},
1268 runtime{&runtime_}, 1300 runtime{&runtime_}, original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info,
1269 original_image(MakeImage(runtime_.device, runtime_.memory_allocator, info)), 1301 runtime->ViewFormats(info.format))),
1270 aspect_mask(ImageAspectMask(info.format)) { 1302 aspect_mask(ImageAspectMask(info.format)) {
1271 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) { 1303 if (IsPixelFormatASTC(info.format) && !runtime->device.IsOptimalAstcSupported()) {
1272 if (Settings::values.async_astc.GetValue()) { 1304 if (Settings::values.async_astc.GetValue()) {
@@ -1471,7 +1503,8 @@ bool Image::ScaleUp(bool ignore) {
1471 auto scaled_info = info; 1503 auto scaled_info = info;
1472 scaled_info.size.width = scaled_width; 1504 scaled_info.size.width = scaled_width;
1473 scaled_info.size.height = scaled_height; 1505 scaled_info.size.height = scaled_height;
1474 scaled_image = MakeImage(runtime->device, runtime->memory_allocator, scaled_info); 1506 scaled_image = MakeImage(runtime->device, runtime->memory_allocator, scaled_info,
1507 runtime->ViewFormats(info.format));
1475 ignore = false; 1508 ignore = false;
1476 } 1509 }
1477 current_image = *scaled_image; 1510 current_image = *scaled_image;
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.h b/src/video_core/renderer_vulkan/vk_texture_cache.h
index 220943116..6621210ea 100644
--- a/src/video_core/renderer_vulkan/vk_texture_cache.h
+++ b/src/video_core/renderer_vulkan/vk_texture_cache.h
@@ -103,6 +103,10 @@ public:
103 103
104 [[nodiscard]] VkBuffer GetTemporaryBuffer(size_t needed_size); 104 [[nodiscard]] VkBuffer GetTemporaryBuffer(size_t needed_size);
105 105
106 std::span<const VkFormat> ViewFormats(PixelFormat format) {
107 return view_formats[static_cast<std::size_t>(format)];
108 }
109
106 void BarrierFeedbackLoop(); 110 void BarrierFeedbackLoop();
107 111
108 const Device& device; 112 const Device& device;
@@ -113,6 +117,7 @@ public:
113 RenderPassCache& render_pass_cache; 117 RenderPassCache& render_pass_cache;
114 std::optional<ASTCDecoderPass> astc_decoder_pass; 118 std::optional<ASTCDecoderPass> astc_decoder_pass;
115 const Settings::ResolutionScalingInfo& resolution; 119 const Settings::ResolutionScalingInfo& resolution;
120 std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
116 121
117 static constexpr size_t indexing_slots = 8 * sizeof(size_t); 122 static constexpr size_t indexing_slots = 8 * sizeof(size_t);
118 std::array<vk::Buffer, indexing_slots> buffers{}; 123 std::array<vk::Buffer, indexing_slots> buffers{};
diff --git a/src/video_core/shader_cache.cpp b/src/video_core/shader_cache.cpp
index 4db948b6d..01701201d 100644
--- a/src/video_core/shader_cache.cpp
+++ b/src/video_core/shader_cache.cpp
@@ -24,7 +24,7 @@ void ShaderCache::InvalidateRegion(VAddr addr, size_t size) {
24 RemovePendingShaders(); 24 RemovePendingShaders();
25} 25}
26 26
27void ShaderCache::OnCPUWrite(VAddr addr, size_t size) { 27void ShaderCache::OnCacheInvalidation(VAddr addr, size_t size) {
28 std::scoped_lock lock{invalidation_mutex}; 28 std::scoped_lock lock{invalidation_mutex};
29 InvalidatePagesInRegion(addr, size); 29 InvalidatePagesInRegion(addr, size);
30} 30}
diff --git a/src/video_core/shader_cache.h b/src/video_core/shader_cache.h
index f3cc4c70b..de8e08002 100644
--- a/src/video_core/shader_cache.h
+++ b/src/video_core/shader_cache.h
@@ -62,7 +62,7 @@ public:
62 /// @brief Unmarks a memory region as cached and marks it for removal 62 /// @brief Unmarks a memory region as cached and marks it for removal
63 /// @param addr Start address of the CPU write operation 63 /// @param addr Start address of the CPU write operation
64 /// @param size Number of bytes of the CPU write operation 64 /// @param size Number of bytes of the CPU write operation
65 void OnCPUWrite(VAddr addr, size_t size); 65 void OnCacheInvalidation(VAddr addr, size_t size);
66 66
67 /// @brief Flushes delayed removal operations 67 /// @brief Flushes delayed removal operations
68 void SyncGuestHost(); 68 void SyncGuestHost();
diff --git a/src/video_core/texture_cache/texture_cache.h b/src/video_core/texture_cache/texture_cache.h
index 8190f3ba1..3a859139c 100644
--- a/src/video_core/texture_cache/texture_cache.h
+++ b/src/video_core/texture_cache/texture_cache.h
@@ -598,6 +598,10 @@ void TextureCache<P>::UnmapGPUMemory(size_t as_id, GPUVAddr gpu_addr, size_t siz
598 [&](ImageId id, Image&) { deleted_images.push_back(id); }); 598 [&](ImageId id, Image&) { deleted_images.push_back(id); });
599 for (const ImageId id : deleted_images) { 599 for (const ImageId id : deleted_images) {
600 Image& image = slot_images[id]; 600 Image& image = slot_images[id];
601 if (True(image.flags & ImageFlagBits::CpuModified)) {
602 continue;
603 }
604 image.flags |= ImageFlagBits::CpuModified;
601 if (True(image.flags & ImageFlagBits::Remapped)) { 605 if (True(image.flags & ImageFlagBits::Remapped)) {
602 continue; 606 continue;
603 } 607 }
@@ -865,11 +869,15 @@ void TextureCache<P>::PopAsyncFlushes() {
865template <class P> 869template <class P>
866ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand, bool is_upload) { 870ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand, bool is_upload) {
867 const ImageInfo dst_info(operand); 871 const ImageInfo dst_info(operand);
868 const ImageId image_id = FindDMAImage(dst_info, operand.address); 872 const ImageId dst_id = FindDMAImage(dst_info, operand.address);
869 if (!image_id) { 873 if (!dst_id) {
874 return NULL_IMAGE_ID;
875 }
876 auto& image = slot_images[dst_id];
877 if (False(image.flags & ImageFlagBits::GpuModified)) {
878 // No need to waste time on an image that's synced with guest
870 return NULL_IMAGE_ID; 879 return NULL_IMAGE_ID;
871 } 880 }
872 auto& image = slot_images[image_id];
873 if (image.info.type == ImageType::e3D) { 881 if (image.info.type == ImageType::e3D) {
874 // Don't accelerate 3D images. 882 // Don't accelerate 3D images.
875 return NULL_IMAGE_ID; 883 return NULL_IMAGE_ID;
@@ -883,7 +891,7 @@ ImageId TextureCache<P>::DmaImageId(const Tegra::DMA::ImageOperand& operand, boo
883 if (!base) { 891 if (!base) {
884 return NULL_IMAGE_ID; 892 return NULL_IMAGE_ID;
885 } 893 }
886 return image_id; 894 return dst_id;
887} 895}
888 896
889template <class P> 897template <class P>
diff --git a/src/video_core/texture_cache/types.h b/src/video_core/texture_cache/types.h
index a0e10643f..0453456b4 100644
--- a/src/video_core/texture_cache/types.h
+++ b/src/video_core/texture_cache/types.h
@@ -54,7 +54,6 @@ enum class RelaxedOptions : u32 {
54 Format = 1 << 1, 54 Format = 1 << 1,
55 Samples = 1 << 2, 55 Samples = 1 << 2,
56 ForceBrokenViews = 1 << 3, 56 ForceBrokenViews = 1 << 3,
57 FormatBpp = 1 << 4,
58}; 57};
59DECLARE_ENUM_FLAG_OPERATORS(RelaxedOptions) 58DECLARE_ENUM_FLAG_OPERATORS(RelaxedOptions)
60 59
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp
index 9a618a57a..0de6ed09d 100644
--- a/src/video_core/texture_cache/util.cpp
+++ b/src/video_core/texture_cache/util.cpp
@@ -1201,8 +1201,7 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const
1201 // Format checking is relaxed, but we still have to check for matching bytes per block. 1201 // Format checking is relaxed, but we still have to check for matching bytes per block.
1202 // This avoids creating a view for blits on UE4 titles where formats with different bytes 1202 // This avoids creating a view for blits on UE4 titles where formats with different bytes
1203 // per block are aliased. 1203 // per block are aliased.
1204 if (BytesPerBlock(existing.format) != BytesPerBlock(candidate.format) && 1204 if (BytesPerBlock(existing.format) != BytesPerBlock(candidate.format)) {
1205 False(options & RelaxedOptions::FormatBpp)) {
1206 return std::nullopt; 1205 return std::nullopt;
1207 } 1206 }
1208 } else { 1207 } else {
@@ -1233,11 +1232,7 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const
1233 } 1232 }
1234 const bool strict_size = False(options & RelaxedOptions::Size); 1233 const bool strict_size = False(options & RelaxedOptions::Size);
1235 if (!IsBlockLinearSizeCompatible(existing, candidate, base->level, 0, strict_size)) { 1234 if (!IsBlockLinearSizeCompatible(existing, candidate, base->level, 0, strict_size)) {
1236 if (False(options & RelaxedOptions::FormatBpp)) { 1235 return std::nullopt;
1237 return std::nullopt;
1238 } else if (!IsBlockLinearSizeCompatibleBPPRelaxed(existing, candidate, base->level, 0)) {
1239 return std::nullopt;
1240 }
1241 } 1236 }
1242 // TODO: compare block sizes 1237 // TODO: compare block sizes
1243 return base; 1238 return base;
diff --git a/src/video_core/vulkan_common/vulkan_device.cpp b/src/video_core/vulkan_common/vulkan_device.cpp
index 421e71e5a..e04852e01 100644
--- a/src/video_core/vulkan_common/vulkan_device.cpp
+++ b/src/video_core/vulkan_common/vulkan_device.cpp
@@ -485,7 +485,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
485 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME); 485 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_EXTENSION_NAME);
486 } 486 }
487 } 487 }
488 if (extensions.extended_dynamic_state2 && (is_radv || is_qualcomm)) { 488 if (extensions.extended_dynamic_state2 && is_radv) {
489 const u32 version = (properties.properties.driverVersion << 3) >> 3; 489 const u32 version = (properties.properties.driverVersion << 3) >> 3;
490 if (version < VK_MAKE_API_VERSION(0, 22, 3, 1)) { 490 if (version < VK_MAKE_API_VERSION(0, 22, 3, 1)) {
491 LOG_WARNING( 491 LOG_WARNING(
@@ -498,6 +498,20 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
498 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME); 498 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
499 } 499 }
500 } 500 }
501 if (extensions.extended_dynamic_state2 && is_qualcomm) {
502 const u32 version = (properties.properties.driverVersion << 3) >> 3;
503 if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) &&
504 version < VK_MAKE_API_VERSION(0, 0, 680, 0)) {
505 // Qualcomm Adreno 7xx drivers do not properly support extended_dynamic_state2.
506 LOG_WARNING(Render_Vulkan,
507 "Qualcomm Adreno 7xx drivers have broken VK_EXT_extended_dynamic_state2");
508 features.extended_dynamic_state2.extendedDynamicState2 = false;
509 features.extended_dynamic_state2.extendedDynamicState2LogicOp = false;
510 features.extended_dynamic_state2.extendedDynamicState2PatchControlPoints = false;
511 extensions.extended_dynamic_state2 = false;
512 loaded_extensions.erase(VK_EXT_EXTENDED_DYNAMIC_STATE_2_EXTENSION_NAME);
513 }
514 }
501 if (extensions.extended_dynamic_state3 && is_radv) { 515 if (extensions.extended_dynamic_state3 && is_radv) {
502 LOG_WARNING(Render_Vulkan, "RADV has broken extendedDynamicState3ColorBlendEquation"); 516 LOG_WARNING(Render_Vulkan, "RADV has broken extendedDynamicState3ColorBlendEquation");
503 features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false; 517 features.extended_dynamic_state3.extendedDynamicState3ColorBlendEnable = false;
@@ -512,8 +526,7 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
512 dynamic_state3_enables = false; 526 dynamic_state3_enables = false;
513 } 527 }
514 } 528 }
515 if (extensions.vertex_input_dynamic_state && (is_radv || is_qualcomm)) { 529 if (extensions.vertex_input_dynamic_state && is_radv) {
516 // Qualcomm S8gen2 drivers do not properly support vertex_input_dynamic_state.
517 // TODO(ameerj): Blacklist only offending driver versions 530 // TODO(ameerj): Blacklist only offending driver versions
518 // TODO(ameerj): Confirm if RDNA1 is affected 531 // TODO(ameerj): Confirm if RDNA1 is affected
519 const bool is_rdna2 = 532 const bool is_rdna2 =
@@ -526,6 +539,19 @@ Device::Device(VkInstance instance_, vk::PhysicalDevice physical_, VkSurfaceKHR
526 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME); 539 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
527 } 540 }
528 } 541 }
542 if (extensions.vertex_input_dynamic_state && is_qualcomm) {
543 const u32 version = (properties.properties.driverVersion << 3) >> 3;
544 if (version >= VK_MAKE_API_VERSION(0, 0, 676, 0) &&
545 version < VK_MAKE_API_VERSION(0, 0, 680, 0)) {
546 // Qualcomm Adreno 7xx drivers do not properly support vertex_input_dynamic_state.
547 LOG_WARNING(
548 Render_Vulkan,
549 "Qualcomm Adreno 7xx drivers have broken VK_EXT_vertex_input_dynamic_state");
550 features.vertex_input_dynamic_state.vertexInputDynamicState = false;
551 extensions.vertex_input_dynamic_state = false;
552 loaded_extensions.erase(VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME);
553 }
554 }
529 555
530 sets_per_pool = 64; 556 sets_per_pool = 64;
531 if (extensions.extended_dynamic_state3 && is_amd_driver && 557 if (extensions.extended_dynamic_state3 && is_amd_driver &&
@@ -774,6 +800,17 @@ bool Device::ShouldBoostClocks() const {
774 return validated_driver && !is_steam_deck && !is_debugging; 800 return validated_driver && !is_steam_deck && !is_debugging;
775} 801}
776 802
803bool Device::HasTimelineSemaphore() const {
804 if (GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
805 GetDriverID() == VK_DRIVER_ID_MESA_TURNIP) {
806 // Timeline semaphores do not work properly on all Qualcomm drivers.
807 // They generally work properly with Turnip drivers, but are problematic on some devices
808 // (e.g. ZTE handsets with Snapdragon 870).
809 return false;
810 }
811 return features.timeline_semaphore.timelineSemaphore;
812}
813
777bool Device::GetSuitability(bool requires_swapchain) { 814bool Device::GetSuitability(bool requires_swapchain) {
778 // Assume we will be suitable. 815 // Assume we will be suitable.
779 bool suitable = true; 816 bool suitable = true;
diff --git a/src/video_core/vulkan_common/vulkan_device.h b/src/video_core/vulkan_common/vulkan_device.h
index 1f17265d5..be3ed45ff 100644
--- a/src/video_core/vulkan_common/vulkan_device.h
+++ b/src/video_core/vulkan_common/vulkan_device.h
@@ -77,6 +77,7 @@ VK_DEFINE_HANDLE(VmaAllocator)
77 EXTENSION(KHR, SPIRV_1_4, spirv_1_4) \ 77 EXTENSION(KHR, SPIRV_1_4, spirv_1_4) \
78 EXTENSION(KHR, SWAPCHAIN, swapchain) \ 78 EXTENSION(KHR, SWAPCHAIN, swapchain) \
79 EXTENSION(KHR, SWAPCHAIN_MUTABLE_FORMAT, swapchain_mutable_format) \ 79 EXTENSION(KHR, SWAPCHAIN_MUTABLE_FORMAT, swapchain_mutable_format) \
80 EXTENSION(KHR, IMAGE_FORMAT_LIST, image_format_list) \
80 EXTENSION(NV, DEVICE_DIAGNOSTICS_CONFIG, device_diagnostics_config) \ 81 EXTENSION(NV, DEVICE_DIAGNOSTICS_CONFIG, device_diagnostics_config) \
81 EXTENSION(NV, GEOMETRY_SHADER_PASSTHROUGH, geometry_shader_passthrough) \ 82 EXTENSION(NV, GEOMETRY_SHADER_PASSTHROUGH, geometry_shader_passthrough) \
82 EXTENSION(NV, VIEWPORT_ARRAY2, viewport_array2) \ 83 EXTENSION(NV, VIEWPORT_ARRAY2, viewport_array2) \
@@ -408,6 +409,11 @@ public:
408 return extensions.workgroup_memory_explicit_layout; 409 return extensions.workgroup_memory_explicit_layout;
409 } 410 }
410 411
412 /// Returns true if the device supports VK_KHR_image_format_list.
413 bool IsKhrImageFormatListSupported() const {
414 return extensions.image_format_list || instance_version >= VK_API_VERSION_1_2;
415 }
416
411 /// Returns true if the device supports VK_EXT_primitive_topology_list_restart. 417 /// Returns true if the device supports VK_EXT_primitive_topology_list_restart.
412 bool IsTopologyListPrimitiveRestartSupported() const { 418 bool IsTopologyListPrimitiveRestartSupported() const {
413 return features.primitive_topology_list_restart.primitiveTopologyListRestart; 419 return features.primitive_topology_list_restart.primitiveTopologyListRestart;
@@ -522,13 +528,7 @@ public:
522 return extensions.shader_atomic_int64; 528 return extensions.shader_atomic_int64;
523 } 529 }
524 530
525 bool HasTimelineSemaphore() const { 531 bool HasTimelineSemaphore() const;
526 if (GetDriverID() == VK_DRIVER_ID_QUALCOMM_PROPRIETARY) {
527 // Timeline semaphores do not work properly on all Qualcomm drivers.
528 return false;
529 }
530 return features.timeline_semaphore.timelineSemaphore;
531 }
532 532
533 /// Returns the minimum supported version of SPIR-V. 533 /// Returns the minimum supported version of SPIR-V.
534 u32 SupportedSpirvVersion() const { 534 u32 SupportedSpirvVersion() const {
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp
index e8418b302..20532416c 100644
--- a/src/yuzu/main.cpp
+++ b/src/yuzu/main.cpp
@@ -101,6 +101,7 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual
101#include "common/settings.h" 101#include "common/settings.h"
102#include "common/telemetry.h" 102#include "common/telemetry.h"
103#include "core/core.h" 103#include "core/core.h"
104#include "core/core_timing.h"
104#include "core/crypto/key_manager.h" 105#include "core/crypto/key_manager.h"
105#include "core/file_sys/card_image.h" 106#include "core/file_sys/card_image.h"
106#include "core/file_sys/common_funcs.h" 107#include "core/file_sys/common_funcs.h"
@@ -389,6 +390,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
389 std::chrono::duration_cast<std::chrono::duration<f64, std::milli>>( 390 std::chrono::duration_cast<std::chrono::duration<f64, std::milli>>(
390 Common::Windows::SetCurrentTimerResolutionToMaximum()) 391 Common::Windows::SetCurrentTimerResolutionToMaximum())
391 .count()); 392 .count());
393 system->CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution());
392#endif 394#endif
393 UpdateWindowTitle(); 395 UpdateWindowTitle();
394 396
@@ -452,7 +454,7 @@ GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan
452 // the user through their desktop environment. 454 // the user through their desktop environment.
453 //: TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the 455 //: TRANSLATORS: This string is shown to the user to explain why yuzu needs to prevent the
454 //: computer from sleeping 456 //: computer from sleeping
455 QByteArray wakelock_reason = tr("Running a game").toLatin1(); 457 QByteArray wakelock_reason = tr("Running a game").toUtf8();
456 SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, wakelock_reason.data()); 458 SDL_SetHint(SDL_HINT_SCREENSAVER_INHIBIT_ACTIVITY_NAME, wakelock_reason.data());
457 459
458 // SDL disables the screen saver by default, and setting the hint 460 // SDL disables the screen saver by default, and setting the hint
diff --git a/src/yuzu_cmd/yuzu.cpp b/src/yuzu_cmd/yuzu.cpp
index 7b6d49c63..d0433ffc6 100644
--- a/src/yuzu_cmd/yuzu.cpp
+++ b/src/yuzu_cmd/yuzu.cpp
@@ -21,6 +21,7 @@
21#include "common/string_util.h" 21#include "common/string_util.h"
22#include "common/telemetry.h" 22#include "common/telemetry.h"
23#include "core/core.h" 23#include "core/core.h"
24#include "core/core_timing.h"
24#include "core/cpu_manager.h" 25#include "core/cpu_manager.h"
25#include "core/crypto/key_manager.h" 26#include "core/crypto/key_manager.h"
26#include "core/file_sys/registered_cache.h" 27#include "core/file_sys/registered_cache.h"
@@ -316,8 +317,6 @@ int main(int argc, char** argv) {
316 317
317#ifdef _WIN32 318#ifdef _WIN32
318 LocalFree(argv_w); 319 LocalFree(argv_w);
319
320 Common::Windows::SetCurrentTimerResolutionToMaximum();
321#endif 320#endif
322 321
323 MicroProfileOnThreadCreate("EmuThread"); 322 MicroProfileOnThreadCreate("EmuThread");
@@ -351,6 +350,11 @@ int main(int argc, char** argv) {
351 break; 350 break;
352 } 351 }
353 352
353#ifdef _WIN32
354 Common::Windows::SetCurrentTimerResolutionToMaximum();
355 system.CoreTiming().SetTimerResolutionNs(Common::Windows::GetCurrentTimerResolution());
356#endif
357
354 system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>()); 358 system.SetContentProvider(std::make_unique<FileSys::ContentProviderUnion>());
355 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>()); 359 system.SetFilesystem(std::make_shared<FileSys::RealVfsFilesystem>());
356 system.GetFileSystemController().CreateFactories(*system.GetFilesystem()); 360 system.GetFileSystemController().CreateFactories(*system.GetFilesystem());