Compare commits
2 Commits
xbzk-saf-r
...
android6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
133fa00d73 | ||
|
|
ed39ec4738 |
@@ -58,7 +58,7 @@ android {
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "dev.eden.eden_emulator"
|
||||
minSdk = 24
|
||||
minSdk = 23
|
||||
targetSdk = 36
|
||||
versionName = getGitVersion()
|
||||
versionCode = autoVersion
|
||||
|
||||
@@ -44,17 +44,6 @@ import androidx.core.content.edit
|
||||
import androidx.core.view.doOnNextLayout
|
||||
|
||||
class GamesFragment : Fragment() {
|
||||
private lateinit var safRecursiveTimestampWriter: org.yuzu.yuzu_emu.utils.SAFWriter
|
||||
private val REQUEST_CODE_OPEN_DOCUMENT_TREE = 1001
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: android.content.Intent?) {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == REQUEST_CODE_OPEN_DOCUMENT_TREE && resultCode == android.app.Activity.RESULT_OK) {
|
||||
val uri = data?.data ?: return
|
||||
safRecursiveTimestampWriter.handlePermissionResult(uri)
|
||||
}
|
||||
}
|
||||
|
||||
private var _binding: FragmentGamesBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
@@ -123,25 +112,9 @@ class GamesFragment : Fragment() {
|
||||
|
||||
applyGridGamesBinding()
|
||||
|
||||
safRecursiveTimestampWriter = org.yuzu.yuzu_emu.utils.SAFWriter(requireContext())
|
||||
|
||||
binding.swipeRefresh.apply {
|
||||
(binding.swipeRefresh as? SwipeRefreshLayout)?.setOnRefreshListener {
|
||||
safRecursiveTimestampWriter.refreshGamesFolder(
|
||||
folders = gamesViewModel.folders.value,
|
||||
requestPermission = { uri ->
|
||||
val intent = android.content.Intent(android.content.Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
intent.putExtra(android.provider.DocumentsContract.EXTRA_INITIAL_URI, uri)
|
||||
intent.addFlags(
|
||||
android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
|
||||
android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION or
|
||||
android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
|
||||
)
|
||||
startActivityForResult(intent, REQUEST_CODE_OPEN_DOCUMENT_TREE)
|
||||
},
|
||||
reloadGames = { gamesViewModel.reloadGames(false) }
|
||||
)
|
||||
gamesViewModel.reloadGames(false)
|
||||
}
|
||||
(binding.swipeRefresh as? SwipeRefreshLayout)?.setProgressBackgroundColorSchemeColor(
|
||||
com.google.android.material.color.MaterialColors.getColor(
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
package org.yuzu.yuzu_emu.utils
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
|
||||
class SAFWriter(private val context: Context) {
|
||||
fun refreshGamesFolder(
|
||||
folders: List<Any>,
|
||||
requestPermission: (Uri) -> Unit,
|
||||
reloadGames: () -> Unit
|
||||
) {
|
||||
if (folders.isNotEmpty()) {
|
||||
val gamesDirUriString = try {
|
||||
val uriField = folders[0]::class.java.getDeclaredField("uriString")
|
||||
uriField.isAccessible = true
|
||||
uriField.get(folders[0]) as? String
|
||||
} catch (e: Exception) {
|
||||
Log.e("YuzuDebug", "[SAFWriter] [SAF] Error accessing uriString: $e")
|
||||
null
|
||||
}
|
||||
if (gamesDirUriString != null) {
|
||||
val gamesRootDocFile = DocumentFile.fromTreeUri(context, Uri.parse(gamesDirUriString))
|
||||
Log.i("YuzuDebug", "[SAFWriter] [SAF] Refresh triggered. gamesDirUri: $gamesDirUriString, gamesRootDocFile: $gamesRootDocFile")
|
||||
if (gamesRootDocFile != null && gamesRootDocFile.isDirectory) {
|
||||
val dirtreeDocFile = gamesRootDocFile.findFile("dirtree")
|
||||
if (dirtreeDocFile != null && dirtreeDocFile.isDirectory) {
|
||||
createRecursiveTimestampFilesInSAF(dirtreeDocFile, requestPermission)
|
||||
} else {
|
||||
Log.e("YuzuDebug", "[SAFWriter] [SAF] 'dirtree' subfolder not found or not a directory.")
|
||||
}
|
||||
} else {
|
||||
Log.e("YuzuDebug", "[SAFWriter] [SAF] Invalid games folder DocumentFile.")
|
||||
}
|
||||
} else {
|
||||
Log.e("YuzuDebug", "[SAFWriter] [SAF] Could not get gamesDirUriString from folders.")
|
||||
}
|
||||
} else {
|
||||
Log.e("YuzuDebug", "[SAFWriter] [SAF] No games folder found in gamesViewModel.folders.")
|
||||
}
|
||||
reloadGames()
|
||||
}
|
||||
private var pendingSAFAction: (() -> Unit)? = null
|
||||
private val REQUEST_CODE_OPEN_DOCUMENT_TREE = 1001
|
||||
|
||||
// Recursively create a timestamp file in each subfolder using SAF
|
||||
fun createRecursiveTimestampFilesInSAF(root: DocumentFile, requestPermission: (Uri) -> Unit, onPermissionGranted: (() -> Unit)? = null) {
|
||||
Log.i("YuzuDebug", "[SAFWriter] [SAF] Processing folder: ${root.uri}, name: ${root.name}, canWrite: ${root.canWrite()}, isDirectory: ${root.isDirectory}, mimeType: ${root.type}")
|
||||
if (!root.isDirectory) return
|
||||
if (!root.canWrite()) {
|
||||
Log.w("YuzuDebug", "[SAFWriter] [SAF] Cannot write to folder: ${root.uri}, requesting permission...")
|
||||
// Save the action to retry after permission
|
||||
pendingSAFAction = { createRecursiveTimestampFilesInSAF(root, requestPermission, onPermissionGranted) }
|
||||
requestPermission(root.uri)
|
||||
return
|
||||
}
|
||||
val timestamp = java.text.SimpleDateFormat("yyyyMMddHHmmss", java.util.Locale.US).format(java.util.Date())
|
||||
try {
|
||||
val exists = root.findFile(timestamp)
|
||||
if (exists == null) {
|
||||
val file = root.createFile("text/plain", timestamp)
|
||||
Log.i("YuzuDebug", "[SAFWriter] [SAF] Attempted to create file: $timestamp, result: ${file?.uri}")
|
||||
} else {
|
||||
Log.i("YuzuDebug", "[SAFWriter] [SAF] File already exists: ${exists.uri}")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e("YuzuDebug", "[SAFWriter] [SAF] Error creating file in ${root.uri}: $e")
|
||||
}
|
||||
root.listFiles()?.filter { it.isDirectory }?.forEach { createRecursiveTimestampFilesInSAF(it, requestPermission, onPermissionGranted) }
|
||||
}
|
||||
|
||||
fun handlePermissionResult(uri: Uri) {
|
||||
context.contentResolver.takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
Log.i("YuzuDebug", "[SAFWriter] [SAF] Permission granted for uri: $uri")
|
||||
// Retry the pending action if any
|
||||
pendingSAFAction?.invoke()
|
||||
pendingSAFAction = null
|
||||
}
|
||||
}
|
||||
@@ -62,29 +62,41 @@ struct DrawParams {
|
||||
VkViewport GetViewportState(const Device& device, const Maxwell& regs, size_t index, float scale) {
|
||||
const auto& src = regs.viewport_transform[index];
|
||||
const auto conv = [scale](float value) {
|
||||
float const new_value = value * scale;
|
||||
return scale < 1.0f
|
||||
? std::round(std::abs(new_value)) * (std::signbit(new_value) ? -1.f : 1.f)
|
||||
: new_value;
|
||||
float new_value = value * scale;
|
||||
if (scale < 1.0f) {
|
||||
const bool sign = std::signbit(value);
|
||||
new_value = std::round(std::abs(new_value));
|
||||
new_value = sign ? -new_value : new_value;
|
||||
}
|
||||
return new_value;
|
||||
};
|
||||
float const w = src.scale_x;
|
||||
float h = src.scale_y;
|
||||
if (regs.window_origin.mode == Maxwell::WindowOrigin::Mode::LowerLeft) // Flip by surface clip height
|
||||
h = -h;
|
||||
if (!device.IsNvViewportSwizzleSupported() && src.swizzle.y == Maxwell::ViewportSwizzle::NegativeY) // Flip by viewport height
|
||||
h = -h;
|
||||
// In theory, a raster flip is equivalent to a texture flip for a whole square viewport
|
||||
// TODO: one day implement this properly and raster flip the triangles, not the whole viewport... guh
|
||||
if(regs.viewport_transform[1].scale_y == 0 && regs.window_origin.flip_y != 0)
|
||||
h = -h;
|
||||
float const x = src.translate_x - w;
|
||||
float const y = src.translate_y - h;
|
||||
float const reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f;
|
||||
const float x = conv(src.translate_x - src.scale_x);
|
||||
const float width = conv(src.scale_x * 2.0f);
|
||||
float y = conv(src.translate_y - src.scale_y);
|
||||
float height = conv(src.scale_y * 2.0f);
|
||||
|
||||
const bool lower_left = regs.window_origin.mode != Maxwell::WindowOrigin::Mode::UpperLeft;
|
||||
const bool y_negate = !device.IsNvViewportSwizzleSupported() &&
|
||||
src.swizzle.y == Maxwell::ViewportSwizzle::NegativeY;
|
||||
|
||||
if (lower_left) {
|
||||
// Flip by surface clip height
|
||||
y += conv(static_cast<f32>(regs.surface_clip.height));
|
||||
height = -height;
|
||||
}
|
||||
|
||||
if (y_negate) {
|
||||
// Flip by viewport height
|
||||
y += height;
|
||||
height = -height;
|
||||
}
|
||||
|
||||
const float reduce_z = regs.depth_mode == Maxwell::DepthMode::MinusOneToOne ? 1.0f : 0.0f;
|
||||
VkViewport viewport{
|
||||
.x = conv(x),
|
||||
.y = conv(y),
|
||||
.width = w != 0.0f ? conv(w * 2.f) : 1.0f,
|
||||
.height = h != 0.0f ? conv(h * 2.f) : 1.0f,
|
||||
.x = x,
|
||||
.y = y,
|
||||
.width = width != 0.0f ? width : 1.0f,
|
||||
.height = height != 0.0f ? height : 1.0f,
|
||||
.minDepth = src.translate_z - src.scale_z * reduce_z,
|
||||
.maxDepth = src.translate_z + src.scale_z,
|
||||
};
|
||||
@@ -1014,10 +1026,10 @@ void RasterizerVulkan::UpdateViewportsState(Tegra::Engines::Maxwell3D::Regs& reg
|
||||
return;
|
||||
}
|
||||
if (!regs.viewport_scale_offset_enabled) {
|
||||
float x = float(regs.surface_clip.x);
|
||||
float y = float(regs.surface_clip.y);
|
||||
float width = (std::max)(1.0f, float(regs.surface_clip.width));
|
||||
float height = (std::max)(1.0f, float(regs.surface_clip.height));
|
||||
float x = static_cast<float>(regs.surface_clip.x);
|
||||
float y = static_cast<float>(regs.surface_clip.y);
|
||||
float width = (std::max)(1.0f, static_cast<float>(regs.surface_clip.width));
|
||||
float height = (std::max)(1.0f, static_cast<float>(regs.surface_clip.height));
|
||||
if (regs.window_origin.mode != Maxwell::WindowOrigin::Mode::UpperLeft) {
|
||||
y += height;
|
||||
height = -height;
|
||||
|
||||
Reference in New Issue
Block a user