mirror of
https://github.com/ARMSX2/ARMSX3.git
synced 2026-08-24 16:58:52 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39ca5cdab6 | ||
|
|
b82432c793 | ||
|
|
7f54855b7d | ||
|
|
0819f1ef15 | ||
|
|
8ee20d91d5 | ||
|
|
4797ad8a9a |
@@ -29,8 +29,8 @@ android {
|
||||
applicationId = "com.armsx3"
|
||||
minSdk = 26
|
||||
targetSdk = 37
|
||||
versionCode = 11
|
||||
versionName = "0.7"
|
||||
versionCode = 13
|
||||
versionName = "0.7.2"
|
||||
|
||||
// ARMSX2's UI reads these. STORAGE_ALL_FILES gates the all-files storage path in
|
||||
// onboarding; IN_APP_UPDATER gates the in-app GitHub-release updater.
|
||||
|
||||
@@ -63,6 +63,8 @@ object ConfigStore {
|
||||
private const val KEY_SPU_DECODER_RESTORE = "config.migrated.spuDecoderRestoreLlvm"
|
||||
private const val KEY_XFLOAT_BACK_TO_APPROX = "config.migrated.xfloatBackToApprox"
|
||||
private const val KEY_PRECISE_SPU_OFF = "config.migrated.preciseSpuVerifyOff"
|
||||
// Oboe became the Android default in 0.7.2; move anyone still on the old Cubeb default.
|
||||
private const val KEY_AUDIO_OBOE = "config.migrated.audioOboeDefault"
|
||||
private const val KEY_ATOMIC_DMA_OFF = "config.migrated.atomicDmaStoresOff"
|
||||
// Bumped: the first pass recorded only Vblank Rate, which did not hold on its own.
|
||||
private const val KEY_VBLANK_60 = "config.migrated.frameCap60"
|
||||
@@ -316,6 +318,16 @@ object ConfigStore {
|
||||
}
|
||||
|
||||
|
||||
// Oboe is the Android default now. Only move people sitting on the previous default
|
||||
// (Cubeb, index 2) -- anyone who deliberately picked Null or another backend keeps it.
|
||||
if (!MainActivityRuntime.prefs.getBoolean(KEY_AUDIO_OBOE, false)) {
|
||||
if (raw != null && parsed.ps3.audioRenderer == 2) {
|
||||
parsed = parsed.copy(ps3 = parsed.ps3.copy(audioRenderer = 4))
|
||||
dirty = true
|
||||
}
|
||||
MainActivityRuntime.prefs.edit { putBoolean(KEY_AUDIO_OBOE, true) }
|
||||
}
|
||||
|
||||
// The ARM64 block checksum is fixed, so the full-compare workaround can go.
|
||||
if (!MainActivityRuntime.prefs.getBoolean(KEY_PRECISE_SPU_OFF, false)) {
|
||||
if (raw != null && parsed.ps3.preciseSpuVerification) {
|
||||
|
||||
@@ -198,6 +198,12 @@ data class Ps3Settings(
|
||||
* preciseSpuVerification). Accurate is a rarely-exercised path and costs
|
||||
* speed, so there is no reason to sit on it.
|
||||
*/
|
||||
/**
|
||||
* Which face button confirms in PS3 system dialogs. 0 = circle, 1 = cross, matching
|
||||
* enter_button_assign. Japanese-region games and hardware confirm with circle; the rest of
|
||||
* the world uses cross, which is why RPCS3 exposes it rather than deriving it from region.
|
||||
*/
|
||||
val enterButtonAssign: Int = 1,
|
||||
val spuXFloat: Int = 1,
|
||||
val accurateSpuRsv: Boolean = true,
|
||||
/**
|
||||
@@ -249,7 +255,8 @@ data class Ps3Settings(
|
||||
val debugConsoleMode: Boolean = false,
|
||||
val resolution: Int = 2,
|
||||
val anisoFilter: Int = 0,
|
||||
val audioRenderer: Int = 2,
|
||||
/** Index into Rpcs3Settings.AUDIO_RENDERERS. 4 = Oboe, the Android default (see node_audio). */
|
||||
val audioRenderer: Int = 4,
|
||||
/**
|
||||
* Output aspect override in permille (1778 = 16:9, 1333 = 4:3), 0 = follow the game.
|
||||
*
|
||||
@@ -1064,6 +1071,7 @@ data class Settings(
|
||||
put("PS3/Net", "Internet enabled", "enum", ps3.netEnabled.toString())
|
||||
put("PS3/Net", "PSN status", "enum", ps3.psnStatus.toString())
|
||||
put("PS3/Net", "UPNP Enabled", "bool", ps3.upnpEnabled.toString())
|
||||
put("PS3/System", "Enter button assignment", "enum", ps3.enterButtonAssign.toString())
|
||||
put("PS3/Core", "SPU XFloat Accuracy", "enum", ps3.spuXFloat.toString())
|
||||
put("PS3/Core", "Accurate SPU Reservations", "bool", ps3.accurateSpuRsv.toString())
|
||||
put("PS3/Core", "Accurate Cache Line Stores", "bool", ps3.accurateCacheLine.toString())
|
||||
@@ -2032,6 +2040,7 @@ data class Settings(
|
||||
put("ps3NetEnabled", ps3.netEnabled)
|
||||
put("ps3PsnStatus", ps3.psnStatus)
|
||||
put("ps3UpnpEnabled", ps3.upnpEnabled)
|
||||
put("ps3EnterButtonAssign", ps3.enterButtonAssign)
|
||||
put("ps3SpuXFloat", ps3.spuXFloat)
|
||||
put("ps3AccurateSpuRsv", ps3.accurateSpuRsv)
|
||||
put("ps3AccurateCacheLine", ps3.accurateCacheLine)
|
||||
@@ -2370,6 +2379,7 @@ data class Settings(
|
||||
netEnabled = json.optBoolean("ps3NetEnabled", def.ps3.netEnabled),
|
||||
psnStatus = json.optBoolean("ps3PsnStatus", def.ps3.psnStatus),
|
||||
upnpEnabled = json.optBoolean("ps3UpnpEnabled", def.ps3.upnpEnabled),
|
||||
enterButtonAssign = json.optInt("ps3EnterButtonAssign", def.ps3.enterButtonAssign),
|
||||
spuXFloat = json.optInt("ps3SpuXFloat", def.ps3.spuXFloat),
|
||||
accurateSpuRsv = json.optBoolean("ps3AccurateSpuRsv", def.ps3.accurateSpuRsv),
|
||||
accurateCacheLine = json.optBoolean("ps3AccurateCacheLine", def.ps3.accurateCacheLine),
|
||||
@@ -2688,6 +2698,7 @@ data class Settings(
|
||||
if (current.ps3.netEnabled != base.ps3.netEnabled) j.put("ps3NetEnabled", current.ps3.netEnabled)
|
||||
if (current.ps3.psnStatus != base.ps3.psnStatus) j.put("ps3PsnStatus", current.ps3.psnStatus)
|
||||
if (current.ps3.upnpEnabled != base.ps3.upnpEnabled) j.put("ps3UpnpEnabled", current.ps3.upnpEnabled)
|
||||
if (current.ps3.enterButtonAssign != base.ps3.enterButtonAssign) j.put("ps3EnterButtonAssign", current.ps3.enterButtonAssign)
|
||||
if (current.ps3.spuXFloat != base.ps3.spuXFloat) j.put("ps3SpuXFloat", current.ps3.spuXFloat)
|
||||
if (current.ps3.accurateSpuRsv != base.ps3.accurateSpuRsv) j.put("ps3AccurateSpuRsv", current.ps3.accurateSpuRsv)
|
||||
if (current.ps3.accurateCacheLine != base.ps3.accurateCacheLine) j.put("ps3AccurateCacheLine", current.ps3.accurateCacheLine)
|
||||
@@ -2987,6 +2998,7 @@ data class Settings(
|
||||
netEnabled = if (overrides.has("ps3NetEnabled")) overrides.getBoolean("ps3NetEnabled") else base.ps3.netEnabled,
|
||||
psnStatus = if (overrides.has("ps3PsnStatus")) overrides.getBoolean("ps3PsnStatus") else base.ps3.psnStatus,
|
||||
upnpEnabled = if (overrides.has("ps3UpnpEnabled")) overrides.getBoolean("ps3UpnpEnabled") else base.ps3.upnpEnabled,
|
||||
enterButtonAssign = if (overrides.has("ps3EnterButtonAssign")) overrides.getInt("ps3EnterButtonAssign") else base.ps3.enterButtonAssign,
|
||||
spuXFloat = if (overrides.has("ps3SpuXFloat")) overrides.getInt("ps3SpuXFloat") else base.ps3.spuXFloat,
|
||||
accurateSpuRsv = if (overrides.has("ps3AccurateSpuRsv")) overrides.getBoolean("ps3AccurateSpuRsv") else base.ps3.accurateSpuRsv,
|
||||
accurateCacheLine = if (overrides.has("ps3AccurateCacheLine")) overrides.getBoolean("ps3AccurateCacheLine") else base.ps3.accurateCacheLine,
|
||||
|
||||
@@ -525,6 +525,14 @@ val EN: Map<String, String> = mapOf(
|
||||
"adv.accurateRsxRsv.description" to "Synchronises GPU access to reserved memory strictly. Fixes rare graphical corruption at a performance cost.",
|
||||
"adv.ppuRsvPriority.label" to "PPU Reservation Priority",
|
||||
"adv.ppuRsvPriority.description" to "Gives the main CPU priority over the SPUs when competing for the same memory. Can help games that stall waiting on the PPU.",
|
||||
"pad.section.enterButton" to "Enter Button Assignment",
|
||||
"pad.enterButton.label" to "Confirm button",
|
||||
"pad.enterButton.circle" to "Enter with circle",
|
||||
"pad.enterButton.cross" to "Enter with cross",
|
||||
"pad.enterButton.description" to "Which button confirms in PS3 system dialogs. Japanese games usually expect circle; most others use cross.",
|
||||
"app.resetAll" to "Reset all settings",
|
||||
"app.resetAll.desc" to "Put every setting back to its default. Per-game settings and controller binds are kept.",
|
||||
"app.resetAll.confirm" to "Every global setting goes back to its default. Per-game overrides and controller binds are not touched.",
|
||||
"adv.spuVerification.label" to "SPU Verification",
|
||||
"adv.spuVerification.description" to "Verifies compiled SPU code against the original. Catches miscompiles; turning it off is faster but makes bad codegen silent.",
|
||||
"adv.preciseSpuVerification.label" to "Precise SPU Verification",
|
||||
@@ -821,6 +829,8 @@ val EN: Map<String, String> = mapOf(
|
||||
"pad.stickFeel.acceleration.description" to "Non-linear response curve: small tilts stay precise for aiming, full tilt ramps up to full speed. 0 = linear (off); higher = more curve.",
|
||||
"pad.stickFeel.acceleration.label" to "Acceleration",
|
||||
"pad.stickFeel.antiDeadzone.description" to "Smallest output sent to the game, to cancel a game's OWN built-in stick deadzone (e.g. Cold Fear / Area 51 ignore the stick until ~45%, then aim jumps). Set near the game's deadzone so any stick movement responds immediately and the full travel maps smoothly above it. 0 = off.",
|
||||
"pad.stickFeel.squareGate.label" to "Full Diagonal Range",
|
||||
"pad.stickFeel.squareGate.description" to "Sends the full range on diagonals instead of the reduced value a real DualShock gives (~70%), so diagonal movement is as fast as straight up/down/left/right. On by default. Turn off to match original hardware exactly.",
|
||||
"pad.stickFeel.antiDeadzone.label" to "Anti-Deadzone",
|
||||
"pad.stickFeel.deadzone.description" to "Fraction of physical analog travel ignored near center (applied to the stick's radial distance, so diagonals behave like cardinals). Output re-normalizes past it, so movement still ramps smoothly from 0 — which also means the on-screen effect can be masked by a game's OWN built-in deadzone (Area 51 ignores input below ~45% no matter what you set here; use Anti-Deadzone for that). 0 = off — raw hardware values pass through, including any stick drift.",
|
||||
"pad.stickFeel.deadzone.label" to "Deadzone",
|
||||
|
||||
@@ -364,6 +364,22 @@ object ControllerMappings {
|
||||
private const val KEY_STICK_ANTIDZ = "pad.stick.antiDeadzone"
|
||||
const val STICK_ANTIDZ_MAX = 0.60f
|
||||
private val prefStickAntiDz = PerStickPref(KEY_STICK_ANTIDZ, 0.0f, 0f, STICK_ANTIDZ_MAX)
|
||||
|
||||
// Square gate: send the full per-axis range on diagonals instead of capping them to the
|
||||
// unit circle. A DualShock 3 is circular-gated, so a full diagonal is ~0.707 per axis, and
|
||||
// emitting that is technically faithful -- but it lands inside the internal deadzone of games
|
||||
// that test each axis separately, and their camera then crawls diagonally while the cardinals
|
||||
// are fine. Oblivion is the case that found this.
|
||||
//
|
||||
// DEFAULT ON. Faithfulness to a circular gate is not worth a control scheme that feels broken,
|
||||
// and a modern pad's own gate is closer to square anyway. Off restores the hardware curve for
|
||||
// anyone who wants it. Per stick.
|
||||
private const val KEY_STICK_SQUARE = "pad.stick.square"
|
||||
fun stickSquareGate(left: Boolean): Boolean =
|
||||
MainActivityRuntime.prefs.getBoolean(KEY_STICK_SQUARE + if (left) ".l" else ".r", true)
|
||||
fun setStickSquareGate(left: Boolean, v: Boolean) =
|
||||
MainActivityRuntime.prefs.edit { putBoolean(KEY_STICK_SQUARE + if (left) ".l" else ".r", v) }
|
||||
|
||||
fun stickAntiDeadzone(left: Boolean): Float = prefStickAntiDz.get(left)
|
||||
fun setStickAntiDeadzone(left: Boolean, v: Float) = prefStickAntiDz.set(left, v)
|
||||
|
||||
|
||||
@@ -4361,7 +4361,12 @@ open class MainActivityRuntime : ComponentActivity() {
|
||||
val mag = kotlin.math.hypot(gx, gy)
|
||||
if (mag <= 0f) return
|
||||
val shaped = shapeStickMag(mag.coerceAtMost(1f), left)
|
||||
val scale = shaped / mag // preserves direction; caps square-gate diagonals at unit circle
|
||||
// Dividing by the magnitude caps a full diagonal at the unit circle: 0.707 per axis, which
|
||||
// is what a circular-gated DualShock 3 really sends. Games that deadzone each axis on its
|
||||
// own then ignore diagonals almost entirely. Dividing by the LARGER axis instead expands
|
||||
// to the square, so a full diagonal reaches 1.0 on both. Same result on the cardinals.
|
||||
val denom = if (ControllerMappings.stickSquareGate(left)) kotlin.math.max(abs(gx), abs(gy)) else mag
|
||||
val scale = if (denom > 0f) shaped / denom else 0f
|
||||
val ox = gx * scale
|
||||
val oy = gy * scale
|
||||
if (ox > 0f) accumAnalog(aXPos, ox) else if (ox < 0f) accumAnalog(aXNeg, -ox)
|
||||
|
||||
@@ -792,6 +792,7 @@ fun AppTab() {
|
||||
)
|
||||
|
||||
ClearCacheRow()
|
||||
ResetAllSettingsRow()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -836,6 +837,77 @@ private fun ClearCacheRow() {
|
||||
}
|
||||
}
|
||||
|
||||
/** Put every global setting back to its default in one go.
|
||||
*
|
||||
* The per-tab Reset in the top bar only covers the page you are looking at, which is right for
|
||||
* undoing one experiment but tedious when a config has drifted across half a dozen tabs. This is
|
||||
* the "start clean" button. Per-game overrides are deliberately left alone: they belong to
|
||||
* individual games, are invisible from here, and wiping them from a global page would be a
|
||||
* surprise. Controller binds live in ControllerMappings and keep their own reset. */
|
||||
@Composable
|
||||
private fun ResetAllSettingsRow() {
|
||||
var confirming by remember { mutableStateOf(false) }
|
||||
|
||||
Surface(
|
||||
onClick = { confirming = true },
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
.controllerFocusable("app.resetAll", RoundedCornerShape(20.dp), onConfirm = { confirming = true }),
|
||||
shape = RoundedCornerShape(20.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.72f),
|
||||
border = BorderStroke(1.dp, MaterialTheme.colorScheme.error.copy(alpha = 0.46f)),
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth().padding(horizontal = 14.dp, vertical = 12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
Surface(
|
||||
modifier = Modifier.size(46.dp),
|
||||
shape = RoundedCornerShape(14.dp),
|
||||
color = MaterialTheme.colorScheme.errorContainer,
|
||||
) {
|
||||
Row(verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.Center) {
|
||||
Text("↺", fontSize = 21.sp)
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(12.dp))
|
||||
Column(Modifier.weight(1f)) {
|
||||
Text(str("app.resetAll"), style = MaterialTheme.typography.titleMedium)
|
||||
Text(
|
||||
str("app.resetAll.desc"),
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (confirming) {
|
||||
com.armsx2.ui.common.ConfirmOverlay(
|
||||
title = str("app.resetAll"),
|
||||
message = str("app.resetAll.confirm"),
|
||||
confirmLabel = str("action.reset"),
|
||||
destructive = true,
|
||||
idPrefix = "settings-reset-all",
|
||||
onConfirm = {
|
||||
val defaults = com.armsx2.config.Settings()
|
||||
com.armsx2.ui.InGameOverlay.settingsState.value = defaults
|
||||
com.armsx2.config.ConfigStore.saveGlobal(defaults)
|
||||
|
||||
// Push straight to the core when a game is live, the same way the per-tab reset
|
||||
// does. Without this the UI shows defaults while the running VM keeps the old
|
||||
// values until the next boot.
|
||||
if (MainActivityRuntime.nativeReady.value &&
|
||||
MainActivityRuntime.eState.value != com.armsx2.EmuState.STOPPED) {
|
||||
runCatching { defaults.applyTo() }
|
||||
}
|
||||
|
||||
confirming = false
|
||||
},
|
||||
onDismiss = { confirming = false },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/** Export / import everything a reinstall would destroy: save states, memory cards, artwork,
|
||||
* per-game settings, controller profiles, patches and every preference. ROMs and BIOS are left
|
||||
* out — those live outside the app and survive on their own. See [com.armsx2.BackupManager]. */
|
||||
|
||||
@@ -56,7 +56,7 @@ import kotlinx.coroutines.withContext
|
||||
import com.armsx3.NativeApp
|
||||
|
||||
@Composable
|
||||
fun PadTab(@Suppress("UNUSED_PARAMETER") state: MutableState<Settings>) {
|
||||
fun PadTab(state: MutableState<Settings>) {
|
||||
val scroll = settingsScrollState()
|
||||
ControllerAutoScroll(scroll)
|
||||
val capture = remember { mutableStateOf<ControllerMappings.Action?>(null) }
|
||||
@@ -448,6 +448,24 @@ fun PadTab(@Suppress("UNUSED_PARAMETER") state: MutableState<Settings>) {
|
||||
// (com.armsx2.ui.settings.GyroSection). Here it follows the Pad tab's Global/Game
|
||||
// scope (editSerial) and shares the tab's refreshToken so it re-reads live.
|
||||
GyroSection(editSerial = editSerial, externalRefresh = refreshToken)
|
||||
// Which face button the PS3 itself treats as "confirm" in system dialogs. This is a
|
||||
// console setting (cellSysutil ID_ENTER_BUTTON_ASSIGN), not a pad remap: it changes what
|
||||
// the GAME asks for, so it has to live in the config rather than in the bind table.
|
||||
// Japanese titles generally expect circle and can read as inverted without it.
|
||||
CollapsibleSection(str("pad.section.enterButton"), initiallyExpanded = false) {
|
||||
SegmentedRow(
|
||||
label = str("pad.enterButton.label"),
|
||||
options = listOf(str("pad.enterButton.circle"), str("pad.enterButton.cross")),
|
||||
selectedIndex = state.value.ps3.enterButtonAssign.coerceIn(0, 1),
|
||||
description = str("pad.enterButton.description"),
|
||||
onChange = { idx ->
|
||||
com.armsx2.ui.InGameOverlay.saveSettings(
|
||||
state.value.copy(ps3 = state.value.ps3.copy(enterButtonAssign = idx)),
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
CollapsibleSection(str("pad.section.buttonMapping"), initiallyExpanded = false) {
|
||||
ControllerMappings.actions.forEach { action ->
|
||||
val physical = ControllerMappings.physicalForScope(action, editPlayer.intValue, editSerial)
|
||||
@@ -899,6 +917,12 @@ private fun StickFeelSliders(left: Boolean, title: String, refreshToken: Mutable
|
||||
valueFormatter = { "${it * 5}%" },
|
||||
onChange = { ControllerMappings.setStickSensitivity(left, it / 20f); refreshToken.value++ },
|
||||
)
|
||||
|
||||
ToggleRow(
|
||||
str("pad.stickFeel.squareGate.label"),
|
||||
ControllerMappings.stickSquareGate(left),
|
||||
description = str("pad.stickFeel.squareGate.description"),
|
||||
) { ControllerMappings.setStickSquareGate(left, it); refreshToken.value++ }
|
||||
SettingsDivider()
|
||||
IntSliderRow(
|
||||
label = str("pad.stickFeel.acceleration.label"),
|
||||
|
||||
+55
-41
@@ -25,73 +25,87 @@ import org.json.JSONObject
|
||||
internal val SETTINGS_CATEGORY_FIELDS: Map<SettingsCategory, List<String>> = mapOf(
|
||||
// PerformanceTab.kt
|
||||
SettingsCategory.Performance to listOf(
|
||||
"eeClampMode", "eeCycleRate", "eeCycleSkip", "eeFpuRoundMode",
|
||||
"fastCDVD", "fpsLimit", "frameSkip", "framerateNtsc", "frameratePal",
|
||||
"intcStat", "mtvu", "nominalSpeedPercent", "skipDuplicateFrames", "vu0RoundMode",
|
||||
"vu1Instant", "vu1RoundMode", "vuClampMode", "vuDeferredWrites", "vuFlagHack",
|
||||
"vuNeonFusions", "vuSkipStallSim", "waitLoop",
|
||||
"accurateBlendingUnit", "affinityMode", "eeClampMode", "eeCycleRate", "eeCycleSkip",
|
||||
"eeFpuRoundMode", "fastCDVD", "fpsLimit", "frameSkip", "framerateNtsc", "frameratePal",
|
||||
"hwMipmap", "hwRov", "hwScaler", "intcStat", "mtvu", "nominalSpeedPercent",
|
||||
"ps3AccurateCacheLine", "ps3AccurateSpuDma", "ps3AccurateSpuRsv", "ps3ClocksScale",
|
||||
"ps3GpuTurbo", "ps3LlvmPrecompile", "ps3LlvmThreads", "ps3MaxSpursThreads",
|
||||
"ps3PpuDecoder", "ps3PreferredSpuThreads", "ps3SavestateCompatibleMode",
|
||||
"ps3SilenceAllLogs", "ps3SpuBlockSize", "ps3SpuCache", "ps3SpuDecoder",
|
||||
"ps3SpuLoopDetection", "ps3SpuXFloat", "screenResOverride", "skipDuplicateFrames",
|
||||
"texturePreloading", "upscaleFloat", "vu0RoundMode", "vu1Instant", "vu1RoundMode",
|
||||
"vuClampMode", "vuDeferredWrites", "vuFlagHack", "vuNeonFusions", "vuSkipStallSim",
|
||||
"waitLoop",
|
||||
),
|
||||
// RendererTab.kt
|
||||
SettingsCategory.Graphics to listOf(
|
||||
"accurateBlendingUnit", "adrenoFbFetch", "aspectRatio", "casMode", "casSharpness",
|
||||
"customAspectRatio", "deinterlaceMode", "displayBilinear", "dumpReplaceableTextures", "fmvAspectRatio",
|
||||
"forceMaliFbFetch", "fxaa", "gpuProfile", "gsBackThreadMode", "hardwareDownloadMode",
|
||||
"hwAa1", "hwAccurateAlphaTest", "hwMipmap", "hwRov", "loadTextureReplacements",
|
||||
"adrenoFbFetch", "aspectRatio", "autoProgressiveScan", "casMode", "casSharpness",
|
||||
"customAspectRatio", "customDriverId", "deinterlaceMode", "displayBilinear",
|
||||
"displayFitMode", "dumpReplaceableTextures", "fmvAspectRatio", "forceMaliFbFetch",
|
||||
"fxaa", "gpuProfile", "gsBackThreadMode", "hardwareDownloadMode", "hwAa1",
|
||||
"hwAccurateAlphaTest", "landscapeRenderTop", "loadTextureReplacements",
|
||||
"loadTextureReplacementsAsync", "maxAnisotropy", "orientation",
|
||||
"osdShowTextureReplacements", "portraitRenderTop", "landscapeRenderTop", "autoProgressiveScan",
|
||||
"affinityMode", "precacheTextureReplacements",
|
||||
"osdShowTextureReplacements", "portraitRenderTop", "precacheTextureReplacements",
|
||||
"ps3AnisoFilter", "ps3AsyncTexStream", "ps3DisableZcull", "ps3DisplayAspect",
|
||||
"ps3MsaaMode", "ps3MultithreadedRsx", "ps3ReadColorBuffers", "ps3ReadDepthBuffer",
|
||||
"ps3RelaxedZcull", "ps3Resolution", "ps3ShaderMode", "ps3StrictRendering",
|
||||
"ps3VramLimitMb", "ps3WriteColorBuffers", "ps3WriteDepthBuffer", "renderer",
|
||||
"shadeBoost", "shadeBoostBrightness", "shadeBoostContrast", "shadeBoostGamma",
|
||||
"shadeBoostSaturation", "shaderChainEnabled", "shaderChainParams", "shaderChainPreset",
|
||||
"textureFiltering", "texturePreloading", "triFilter", "tvShader", "upscaleFloat",
|
||||
"vsyncEnable", "displayFitMode", "ps3DisplayAspect",
|
||||
"textureFiltering", "triFilter", "tvShader", "upscaleFloat", "useAngleOpenGL",
|
||||
"vsyncEnable",
|
||||
),
|
||||
// AudioTab.kt
|
||||
SettingsCategory.Audio to listOf(
|
||||
"audioBufferMs", "audioFastForwardVolume", "audioMuted", "audioOpenSLES",
|
||||
"audioOutputLatencyMs", "audioSwapChannels", "audioTimeStretch", "audioVolume",
|
||||
"spu2LightweightMix", "spu2NeonReverb",
|
||||
"ps3AudioBufferMs", "ps3AudioChannels", "ps3AudioCubebBackend", "ps3AudioFormat",
|
||||
"ps3AudioRenderer", "ps3AudioTimeStretch", "spu2LightweightMix", "spu2NeonReverb",
|
||||
),
|
||||
// NetworkTab.kt
|
||||
SettingsCategory.Network to listOf(
|
||||
"dev9AutoGateway", "dev9AutoMask", "dev9Dns1", "dev9Dns2", "dev9EthApi", "dev9EthDevice",
|
||||
"dev9EthEnable", "dev9EthHosts", "dev9EthLogDhcp", "dev9EthLogDns", "dev9Gateway",
|
||||
"dev9HddEnable", "dev9HddFile", "dev9InterceptDhcp", "dev9Mask", "dev9ModeDns1",
|
||||
"dev9ModeDns2", "dev9Ps2Ip", "ip", "url", "usbKeyboard",
|
||||
"dev9AutoGateway", "dev9AutoMask", "dev9Dns1", "dev9Dns2", "dev9EthApi",
|
||||
"dev9EthDevice", "dev9EthEnable", "dev9EthHosts", "dev9EthLogDhcp", "dev9EthLogDns",
|
||||
"dev9Gateway", "dev9HddEnable", "dev9HddFile", "dev9InterceptDhcp", "dev9Mask",
|
||||
"dev9ModeDns1", "dev9ModeDns2", "dev9Ps2Ip", "ip", "ps3NetEnabled", "ps3PsnStatus",
|
||||
"ps3UpnpEnabled", "url", "usbKeyboard",
|
||||
),
|
||||
// OverlayTab.kt
|
||||
SettingsCategory.OnScreen to listOf(
|
||||
"osdColor", "osdScale", "osdShowCpu", "osdShowFps", "osdShowFrameTimes", "osdShowGpu",
|
||||
"osdShowGpuStats", "osdShowGsStats", "osdShowHardwareInfo", "osdShowInputs",
|
||||
"osdShowMessages", "osdShowResolution", "osdShowSettings", "osdShowSpeed",
|
||||
"osdShowVersion", "osdShowVps",
|
||||
// RPCS3's own performance overlay (the lower half of the tab).
|
||||
"ps3OverlayEnabled", "ps3OverlayDetail", "ps3OverlayPosition", "ps3OverlayFontSize",
|
||||
"ps3OverlayOpacity", "ps3OverlayFramerateGraph", "ps3OverlayFrametimeGraph",
|
||||
"ps3OverlayBodyColor", "ps3OverlayBodyBg", "ps3OverlayTitleColor", "ps3OverlayTitleBg",
|
||||
"osdShowVersion", "osdShowVps", "ps3OverlayBodyBg", "ps3OverlayBodyColor",
|
||||
"ps3OverlayDetail", "ps3OverlayEnabled", "ps3OverlayFontSize",
|
||||
"ps3OverlayFramerateGraph", "ps3OverlayFrametimeGraph", "ps3OverlayOpacity",
|
||||
"ps3OverlayPosition", "ps3OverlayTitleBg", "ps3OverlayTitleColor",
|
||||
),
|
||||
// FixesTab.kt — also owns the GameDB fixes and the recompiler toggles, which moved here
|
||||
// from Performance and from the retired Recompiler tab.
|
||||
SettingsCategory.Advanced to listOf(
|
||||
"enableFastBoot", "enableGameFixes",
|
||||
"gamefixBlitInternalFps", "gamefixDmaBusy", "gamefixEETiming", "gamefixFpuMul",
|
||||
"gamefixFullVu0Sync", "gamefixGifFifo", "gamefixGoemonTlb", "gamefixIbit",
|
||||
"gamefixInstantDma", "gamefixOphFlag", "gamefixSkipMpeg",
|
||||
"gamefixSoftwareRendererFmv", "gamefixVif1Stall", "gamefixVuAddSub",
|
||||
"gamefixVuOverflow", "gamefixVuSync", "gamefixXgkick",
|
||||
"enableFastmem", "recEE", "recIOP", "recVU0", "recVU1",
|
||||
"alignSprite", "antiBlur", "autoFlush", "autoFlushSw", "bilinearUpscale", "cpuClutRender",
|
||||
"cpuFramebufferConversion", "cpuSpriteRenderBw", "cpuSpriteRenderLevel", "cropBottom",
|
||||
"cropLeft", "cropRight", "cropTop", "displayZoom", "disableDepthEmulation", "disableFramebufferFetch",
|
||||
"disableInterlaceOffset", "disablePartialInvalidation", "disableRenderFixes",
|
||||
"disableSafeFeatures", "disableShaderCache", "disableVertexShaderExpand", "dithering",
|
||||
"drawBuffering", "estimateTextureRegion", "forceEvenSpritePosition",
|
||||
"alignSprite", "antiBlur", "autoFlush", "autoFlushSw", "bilinearUpscale",
|
||||
"cpuClutRender", "cpuFramebufferConversion", "cpuSpriteRenderBw",
|
||||
"cpuSpriteRenderLevel", "cropBottom", "cropLeft", "cropRight", "cropTop",
|
||||
"disableDepthEmulation", "disableFramebufferFetch", "disableInterlaceOffset",
|
||||
"disablePartialInvalidation", "disableRenderFixes", "disableSafeFeatures",
|
||||
"disableShaderCache", "disableVertexShaderExpand", "displayZoom", "dithering",
|
||||
"drawBuffering", "enableFastBoot", "enableFastmem", "enableGameFixes",
|
||||
"estimateTextureRegion", "forceEvenSpritePosition", "gamefixBlitInternalFps",
|
||||
"gamefixDmaBusy", "gamefixEETiming", "gamefixFpuMul", "gamefixFullVu0Sync",
|
||||
"gamefixGifFifo", "gamefixGoemonTlb", "gamefixIbit", "gamefixInstantDma",
|
||||
"gamefixOphFlag", "gamefixSkipMpeg", "gamefixSoftwareRendererFmv", "gamefixVif1Stall",
|
||||
"gamefixVuAddSub", "gamefixVuOverflow", "gamefixVuSync", "gamefixXgkick",
|
||||
"gpuPaletteConversion", "gpuTargetClut", "halfPixelOffset", "hwAccurateAlphaTest",
|
||||
"integerScaling", "limit24BitDepth", "manualUserHacks", "mergeSprite", "mipmapSw",
|
||||
"nativeScaling", "overrideTextureBarriers", "preloadFrameData", "readTargetsWhenClosing",
|
||||
"roundSprite", "screenOffsets", "showOverscan", "skipDrawEnd", "skipDrawStart",
|
||||
"spinCpuReadbacks", "spinGpuReadbacks", "swThreads", "swThreadsHeight",
|
||||
"syncToHostRefresh", "textureInsideRt", "textureOffsetX", "textureOffsetY",
|
||||
"unscaledPaletteDraw", "useBlitSwapChain", "vsyncQueueSize",
|
||||
"nativeScaling", "overrideTextureBarriers", "preloadFrameData", "ps3AccurateCacheLine",
|
||||
"ps3AccurateDfma", "ps3AccurateRsxRsv", "ps3AccurateSpuRsv", "ps3DebugConsoleMode",
|
||||
"ps3HleLwmutex", "ps3PpuNanHandling", "ps3PpuRsvPriority", "ps3PreciseSpuVerification",
|
||||
"ps3SetDazFtz", "ps3SleepTimers", "ps3SpuVerification", "ps3SpuXFloat",
|
||||
"readTargetsWhenClosing", "recEE", "recIOP", "recVU0", "recVU1", "roundSprite",
|
||||
"screenOffsets", "showOverscan", "skipDrawEnd", "skipDrawStart", "spinCpuReadbacks",
|
||||
"spinGpuReadbacks", "swThreads", "swThreadsHeight", "syncToHostRefresh",
|
||||
"textureInsideRt", "textureOffsetX", "textureOffsetY", "unscaledPaletteDraw",
|
||||
"useBlitSwapChain", "vsyncQueueSize",
|
||||
),
|
||||
// Controls / Hotkeys / Skins / General / Info / Patches / About own no Settings fields —
|
||||
// Controls keeps its binds and tunables in ControllerMappings and has its own reset row.
|
||||
|
||||
@@ -2174,6 +2174,19 @@ error_code sys_fs_unlink(ppu_thread& ppu, vm::cptr<char> path)
|
||||
{
|
||||
return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path};
|
||||
}
|
||||
case fs::error::readonly:
|
||||
{
|
||||
// A virtual device that cannot be modified. On Android that is the mounted ISO behind
|
||||
// /app_home, whose device_base mutators report readonly by design -- so any game that
|
||||
// writes, renames or deletes inside its own USRDIR lands here. Oblivion deletes
|
||||
// /app_home/warnings.txt at startup, which a real console would simply allow.
|
||||
//
|
||||
// This used to fall through to the throw below, which killed the PPU thread inside the
|
||||
// syscall: the game froze with the CPU idle and nothing in the log but a stalled RSX.
|
||||
// Report it and let the guest decide what to do, which is what hardware would do for a
|
||||
// write to read-only media.
|
||||
return { CELL_EROFS, path };
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
|
||||
@@ -2425,7 +2438,9 @@ error_code sys_fs_fcntl(ppu_thread& ppu, u32 fd, u32 op, vm::ptr<void> _arg, u32
|
||||
// Load mountpoint (doesn't support multiple // at the start)
|
||||
std::string_view vpath{arg->name.get_ptr(), arg->name_size};
|
||||
|
||||
sys_fs.notice("sys_fs_fcntl(0xc0000006): %s", vpath);
|
||||
// Trace for the same reason as sys_fs_utime: this rides along with the utime polling
|
||||
// loop and made up the last third of that flood.
|
||||
sys_fs.trace("sys_fs_fcntl(0xc0000006): %s", vpath);
|
||||
|
||||
// Check only mountpoint
|
||||
vpath = vpath.substr(0, vpath.find_first_of('\0'));
|
||||
@@ -3087,6 +3102,19 @@ error_code sys_fs_truncate(ppu_thread& ppu, vm::cptr<char> path, u64 size)
|
||||
{
|
||||
return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path};
|
||||
}
|
||||
case fs::error::readonly:
|
||||
{
|
||||
// A virtual device that cannot be modified. On Android that is the mounted ISO behind
|
||||
// /app_home, whose device_base mutators report readonly by design -- so any game that
|
||||
// writes, renames or deletes inside its own USRDIR lands here. Oblivion deletes
|
||||
// /app_home/warnings.txt at startup, which a real console would simply allow.
|
||||
//
|
||||
// This used to fall through to the throw below, which killed the PPU thread inside the
|
||||
// syscall: the game froze with the CPU idle and nothing in the log but a stalled RSX.
|
||||
// Report it and let the guest decide what to do, which is what hardware would do for a
|
||||
// write to read-only media.
|
||||
return { CELL_EROFS, path };
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
|
||||
@@ -3311,8 +3339,13 @@ error_code sys_fs_utime(ppu_thread& ppu, vm::cptr<char> path, vm::cptr<CellFsUti
|
||||
{
|
||||
lv2_obj::sleep(ppu);
|
||||
|
||||
sys_fs.warning("sys_fs_utime(path=%s, timep=*0x%x)", path, timep);
|
||||
sys_fs.warning("** actime=%u, modtime=%u", timep->actime, timep->modtime);
|
||||
// Trace, not warning. Setting a file's timestamps is routine and uninteresting, but games
|
||||
// that poll it do so in tight loops: Oblivion's FileCaching thread hit this 7274 times in ten
|
||||
// seconds on one .BSA, and at two warning lines a call that alone stalled the emulator for
|
||||
// over twenty seconds. Logging is not free on Android. Raise the sys_fs channel to Trace to
|
||||
// get these back.
|
||||
sys_fs.trace("sys_fs_utime(path=%s, timep=*0x%x)", path, timep);
|
||||
sys_fs.trace("** actime=%u, modtime=%u", timep->actime, timep->modtime);
|
||||
|
||||
const auto [path_error, vpath] = translate_to_str(path);
|
||||
|
||||
@@ -3354,6 +3387,19 @@ error_code sys_fs_utime(ppu_thread& ppu, vm::cptr<char> path, vm::cptr<CellFsUti
|
||||
{
|
||||
return {mp == &g_mp_sys_dev_hdd1 ? sys_fs.warning : sys_fs.error, CELL_ENOENT, path};
|
||||
}
|
||||
case fs::error::readonly:
|
||||
{
|
||||
// A virtual device that cannot be modified. On Android that is the mounted ISO behind
|
||||
// /app_home, whose device_base mutators report readonly by design -- so any game that
|
||||
// writes, renames or deletes inside its own USRDIR lands here. Oblivion deletes
|
||||
// /app_home/warnings.txt at startup, which a real console would simply allow.
|
||||
//
|
||||
// This used to fall through to the throw below, which killed the PPU thread inside the
|
||||
// syscall: the game froze with the CPU idle and nothing in the log but a stalled RSX.
|
||||
// Report it and let the guest decide what to do, which is what hardware would do for a
|
||||
// write to read-only media.
|
||||
return { CELL_EROFS, path };
|
||||
}
|
||||
default:
|
||||
{
|
||||
if (has_non_directory_components(local_path, ends_with_delim_dot_or_dotdot(vpath)))
|
||||
|
||||
@@ -614,7 +614,10 @@ error_code sys_mmapper_map_shared_memory(ppu_thread& ppu, u32 addr, u32 mem_id,
|
||||
{
|
||||
ppu.state += cpu_flag::wait;
|
||||
|
||||
sys_mmapper.warning("sys_mmapper_map_shared_memory(addr=0x%x, mem_id=0x%x, flags=0x%x)", addr, mem_id, flags);
|
||||
// Trace, not warning: a successful map is routine, and games that cycle shared memory do it
|
||||
// thousands of times a second. Oblivion logged 6476 of these in ten seconds, which on Android
|
||||
// costs more than the mapping itself. Raise the sys_mmapper channel to Trace to get them back.
|
||||
sys_mmapper.trace("sys_mmapper_map_shared_memory(addr=0x%x, mem_id=0x%x, flags=0x%x)", addr, mem_id, flags);
|
||||
|
||||
const auto area = vm::get(vm::any, addr);
|
||||
|
||||
@@ -765,7 +768,8 @@ error_code sys_mmapper_unmap_shared_memory(ppu_thread& ppu, u32 addr, vm::ptr<u3
|
||||
{
|
||||
ppu.state += cpu_flag::wait;
|
||||
|
||||
sys_mmapper.warning("sys_mmapper_unmap_shared_memory(addr=0x%x, mem_id=*0x%x)", addr, mem_id);
|
||||
// Pairs with the map above; same reasoning.
|
||||
sys_mmapper.trace("sys_mmapper_unmap_shared_memory(addr=0x%x, mem_id=*0x%x)", addr, mem_id);
|
||||
|
||||
const auto area = vm::get(vm::any, addr);
|
||||
|
||||
|
||||
+16
-1
@@ -1194,7 +1194,22 @@ namespace vm
|
||||
|
||||
if (!utils::memory_lock(g_sudo_addr + addr, size))
|
||||
{
|
||||
vm_log.error("Failed to lock sudo memory (addr=0x%x, size=0x%x). Consider increasing your system limits.", addr, size);
|
||||
// Report this once per session, not once per call.
|
||||
//
|
||||
// Android does not grant RLIMIT_MEMLOCK to ordinary apps, so this fails for every
|
||||
// mapping and cannot be "fixed" by the user the message tells to raise their limits.
|
||||
// Games that map and unmap shared memory in a loop then drown the log in it --
|
||||
// Oblivion's BSTaskManagerThread produced 6470 of these in ten seconds, and writing
|
||||
// them is expensive enough on Android to stall the emulator outright.
|
||||
//
|
||||
// The first one still says what happened; the rest are the same fact repeated.
|
||||
static atomic_t<bool> s_reported{false};
|
||||
|
||||
if (!s_reported.exchange(true))
|
||||
{
|
||||
vm_log.error("Failed to lock sudo memory (addr=0x%x, size=0x%x). Consider increasing your system limits."
|
||||
" Further failures will not be reported.", addr, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -122,30 +122,13 @@ namespace rsx
|
||||
texture_cache_predictor_entry_history_queue<max_write_history_size> write_history;
|
||||
|
||||
static const u32 max_confidence = 8; // Cannot be more "confident" than this value
|
||||
#ifdef __ANDROID__
|
||||
// Mobile-tiler tuning. Here the GPU is usually idle while the CPU stalls on synchronous
|
||||
// colour-buffer readbacks (Write Color Buffers titles such as Demon's Souls). With no GPU
|
||||
// contention a speculative pre-flush is close to free, so engage the predictor sooner:
|
||||
// cross the threshold on the first repeat of a stable readback region, so the pre-flush
|
||||
// fires at framebuffer setup and the later CPU read finds an already-signalled fence
|
||||
// instead of spinning on a fresh GPU copy.
|
||||
static const u32 confident_threshold = 4;
|
||||
static const u32 starting_confidence = 4;
|
||||
#else
|
||||
static const u32 confident_threshold = 6; // We are confident if confidence >= confidence_threshold
|
||||
static const u32 starting_confidence = 3;
|
||||
#endif
|
||||
|
||||
static const u32 confidence_guessed_flush = 2; // Confidence granted when we correctly guess there will be a flush
|
||||
static const u32 confidence_guessed_no_flush = 1; // Confidence granted when we correctly guess there won't be a flush
|
||||
static const u32 confidence_incorrect_guess = -2; // Confidence granted when our guess is incorrect
|
||||
#ifdef __ANDROID__
|
||||
// A wrong speculative flush costs little on an idle GPU, so do not punish it into a
|
||||
// multi-frame confidence rebuild.
|
||||
static const u32 confidence_mispredict = -2;
|
||||
#else
|
||||
static const u32 confidence_mispredict = -4; // Confidence granted when a speculative flush is incorrect
|
||||
#endif
|
||||
|
||||
u32 confidence;
|
||||
|
||||
|
||||
@@ -445,33 +445,6 @@ namespace rsx
|
||||
u32 processed = 0;
|
||||
const bool has_unclaimed = (m_pending_writes.back().sink == 0);
|
||||
|
||||
// Batch-prefetch every GPU occlusion result this drain is about to read, in one round
|
||||
// trip. The VK backend collapses N blocking reads into a single copy + fence and primes
|
||||
// its per-query cache; other backends no-op and the loop below reads per-query as before.
|
||||
// The filter is implemented && num_draws, a superset of what the loop actually reads --
|
||||
// the dynamic have_result early-out only skips some, costing at most a wasted copy.
|
||||
{
|
||||
std::vector<occlusion_query_info*> prefetch_set;
|
||||
prefetch_set.reserve(m_pending_writes.size());
|
||||
|
||||
for (auto& writer : m_pending_writes)
|
||||
{
|
||||
if (!writer.sink)
|
||||
break;
|
||||
|
||||
auto query = writer.query;
|
||||
if (!query || !query->num_draws)
|
||||
continue;
|
||||
|
||||
if (writer.type == CELL_GCM_ZPASS_PIXEL_CNT || writer.type == CELL_GCM_ZCULL_STATS3)
|
||||
prefetch_set.push_back(query);
|
||||
}
|
||||
|
||||
if (prefetch_set.size() > 1)
|
||||
prefetch_occlusion_query_results(prefetch_set);
|
||||
}
|
||||
|
||||
|
||||
// Write all claimed reports unconditionally
|
||||
for (auto& writer : m_pending_writes)
|
||||
{
|
||||
@@ -606,33 +579,6 @@ namespace rsx
|
||||
}
|
||||
|
||||
u32 processed = 0;
|
||||
|
||||
// Batch-prefetch every GPU occlusion result this drain is about to read, in one round
|
||||
// trip. The VK backend collapses N blocking reads into a single copy + fence and primes
|
||||
// its per-query cache; other backends no-op and the loop below reads per-query as before.
|
||||
// The filter is implemented && num_draws, a superset of what the loop actually reads --
|
||||
// the dynamic have_result early-out only skips some, costing at most a wasted copy.
|
||||
{
|
||||
std::vector<occlusion_query_info*> prefetch_set;
|
||||
prefetch_set.reserve(m_pending_writes.size());
|
||||
|
||||
for (auto& writer : m_pending_writes)
|
||||
{
|
||||
if (!writer.sink)
|
||||
break;
|
||||
|
||||
auto query = writer.query;
|
||||
if (!query || !query->num_draws)
|
||||
continue;
|
||||
|
||||
if (writer.type == CELL_GCM_ZPASS_PIXEL_CNT || writer.type == CELL_GCM_ZCULL_STATS3)
|
||||
prefetch_set.push_back(query);
|
||||
}
|
||||
|
||||
if (prefetch_set.size() > 1)
|
||||
prefetch_occlusion_query_results(prefetch_set);
|
||||
}
|
||||
|
||||
for (auto& writer : m_pending_writes)
|
||||
{
|
||||
if (!writer.sink)
|
||||
|
||||
@@ -87,19 +87,8 @@ namespace rsx
|
||||
|
||||
enum constants
|
||||
{
|
||||
#ifdef __ANDROID__
|
||||
// Mobile-tiler tuning. The GPU finishes occlusion queries in microseconds but is
|
||||
// otherwise idle (we are sync-bound, not GPU-bound), so the desktop cadence leaves
|
||||
// completed results unharvested for up to 300us and the guest's Reports-area read
|
||||
// then force-drains them one at a time -- the per-frame ZCULL hitch under Accurate
|
||||
// ZCULL stats. Harvest 4x more often so ready results are picked up without blocking
|
||||
// before the guest asks for them.
|
||||
max_zcull_delay_us = 100, // Delay before a report update operation is forced to retire
|
||||
min_zcull_tick_us = 25, // Default tick duration. To avoid hardware spam, we schedule peeks in multiples of this.
|
||||
#else
|
||||
max_zcull_delay_us = 300, // Delay before a report update operation is forced to retire
|
||||
min_zcull_tick_us = 100, // Default tick duration. To avoid hardware spam, we schedule peeks in multiples of this.
|
||||
#endif
|
||||
occlusion_query_count = 2048, // Number of occlusion query slots available. Real hardware actually has far fewer units before choking
|
||||
max_safe_queue_depth = 1792, // Number of in-flight queries before we start forcefully flushing data from the GPU device.
|
||||
max_stat_registers = 8192 // Size of the statistics cache
|
||||
@@ -214,11 +203,6 @@ namespace rsx
|
||||
virtual void end_occlusion_query(occlusion_query_info* /*query*/) {}
|
||||
virtual bool check_occlusion_query_status(occlusion_query_info* /*query*/) { return true; }
|
||||
virtual void get_occlusion_query_result(occlusion_query_info* query) { query->result = -1; }
|
||||
|
||||
// Optional batch hint: the backend may fetch every result this drain is about to read in
|
||||
// one round trip and prime its per-query cache, so the reads below are then free. Purely
|
||||
// an optimization -- the default does nothing and the per-query path still works.
|
||||
virtual void prefetch_occlusion_query_results(const std::vector<occlusion_query_info*>&) {}
|
||||
virtual void discard_occlusion_query(occlusion_query_info* /*query*/) {}
|
||||
};
|
||||
|
||||
|
||||
+10
-137
@@ -46,32 +46,6 @@ namespace vk
|
||||
}
|
||||
}
|
||||
|
||||
// The render target this sampled image is, if it is one and if the sample park applies to it.
|
||||
// Null on every other target, on every non-RTT, and whenever the tunable is off, so all the
|
||||
// gating lives in one place and the switch below reads as a plain "park or do what we did".
|
||||
static vk::render_target* sample_park_candidate(vk::image* raw, const rsx::sampled_image_descriptor_base* sampler_state)
|
||||
{
|
||||
#ifdef __ANDROID__
|
||||
if constexpr (vk::s_sample_park_frames == 0)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (sampler_state->upload_context != rsx::texture_upload_context::framebuffer_storage)
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// dynamic_cast rather than vk::as_rtt: as_rtt asserts, and a framebuffer_storage
|
||||
// descriptor can still hand over a temporary subresource that is a plain viewable_image.
|
||||
return dynamic_cast<vk::render_target*>(raw);
|
||||
#else
|
||||
static_cast<void>(raw);
|
||||
static_cast<void>(sampler_state);
|
||||
return nullptr;
|
||||
#endif
|
||||
}
|
||||
|
||||
void validate_image_layout_for_read_access(
|
||||
vk::command_buffer& cmd,
|
||||
vk::image_view* view,
|
||||
@@ -100,33 +74,6 @@ namespace vk
|
||||
break;
|
||||
}
|
||||
|
||||
// A sample-parked surface arrives here on every sample after the first, because it
|
||||
// was left in GENERAL instead of being moved to SHADER_READ_ONLY_OPTIMAL.
|
||||
//
|
||||
// It still needs a write -> read barrier whenever it has been written since the last
|
||||
// one, and that barrier still has to end the open pass: an unbound RTT is not an
|
||||
// attachment of the pass in flight, so VUID-vkCmdPipelineBarrier-image-04073 rules
|
||||
// out keeping it. What it must NOT do is move the layout, or the surface un-parks and
|
||||
// pays the return trip after all.
|
||||
//
|
||||
// The "has been written since" test is the load-bearing part. Without parking the
|
||||
// layout itself answered it - already in SHADER_READ_ONLY_OPTIMAL meant already
|
||||
// synchronized, so the second through five-hundredth draw sampling a shadow map fell
|
||||
// through the default case and issued nothing. Leaving the surface in GENERAL erases
|
||||
// that signal, and issuing a barrier per sampling draw instead of per write would be
|
||||
// a catastrophic regression rather than a win. render_target carries the answer
|
||||
// explicitly now; see sample_park_needs_read_barrier.
|
||||
if (auto parked = sample_park_candidate(raw, sampler_state);
|
||||
parked && raw->current_layout == vk::render_target::get_sample_park_layout())
|
||||
{
|
||||
if (!parked->sample_park_needs_read_barrier())
|
||||
{
|
||||
// Already synchronized for reading, and already in a layout that is legal to
|
||||
// sample from. Nothing at all to record.
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This was used in a cyclic ref before, but is missing a barrier
|
||||
// No need for a full stall, use a custom barrier instead
|
||||
VkPipelineStageFlags src_stage;
|
||||
@@ -146,74 +93,22 @@ namespace vk
|
||||
dst_stage |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT;
|
||||
}
|
||||
|
||||
{
|
||||
// Stay in the park layout if this surface is parked, otherwise the historical
|
||||
// move to SHADER_READ_ONLY_OPTIMAL. Note this keeps oldLayout == newLayout in
|
||||
// the parked case, which is the only form a barrier is allowed to take inside a
|
||||
// render pass - not that it can stay inside one here, but it means the barrier
|
||||
// carries no transition cost of its own either.
|
||||
auto parked = sample_park_candidate(raw, sampler_state);
|
||||
const bool stay_parked = parked && parked->try_arm_sample_park() &&
|
||||
raw->current_layout == vk::render_target::get_sample_park_layout();
|
||||
vk::insert_image_memory_barrier(
|
||||
cmd,
|
||||
raw->value,
|
||||
raw->current_layout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL,
|
||||
src_stage, dst_stage,
|
||||
src_access, dst_access,
|
||||
{ raw->aspect(), 0, 1, 0, 1 });
|
||||
|
||||
const VkImageLayout target_layout = stay_parked
|
||||
? vk::render_target::get_sample_park_layout()
|
||||
: VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
|
||||
vk::insert_image_memory_barrier(
|
||||
cmd,
|
||||
raw->value,
|
||||
raw->current_layout, target_layout,
|
||||
src_stage, dst_stage,
|
||||
src_access, dst_access,
|
||||
{ raw->aspect(), 0, 1, 0, 1 });
|
||||
|
||||
raw->current_layout = target_layout;
|
||||
|
||||
if (stay_parked)
|
||||
{
|
||||
parked->on_sample_park_synced();
|
||||
}
|
||||
else if (parked)
|
||||
{
|
||||
parked->clear_sample_park();
|
||||
}
|
||||
}
|
||||
raw->current_layout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL;
|
||||
break;
|
||||
case VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL:
|
||||
case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL:
|
||||
ensure(sampler_state->upload_context == rsx::texture_upload_context::framebuffer_storage);
|
||||
if (!sampler_state->is_cyclic_reference) [[ likely ]]
|
||||
{
|
||||
// Standard pre-read barrier, and the first half of the sample/rebind cycle.
|
||||
//
|
||||
// This transition is the floor. The surface was just rendered to, so the read
|
||||
// needs a write -> read barrier, and a barrier cannot stay inside a pass for an
|
||||
// image the pass does not have attached. What the park changes is the
|
||||
// destination: GENERAL is legal to sample from and legal to attach, so the next
|
||||
// bind has nothing to undo, whereas SHADER_READ_ONLY_OPTIMAL is not a legal
|
||||
// attachment layout and forces a second teardown to leave it.
|
||||
//
|
||||
// The transition itself is issued through the same change_layout as before, so
|
||||
// the access/stage scopes image_helpers derives for it are unchanged apart from
|
||||
// the destination, and it is still charged to ImgHelper:43.
|
||||
if (auto parked = sample_park_candidate(raw, sampler_state))
|
||||
{
|
||||
if (parked->try_arm_sample_park())
|
||||
{
|
||||
raw->change_layout(cmd, vk::render_target::get_sample_park_layout());
|
||||
parked->on_sample_park_synced();
|
||||
break;
|
||||
}
|
||||
|
||||
// Declined - bound, multisampled, or no renderer. Drop any window left over
|
||||
// from an earlier park so the deadline can never outlive the layout it
|
||||
// describes. Both readers already re-check current_layout, so this is
|
||||
// tidiness rather than a fix, but it keeps the state machine to one rule:
|
||||
// a live deadline always means the surface is in the park layout.
|
||||
parked->clear_sample_park();
|
||||
}
|
||||
|
||||
// Standard pre-read barrier.
|
||||
raw->change_layout(cmd, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL);
|
||||
break;
|
||||
}
|
||||
@@ -235,11 +130,6 @@ void VKGSRender::begin_render_pass()
|
||||
get_render_pass(),
|
||||
m_draw_fbo->value,
|
||||
{ positionu{0u, 0u}, sizeu{m_draw_fbo->width(), m_draw_fbo->height()} });
|
||||
|
||||
// Publish what this pass actually has attached, so a barrier that wants to stay inside it can
|
||||
// check VUID-vkCmdPipelineBarrier-image-04073 rather than trust the caller. m_fbo_images is
|
||||
// the exact list the framebuffer was built from a few lines up the call chain in prepare_rtts.
|
||||
vk::set_renderpass_attachments(*m_current_command_buffer, m_fbo_images);
|
||||
}
|
||||
|
||||
void VKGSRender::close_render_pass()
|
||||
@@ -1208,24 +1098,7 @@ void VKGSRender::emit_geometry(u32 sub_index)
|
||||
if (pass)
|
||||
{
|
||||
// Subpass mismatch, end it before proceeding
|
||||
if (rsx::prof::enabled()) [[unlikely]]
|
||||
{
|
||||
rsx::prof::g_rp_sites[1]++;
|
||||
|
||||
// Which of the two mismatches fired. The framebuffer changing is the game
|
||||
// switching render target and nothing here can avoid it; the render pass handle
|
||||
// changing under an unchanged framebuffer can only be the attachment layouts,
|
||||
// since format and sample count cannot move without the framebuffer moving too.
|
||||
//
|
||||
// This site only became loud once parking stopped the bind-time layout change
|
||||
// from ending the pass earlier in the draw, which left the previous draw's pass
|
||||
// open to be torn down here instead. Whether that is the same teardown relocated
|
||||
// or an extra one caused by layout churn is exactly what these two counters
|
||||
// separate, and it decides whether parking longer is worth anything.
|
||||
rsx::prof::g_rp_sites[(m_draw_fbo->value != fbo) ? 19 : 18]++;
|
||||
}
|
||||
|
||||
vk::end_renderpass(cmd);
|
||||
if (rsx::prof::enabled()) [[unlikely]] rsx::prof::g_rp_sites[1]++; vk::end_renderpass(cmd);
|
||||
}
|
||||
|
||||
// Starting a new renderpass should clobber dynamic state
|
||||
|
||||
@@ -483,13 +483,6 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar)
|
||||
if (!m_swapchain->init(m_swapchain_dims.width, m_swapchain_dims.height))
|
||||
{
|
||||
swapchain_unavailable = true;
|
||||
#ifdef ANDROID
|
||||
// The VkSurfaceKHR is bound to the ANativeWindow captured at create time, so a surface
|
||||
// lost during boot-time init stays lost however often we re-query it. Flag it so the first
|
||||
// reinitialize_swapchain() takes the recreate branch with the new window instead of
|
||||
// soft-looping forever against a dead surface.
|
||||
m_surface_lost = true;
|
||||
#endif
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -522,37 +515,9 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar)
|
||||
m_occlusion_query_manager->set_control_flags(VK_QUERY_CONTROL_PRECISE_BIT, 0);
|
||||
}
|
||||
|
||||
// The vertex cache can only retain an entry while the ring memory naming it survives, so
|
||||
// retention engages only once the ring holds the in-flight headroom plus another frame of
|
||||
// geometry -- four frames at the current headroom. Arkham City peaks near 31MB of geometry a
|
||||
// frame, needing ~124MB, so the 64MB default leaves retention permanently disengaged in
|
||||
// exactly the heavy scenes that stand to gain from it. The heap is growable, but it grows on
|
||||
// allocation pressure and wrapping relieves that pressure, so it never reaches a size that
|
||||
// would let entries live: the initial size is the only lever. Scale it against the memory
|
||||
// budget rather than taking a fixed 192MB, because that is a fifth of the floor budget we
|
||||
// hand a 4GB phone.
|
||||
u32 attrib_ring_size_m = VK_ATTRIB_RING_BUFFER_SIZE_M;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
{
|
||||
// Flat, deliberately. This was first scaled off get_budgetable_device_memory, which was
|
||||
// wrong twice over: that figure is a texture-cache quota the ring does not draw from, and
|
||||
// it is derived from free memory *after* the emulator has taken its RAM, so it floors at
|
||||
// 1024M on an 8GB device and every tier collapsed back to 64M.
|
||||
//
|
||||
// 192M covers a peak frame up to 48M against the 4x rule; measured peaks here run 11-32M.
|
||||
// 128M would only just clear the observed 31.8M peak and would flap on anything heavier.
|
||||
attrib_ring_size_m = 192u;
|
||||
|
||||
// Named because the retention log reports the ring size it had to work with, and a reader
|
||||
// otherwise cannot tell a ring that was sized down from one that was never sized up.
|
||||
rsx_log.notice("Attribute ring sized to %uM.", attrib_ring_size_m);
|
||||
}
|
||||
#endif
|
||||
|
||||
// VRAM allocation
|
||||
// This first set is bound persistently, so grow notifications are enabled.
|
||||
m_attrib_ring_info.create(VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, attrib_ring_size_m * 0x100000, vk::heap_pool_default, "attrib buffer", 0x400000, VK_TRUE);
|
||||
m_attrib_ring_info.create(VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT, VK_ATTRIB_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_default, "attrib buffer", 0x400000, VK_TRUE);
|
||||
m_fragment_env_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment env buffer", 0x10000, VK_TRUE);
|
||||
m_vertex_env_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_default, "vertex env buffer", 0x10000, VK_TRUE);
|
||||
m_fragment_texture_params_ring_info.create(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_UBO_RING_BUFFER_SIZE_M * 0x100000, vk::heap_pool_low_latency, "fragment texture params buffer", 0x10000, VK_TRUE);
|
||||
@@ -606,9 +571,7 @@ VKGSRender::VKGSRender(utils::serial* ar) noexcept : GSRender(ar)
|
||||
m_fragment_constants_buffer_info = { *m_fragment_constants_ring_info.heap, 0, VK_WHOLE_SIZE };
|
||||
|
||||
const auto& limits = m_device->gpu().get_limits();
|
||||
// Clamped to the device limit, not the ring size: views window across the ring (see
|
||||
// upload_vertex_data), so a ring larger than one view is fine and is the normal case now.
|
||||
m_texbuffer_view_size = std::min(limits.maxTexelBufferElements, attrib_ring_size_m * 0x100000u);
|
||||
m_texbuffer_view_size = std::min(limits.maxTexelBufferElements, VK_ATTRIB_RING_BUFFER_SIZE_M * 0x100000u);
|
||||
|
||||
// Initialize bulk allocators
|
||||
m_vertex_env_allocator = std::make_unique<rsx::data_heap::bulk_allocator<256, 96>>(
|
||||
@@ -2926,85 +2889,6 @@ bool VKGSRender::check_occlusion_query_status(rsx::reports::occlusion_query_info
|
||||
return m_occlusion_query_manager->check_query_status(oldest);
|
||||
}
|
||||
|
||||
// Collapse the drain's N blocking per-query reads into one GPU copy plus a single fence wait,
|
||||
// then prime the per-query cache so the reads that follow cost nothing. On a tiler each
|
||||
// individual read is a full round trip, so N of them back to back is most of the ZCULL cost.
|
||||
// Best-effort: on any bail the caller's per-query path still runs unchanged.
|
||||
// Ported from ouroboros420/rpcsx (7ed3365bc).
|
||||
void VKGSRender::prefetch_occlusion_query_results(const std::vector<rsx::reports::occlusion_query_info*>& queries)
|
||||
{
|
||||
if (queries.size() < 2)
|
||||
{
|
||||
// Not worth a batch round trip; the per-query loop handles it with no extra hard sync.
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<u32> indices;
|
||||
indices.reserve(queries.size() * 4);
|
||||
|
||||
bool needs_hard_sync = false;
|
||||
|
||||
for (auto* query : queries)
|
||||
{
|
||||
if (!query) continue;
|
||||
|
||||
auto& data = m_occlusion_map[query->driver_handle];
|
||||
if (data.indices.empty()) continue;
|
||||
|
||||
// A query begun in the current command buffer has not been ENDED yet, so copying it
|
||||
// would need a hard sync -- exactly what the per-query path deliberately avoids. Skip
|
||||
// the whole batch rather than force one.
|
||||
if (data.is_current(m_current_command_buffer))
|
||||
{
|
||||
needs_hard_sync = true;
|
||||
break;
|
||||
}
|
||||
|
||||
for (const auto id : data.indices)
|
||||
{
|
||||
indices.push_back(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (needs_hard_sync || indices.size() < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Sorted so the pool-aware coalescing in copy_query_results actually finds runs.
|
||||
std::sort(indices.begin(), indices.end());
|
||||
indices.erase(std::unique(indices.begin(), indices.end()), indices.end());
|
||||
|
||||
const u64 required = indices.size() * 4ull;
|
||||
|
||||
if (!m_occlusion_readback_buffer || m_occlusion_readback_buffer->size() < required)
|
||||
{
|
||||
const u64 alloc_size = std::max<u64>(required, 4096);
|
||||
m_occlusion_readback_buffer = std::make_unique<vk::buffer>(*m_device,
|
||||
alloc_size,
|
||||
m_device->get_memory_mapping().host_visible_coherent, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT,
|
||||
VK_BUFFER_USAGE_TRANSFER_DST_BIT, 0,
|
||||
VMM_ALLOCATION_POOL_SYSTEM);
|
||||
}
|
||||
|
||||
m_occlusion_query_manager->copy_query_results(*m_current_command_buffer, indices, m_occlusion_readback_buffer->value);
|
||||
|
||||
// One fence wait drains the whole batch. flush_command_queue(true) submits and waits, which
|
||||
// is the single round trip this exists to pay instead of N.
|
||||
if (rsx::prof::enabled()) [[unlikely]] rsx::prof::g_flush_sites[11]++;
|
||||
flush_command_queue(true);
|
||||
|
||||
if (auto* mapped = static_cast<u32*>(m_occlusion_readback_buffer->map(0, required)))
|
||||
{
|
||||
for (usz i = 0; i < indices.size(); ++i)
|
||||
{
|
||||
m_occlusion_query_manager->prime_query_result(indices[i], mapped[i]);
|
||||
}
|
||||
|
||||
m_occlusion_readback_buffer->unmap();
|
||||
}
|
||||
}
|
||||
|
||||
void VKGSRender::get_occlusion_query_result(rsx::reports::occlusion_query_info* query)
|
||||
{
|
||||
auto &data = m_occlusion_map[query->driver_handle];
|
||||
@@ -3029,82 +2913,9 @@ void VKGSRender::get_occlusion_query_result(rsx::reports::occlusion_query_info*
|
||||
|
||||
data.sync();
|
||||
|
||||
// On a tile-based renderer the result is usually a whole tiling pass away, so this wait
|
||||
// is long -- and it must stay interruptible. get_query_result() blocks without ever
|
||||
// checking external_interrupt_lock, and a PPU thread that faults on RSX-guarded memory
|
||||
// during that window spins in on_access_violation() waiting for an ack this thread can no
|
||||
// longer give: PPU pinned in sched_yield, RSX parked in the query wait, presenting as a
|
||||
// freeze. Wait here instead, servicing external interrupts the way the FIFO
|
||||
// semaphore_acquire loop does, and only call get_query_result() once the value is ready.
|
||||
// Ported from rfandango/rpcsx (a560768ce).
|
||||
static const bool needs_interruptible_wait = vk::is_tile_based_renderer(vk::get_driver_vendor());
|
||||
|
||||
bool aborted = false;
|
||||
|
||||
// Gather data
|
||||
for (const auto occlusion_id : data.indices)
|
||||
{
|
||||
if (needs_interruptible_wait)
|
||||
{
|
||||
u32 wait_iterations = 0;
|
||||
bool rescued = false;
|
||||
|
||||
while (!m_occlusion_query_manager->check_query_status(occlusion_id))
|
||||
{
|
||||
// Rescue path. A query begun in the current command buffer is not even ENDED
|
||||
// until close_and_submit_command_buffer(), so if the is_current() bookkeeping
|
||||
// above mis-reported, the value can never arrive without a flush. Rather than
|
||||
// pay a hard sync on every ZCULL read, give it a generous window then force the
|
||||
// flush once. If the query still never readies after this fires, the GPU is not
|
||||
// retiring work at all -- a driver hang, not a bookkeeping bug -- and this
|
||||
// warning is the breadcrumb that tells the two apart.
|
||||
if (!rescued && ++wait_iterations >= 4096)
|
||||
{
|
||||
rescued = true;
|
||||
rsx_log.warning("ZCULL result did not arrive; forcing command flush (query=%d)", occlusion_id);
|
||||
|
||||
std::lock_guard lock(m_flush_queue_mutex);
|
||||
if (rsx::prof::enabled()) [[unlikely]] rsx::prof::g_flush_sites[11]++; flush_command_queue();
|
||||
|
||||
if (m_flush_requests.pending())
|
||||
{
|
||||
m_flush_requests.clear_pending_flag();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
// Not optional: a PPU thread that faults on RSX-guarded memory posts a flush
|
||||
// request in on_access_violation() and spins in producer_wait() until this
|
||||
// thread consumes it. Servicing only external_interrupt_lock is not enough --
|
||||
// that was a measured deadlock. The FIFO semaphore_acquire wait survives the
|
||||
// same situation precisely because cpu_wait() makes this call.
|
||||
on_semaphore_acquire_wait();
|
||||
|
||||
if (external_interrupt_lock)
|
||||
{
|
||||
wait_pause();
|
||||
}
|
||||
else if (state & cpu_flag::exit)
|
||||
{
|
||||
// The result may never arrive during shutdown, so do not read it.
|
||||
aborted = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Park at near-zero power; the architected event stream bounds the poll
|
||||
// period to tens of microseconds.
|
||||
utils::wait_for_event();
|
||||
}
|
||||
}
|
||||
|
||||
if (aborted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
query->result += m_occlusion_query_manager->get_query_result(occlusion_id);
|
||||
if (query->result && !g_cfg.video.precise_zpass_count)
|
||||
{
|
||||
|
||||
@@ -70,8 +70,6 @@ private:
|
||||
output_scaling_mode m_output_scaling{output_scaling_mode::bilinear};
|
||||
|
||||
std::unique_ptr<vk::buffer> m_cond_render_buffer;
|
||||
// Host-visible scratch for the batched ZCULL readback. Allocated on first use.
|
||||
std::unique_ptr<vk::buffer> m_occlusion_readback_buffer;
|
||||
u64 m_cond_render_sync_tag = 0;
|
||||
|
||||
shared_mutex m_sampler_mutex;
|
||||
@@ -104,8 +102,6 @@ private:
|
||||
bool m_surface_lost = false;
|
||||
vk::instance m_instance;
|
||||
vk::render_device *m_device;
|
||||
// Timestamp of the last periodic pipeline-cache serialize (see flip()).
|
||||
u64 m_last_pipeline_cache_save_time = 0;
|
||||
|
||||
//Vulkan internals
|
||||
std::unique_ptr<vk::query_pool_manager> m_occlusion_query_manager;
|
||||
@@ -148,23 +144,6 @@ private:
|
||||
|
||||
rsx::simple_array<vk::data_heap*> m_flushable_data_heaps; // List of heaps that can be 'dirty' and need manual flush
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// Cross-frame vertex cache retention. m_attrib_ring_info is a ring, so a cached
|
||||
// offset_in_heap stays meaningful only until the ring laps back over it; the cache used to be
|
||||
// purged every frame because nothing tracked that. See vertex_cache_on_frame_end().
|
||||
u64 m_vtx_heap_cursor = 0; // Monotonic byte count ever handed out by m_attrib_ring_info
|
||||
usz m_vtx_heap_put = 0; // Ring PUT at the last cursor sample
|
||||
u64 m_vtx_retain_floor = 0; // Entries stamped below this cursor value are evicted
|
||||
u64 m_vtx_frame_base = 0; // Cursor as of the start of the current frame
|
||||
u64 m_vtx_frame_peak = 0; // Largest single-frame consumption in the recent window
|
||||
u32 m_vtx_peak_ttl = 0; // Frames left before the peak is allowed to decay
|
||||
u64 m_vtx_reserve_bytes = 0; // Ring bytes withheld from the allocator (0 = retention off)
|
||||
u64 m_vtx_frame_marks[8]{}; // Cursor at the end of each of the last 8 frames
|
||||
u32 m_vtx_frame_index = 0;
|
||||
bool m_vtx_retention_locked_out = false;
|
||||
VkBuffer m_vtx_heap_handle = VK_NULL_HANDLE; // Detects a grow(), which discards the whole heap
|
||||
#endif
|
||||
|
||||
VkDescriptorBufferInfoEx m_vertex_env_buffer_info {};
|
||||
VkDescriptorBufferInfoEx m_fragment_env_buffer_info {};
|
||||
VkDescriptorBufferInfoEx m_vertex_layout_stream_info {};
|
||||
@@ -268,12 +247,6 @@ private:
|
||||
vk::vertex_upload_info upload_vertex_data();
|
||||
rsx::simple_array<u8> m_scratch_mem;
|
||||
|
||||
#ifdef __ANDROID__
|
||||
void vertex_cache_sample_heap(); // Advance the ring cursor, and detect a heap reallocation
|
||||
void vertex_cache_on_heap_reset(); // Drop everything; the heap the offsets named is gone
|
||||
void vertex_cache_on_frame_end(); // Size the reservation and evict what it cannot cover
|
||||
#endif
|
||||
|
||||
bool load_program();
|
||||
void load_program_env();
|
||||
void update_vertex_env(u32 id, const vk::vertex_upload_info& vertex_info);
|
||||
@@ -298,7 +271,6 @@ public:
|
||||
void end_occlusion_query(rsx::reports::occlusion_query_info* query) override;
|
||||
bool check_occlusion_query_status(rsx::reports::occlusion_query_info* query) override;
|
||||
void get_occlusion_query_result(rsx::reports::occlusion_query_info* query) override;
|
||||
void prefetch_occlusion_query_results(const std::vector<rsx::reports::occlusion_query_info*>& queries) override;
|
||||
void discard_occlusion_query(rsx::reports::occlusion_query_info* query) override;
|
||||
|
||||
// External callback in case we need to suddenly submit a commandlist unexpectedly, e.g in a violation handler
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "stdafx.h"
|
||||
#include "VKGSRender.h"
|
||||
#include "Emu/Cell/timers.hpp"
|
||||
#include "vkutils/buffer_object.h"
|
||||
#include "vkutils/memory.h"
|
||||
#include "Emu/RSX/Overlays/overlay_manager.h"
|
||||
@@ -244,35 +243,9 @@ void VKGSRender::advance_queued_frames()
|
||||
|
||||
vk::remove_unused_framebuffers();
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// Was an unconditional m_vertex_cache->purge(). Arkham City lands 10 cache hits in 1386
|
||||
// requests because of it: every entry is thrown away at the frame boundary, so the 10 are
|
||||
// within-frame duplicates and the other 1376 draws re-upload geometry that never changed,
|
||||
// 2499us of it a frame, and hand a tile-based GPU 1.35M vertices in one pass to re-bin.
|
||||
//
|
||||
// Entries can only survive if the ring memory they name survives with them, so this sizes a
|
||||
// reservation and evicts anything it cannot cover. See vertex_cache_on_frame_end().
|
||||
vertex_cache_on_frame_end();
|
||||
#else
|
||||
m_vertex_cache->purge();
|
||||
#endif
|
||||
|
||||
m_current_frame->tag_frame_end();
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// tag_frame_end() records PUT-1 for every managed heap, which is what frame_context_cleanup
|
||||
// later publishes as that heap's GET once this frame retires - i.e. "the GPU is done with
|
||||
// everything before here, reuse it". For the attribute ring that is exactly the statement that
|
||||
// makes a retained entry unsafe, so publish the retention floor instead. It is always at or
|
||||
// behind the value this would otherwise carry, so the allocator ends up strictly more
|
||||
// conservative than before and in-flight frames stay protected as they already were.
|
||||
if (m_vtx_reserve_bytes && m_attrib_ring_info.size())
|
||||
{
|
||||
m_current_frame->heap_snapshot[&m_attrib_ring_info] =
|
||||
static_cast<s64>(m_vtx_retain_floor % m_attrib_ring_info.size());
|
||||
}
|
||||
#endif
|
||||
|
||||
// Throttle here rather than by accident.
|
||||
//
|
||||
// The queue used to stay at one entry because the poll above blocked until the oldest
|
||||
@@ -1247,19 +1220,4 @@ void VKGSRender::flip(const rsx::display_flip_info_t& info)
|
||||
if (rsx::prof::enabled()) [[unlikely]] rsx::prof::g_flush_sites[20]++; flush_command_queue(true);
|
||||
}
|
||||
}
|
||||
|
||||
// Crash-resilient pipeline-cache persistence. The blob is otherwise only written on
|
||||
// clean teardown, so a crash or an Android LMK kill mid-session loses the entire
|
||||
// warmup -- and a cold-boot compile-burst crash then keeps every later boot cold too.
|
||||
// save_pipeline_cache() skips the write when the cache size has not moved, so steady
|
||||
// state costs one size query per interval.
|
||||
if (const u64 now = get_system_time(); now >= m_last_pipeline_cache_save_time + 120'000'000)
|
||||
{
|
||||
m_last_pipeline_cache_save_time = now;
|
||||
|
||||
if (m_device)
|
||||
{
|
||||
m_device->save_pipeline_cache();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -269,17 +269,13 @@ namespace vk
|
||||
{
|
||||
VkGraphicsPipelineCreateInfo create_info = *p_graphics_info;
|
||||
create_info.layout = m_pipeline_layout;
|
||||
// Shared driver cache, so the SPIR-V -> ISA compile is not redone every cold boot.
|
||||
// Null when unavailable, which is exactly what was passed here before.
|
||||
const VkPipelineCache pipe_cache = g_render_device ? g_render_device->get_pipeline_cache() : VK_NULL_HANDLE;
|
||||
CHECK_RESULT(vkCreateGraphicsPipelines(m_device, pipe_cache, 1, &create_info, nullptr, &m_pipeline));
|
||||
CHECK_RESULT(vkCreateGraphicsPipelines(m_device, nullptr, 1, &create_info, nullptr, &m_pipeline));
|
||||
}
|
||||
else
|
||||
{
|
||||
VkComputePipelineCreateInfo create_info = *p_compute_info;
|
||||
create_info.layout = m_pipeline_layout;
|
||||
const VkPipelineCache pipe_cache = g_render_device ? g_render_device->get_pipeline_cache() : VK_NULL_HANDLE;
|
||||
CHECK_RESULT(vkCreateComputePipelines(m_device, pipe_cache, 1, &create_info, nullptr, &m_pipeline));
|
||||
CHECK_RESULT(vkCreateComputePipelines(m_device, nullptr, 1, &create_info, nullptr, &m_pipeline));
|
||||
}
|
||||
|
||||
m_linked = true;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user