mirror of
https://github.com/ARMSX2/ARMSX2.git
synced 2026-08-24 16:50:16 -07:00
iOS: share one option list between the global and per game screens
Every picker in the graphics settings was written out twice, once on the global screen and once on the per game tab, and they had drifted. The per game TV/CRT Shader stopped at Lottes while the global one went two further to 4xRGSS and NxAGSS, so those two could not be chosen for a game at all and a file already holding 6 or 7 showed an empty picker. The per game Renderer was missing Null, and offered Software on Mac Catalyst where the global screen deliberately hides it behind a build check. UpscaleOptions already exists for this reason. Its header records that its own two copies had drifted so the per game one stopped at 4x while the global went to 8x. That got fixed once, for one setting, and never generalised. The other eleven lists live next to it now and both screens read the same one. The Catalyst check moved inside the renderer list so the two cannot disagree about it again. PickerOption is gone; it only existed to hold the per game copies. Trilinear is the one that cannot use the shared use global helper, since -1 is a real TriFiltering value there, so its marker stays Int32.min and says so. Left alone on purpose: CAS Sharpness, the queue and latency lists, and aspect ratio. Those do differ, but as a slider against a picker, a stepper against a picker, and an Int tag against a String tag. Each needs a decision about which control is right rather than a list to move.
This commit is contained in:
@@ -114,11 +114,9 @@ struct GraphicsSettingsView: View {
|
||||
|
||||
Section(settings.localized("Renderer")) {
|
||||
Picker(settings.localized("Renderer"), selection: $settings.renderer) {
|
||||
Text(settings.localized("Metal (Hardware)")).tag(17)
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
Text(settings.localized("Software")).tag(13)
|
||||
Text(settings.localized("Null (No Output)")).tag(11)
|
||||
#endif
|
||||
ForEach(SettingsOptions.renderer, id: \.id) { option in
|
||||
Text(settings.localized(option.title)).tag(option.id)
|
||||
}
|
||||
}
|
||||
.disabled(gameIsLoaded)
|
||||
if gameIsLoaded {
|
||||
@@ -372,35 +370,23 @@ struct GraphicsSettingsView: View {
|
||||
.font(.caption)
|
||||
.foregroundStyle(.secondary)
|
||||
|
||||
intPicker("Texture Inside RT", selection: $settings.textureInsideRt, options: [
|
||||
("Off", 0), ("Inside Targets", 1), ("Merge Targets", 2)
|
||||
])
|
||||
intPicker("Texture Inside RT", selection: $settings.textureInsideRt, shared: SettingsOptions.textureInsideRT)
|
||||
intPicker("Limit 24-Bit Depth", selection: $settings.limit24BitDepth, options: [
|
||||
("Off", 0), ("Prioritise Upper Bits", 1), ("Prioritise Lower Bits", 2)
|
||||
])
|
||||
intPicker("Native Scaling", selection: $settings.nativeScaling, options: [
|
||||
("Off", 0), ("Normal", 1), ("Aggressive", 2), ("Normal (Maintain Upscale)", 3), ("Aggressive (Maintain Upscale)", 4)
|
||||
])
|
||||
intPicker("CPU CLUT Render", selection: $settings.cpuClutRender, options: [
|
||||
("Disabled", 0), ("Normal", 1), ("Aggressive", 2)
|
||||
])
|
||||
intPicker("GPU Target CLUT", selection: $settings.gpuTargetClut, options: [
|
||||
("Off", 0), ("Enabled (Exact Match)", 1), ("Enabled (Inside Target)", 2)
|
||||
])
|
||||
intPicker("CPU CLUT Render", selection: $settings.cpuClutRender, shared: SettingsOptions.cpuClutRender)
|
||||
intPicker("GPU Target CLUT", selection: $settings.gpuTargetClut, shared: SettingsOptions.gpuTargetClut)
|
||||
intPicker("Bilinear Upscale", selection: $settings.bilinearUpscaleHack, options: [
|
||||
("Automatic", 0), ("Force Bilinear", 1), ("Force Nearest", 2)
|
||||
])
|
||||
ClampedIntField(title: settings.localized("CPU Sprite Render BW"), value: $settings.cpuSpriteRenderBw, range: 0...10)
|
||||
ClampedIntField(title: settings.localized("CPU Sprite Render Level"), value: $settings.cpuSpriteRenderLevel, range: 0...2)
|
||||
intPicker("Max Anisotropy", selection: $settings.maxAnisotropy, options: [
|
||||
("Off", 0), ("2x", 2), ("4x", 4), ("8x", 8), ("16x", 16)
|
||||
])
|
||||
intPicker("Hardware Download Mode", selection: $settings.hardwareDownloadMode, options: [
|
||||
("Enabled", 0), ("Force Full", 1), ("No Readbacks", 2), ("Unsynchronized", 3), ("Disabled", 4)
|
||||
])
|
||||
intPicker("TV/CRT Shader", selection: $settings.tvShader, options: [
|
||||
("Off", 0), ("Scanline", 1), ("Diagonal", 2), ("Tri", 3), ("Wave", 4), ("Lottes", 5), ("4xRGSS", 6), ("NxAGSS", 7)
|
||||
])
|
||||
intPicker("Max Anisotropy", selection: $settings.maxAnisotropy, shared: SettingsOptions.maxAnisotropy)
|
||||
intPicker("Hardware Download Mode", selection: $settings.hardwareDownloadMode, shared: SettingsOptions.hardwareDownloadMode)
|
||||
intPicker("TV/CRT Shader", selection: $settings.tvShader, shared: SettingsOptions.tvShader)
|
||||
|
||||
ForEach(SettingsStore.gsBoolHackOptions) { option in
|
||||
Toggle(settings.localized(option.label), isOn: Binding(
|
||||
@@ -538,6 +524,11 @@ struct GraphicsSettingsView: View {
|
||||
}
|
||||
}
|
||||
|
||||
/// Same picker over a shared `SettingsOptions` list, which the per-game tabs read too.
|
||||
private func intPicker(_ title: String, selection: Binding<Int>, shared: [(id: Int, title: String)]) -> some View {
|
||||
intPicker(title, selection: selection, options: shared.map { ($0.title, $0.id) })
|
||||
}
|
||||
|
||||
/// Labeled 1–100 percent slider used by Shade Boost.
|
||||
@ViewBuilder
|
||||
private func percentSlider(_ title: String, value: Binding<Int>) -> some View {
|
||||
|
||||
@@ -17,9 +17,9 @@ struct FixesTab: View {
|
||||
PerGameTab(title: settings.localized("Fixes & Compatibility")) {
|
||||
Section {
|
||||
Picker(settings.localized("Renderer"), selection: $perGameRenderer) {
|
||||
Text(settings.localized("Use Global")).tag(-1)
|
||||
Text(settings.localized("Metal (Hardware)")).tag(17)
|
||||
Text(settings.localized("Software")).tag(13)
|
||||
ForEach(SettingsOptions.withUseGlobal(SettingsOptions.renderer), id: \.id) { option in
|
||||
Text(settings.localized(option.title)).tag(option.id)
|
||||
}
|
||||
}
|
||||
.disabled(!enabled)
|
||||
Text(settings.localized("Software Renderer is much slower but can fix games that break on Metal. It applies the next time this game boots."))
|
||||
|
||||
@@ -71,53 +71,16 @@ struct GraphicsTab: View {
|
||||
|
||||
// MARK: Static option tables (moved from the panel)
|
||||
|
||||
private struct PickerOption: Identifiable {
|
||||
let id: Int
|
||||
let title: String
|
||||
}
|
||||
|
||||
private static let useGlobalSentinel = -1
|
||||
private static let upscaleUseGlobalSentinel: Float = -1.0
|
||||
private static let aspectUseGlobalSentinel = ""
|
||||
private static let trilinearUseGlobalSentinelLocal = Int(Int32.min)
|
||||
|
||||
// Tags are GSInterlaceMode values (Config.h). The old table was shifted by one from index 1 up,
|
||||
// so every label named the mode below it and Adaptive was unreachable. Worse, "Adaptive
|
||||
// (Default)" was really Blend BFF, which is how it ended up as the per-game fallback.
|
||||
private static let deinterlaceOptions = [
|
||||
PickerOption(id: 0, title: "Automatic (Default)"),
|
||||
PickerOption(id: 1, title: "Off (No Deinterlacing)"),
|
||||
PickerOption(id: 2, title: "Weave (TFF)"),
|
||||
PickerOption(id: 3, title: "Weave (BFF)"),
|
||||
PickerOption(id: 4, title: "Bob (TFF)"),
|
||||
PickerOption(id: 5, title: "Bob (BFF)"),
|
||||
PickerOption(id: 6, title: "Blend (TFF)"),
|
||||
PickerOption(id: 7, title: "Blend (BFF)"),
|
||||
PickerOption(id: 8, title: "Adaptive (TFF)"),
|
||||
PickerOption(id: 9, title: "Adaptive (BFF)")
|
||||
]
|
||||
private static let trilinearFilteringOptions = [
|
||||
PickerOption(id: trilinearUseGlobalSentinelLocal, title: "Use Global"),
|
||||
PickerOption(id: -1, title: "Automatic / Default"),
|
||||
PickerOption(id: 0, title: "Off"),
|
||||
PickerOption(id: 1, title: "PS2"),
|
||||
PickerOption(id: 2, title: "Forced")
|
||||
]
|
||||
private static let halfPixelOffsetOptions = [
|
||||
PickerOption(id: useGlobalSentinel, title: "Use Global"),
|
||||
PickerOption(id: 0, title: "Off"),
|
||||
PickerOption(id: 1, title: "Normal / Vertex"),
|
||||
PickerOption(id: 2, title: "Special / Texture"),
|
||||
PickerOption(id: 3, title: "Special / Texture Aggressive"),
|
||||
PickerOption(id: 4, title: "Align to Native"),
|
||||
PickerOption(id: 5, title: "Align to Native + Texture Offset")
|
||||
]
|
||||
private static let roundSpriteOptions = [
|
||||
PickerOption(id: useGlobalSentinel, title: "Use Global"),
|
||||
PickerOption(id: 0, title: "Off"),
|
||||
PickerOption(id: 1, title: "Half"),
|
||||
PickerOption(id: 2, title: "Full")
|
||||
]
|
||||
// Trilinear needs Int32.min rather than -1, because -1 is a real TriFiltering value.
|
||||
private static let trilinearFilteringOptions =
|
||||
[(id: trilinearUseGlobalSentinelLocal, title: "Use Global")] + SettingsOptions.trilinearFiltering
|
||||
private static let halfPixelOffsetOptions = SettingsOptions.withUseGlobal(SettingsOptions.halfPixelOffset)
|
||||
private static let roundSpriteOptions = SettingsOptions.withUseGlobal(SettingsOptions.roundSprite)
|
||||
|
||||
private static let aspectRatioOptions: [(id: String, title: String)] = [
|
||||
("Auto 4:3/3:2", "Auto 4:3 / 3:2"),
|
||||
@@ -196,8 +159,7 @@ struct GraphicsTab: View {
|
||||
.disabled(!enabled)
|
||||
|
||||
Picker(settings.localized("Deinterlace"), selection: $interlaceMode) {
|
||||
Text(settings.localized("Use Global")).tag(Self.useGlobalSentinel)
|
||||
ForEach(Self.deinterlaceOptions) { option in
|
||||
ForEach(SettingsOptions.withUseGlobal(SettingsOptions.deinterlace), id: \.id) { option in
|
||||
Text(settings.localized(option.title)).tag(option.id)
|
||||
}
|
||||
}
|
||||
@@ -233,13 +195,9 @@ struct GraphicsTab: View {
|
||||
.disabled(!enabled)
|
||||
|
||||
Picker(settings.localized("TV/CRT Shader"), selection: $perGameTVShader) {
|
||||
Text(settings.localized("Use Global")).tag(-1)
|
||||
Text(settings.localized("Off")).tag(0)
|
||||
Text(settings.localized("Scanline")).tag(1)
|
||||
Text(settings.localized("Diagonal")).tag(2)
|
||||
Text(settings.localized("Tri")).tag(3)
|
||||
Text(settings.localized("Wave")).tag(4)
|
||||
Text(settings.localized("Lottes")).tag(5)
|
||||
ForEach(SettingsOptions.withUseGlobal(SettingsOptions.tvShader), id: \.id) { option in
|
||||
Text(settings.localized(option.title)).tag(option.id)
|
||||
}
|
||||
}
|
||||
.disabled(!enabled)
|
||||
Text(settings.localized("Scanline and CRT effects are subtle on high-resolution displays and are more visible at a lower Internal Resolution."))
|
||||
@@ -328,7 +286,7 @@ struct GraphicsTab: View {
|
||||
}
|
||||
|
||||
Picker(settings.localized("Trilinear Filtering"), selection: $trilinearFiltering) {
|
||||
ForEach(Self.trilinearFilteringOptions) { option in
|
||||
ForEach(Self.trilinearFilteringOptions, id: \.id) { option in
|
||||
Text(settings.localized(option.title)).tag(option.id)
|
||||
}
|
||||
}
|
||||
@@ -341,14 +299,14 @@ struct GraphicsTab: View {
|
||||
}
|
||||
|
||||
Picker(settings.localized("Half-pixel Offset"), selection: $halfPixelOffset) {
|
||||
ForEach(Self.halfPixelOffsetOptions) { option in
|
||||
ForEach(Self.halfPixelOffsetOptions, id: \.id) { option in
|
||||
Text(settings.localized(option.title)).tag(option.id)
|
||||
}
|
||||
}
|
||||
.disabled(!manualAdvancedHacksEnabled)
|
||||
|
||||
Picker(settings.localized("Round Sprite"), selection: $roundSprite) {
|
||||
ForEach(Self.roundSpriteOptions) { option in
|
||||
ForEach(Self.roundSpriteOptions, id: \.id) { option in
|
||||
Text(settings.localized(option.title)).tag(option.id)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,82 @@
|
||||
// SettingsOptions.swift — option lists shared by the global settings screens and the per-game tabs.
|
||||
// SPDX-License-Identifier: GPL-3.0+
|
||||
|
||||
import Foundation
|
||||
|
||||
/// One list per setting, so a global screen and its per-game tab cannot say different things.
|
||||
/// UpscaleOptions did this for internal resolution after its two copies drifted; these are the rest.
|
||||
///
|
||||
/// Ids are the values the core stores. Titles use the global screen's wording, which is what the
|
||||
/// translation tables already carry.
|
||||
enum SettingsOptions {
|
||||
/// Prefix for a per-game picker: -1 means inherit rather than override.
|
||||
static let useGlobalID = -1
|
||||
|
||||
static func withUseGlobal(_ options: [(id: Int, title: String)],
|
||||
title: String = "Use Global") -> [(id: Int, title: String)] {
|
||||
[(id: useGlobalID, title: title)] + options
|
||||
}
|
||||
|
||||
// Tags are GSInterlaceMode (pcsx2/Config.h). Automatic lets the game's own GameDB deinterlace
|
||||
// fix apply; anything else suppresses it.
|
||||
static let deinterlace: [(id: Int, title: String)] = [
|
||||
(0, "Automatic (Default)"), (1, "Off (No Deinterlacing)"),
|
||||
(2, "Weave (TFF)"), (3, "Weave (BFF)"),
|
||||
(4, "Bob (TFF)"), (5, "Bob (BFF)"),
|
||||
(6, "Blend (TFF)"), (7, "Blend (BFF)"),
|
||||
(8, "Adaptive (TFF)"), (9, "Adaptive (BFF)")
|
||||
]
|
||||
|
||||
static let tvShader: [(id: Int, title: String)] = [
|
||||
(0, "Off"), (1, "Scanline"), (2, "Diagonal"), (3, "Tri"),
|
||||
(4, "Wave"), (5, "Lottes"), (6, "4xRGSS"), (7, "NxAGSS")
|
||||
]
|
||||
|
||||
// Catalyst does not ship these, and both screens have to hide them together or the per-game
|
||||
// tab offers a renderer the global one denies.
|
||||
static let renderer: [(id: Int, title: String)] = {
|
||||
var options: [(id: Int, title: String)] = [(17, "Metal (Hardware)")]
|
||||
#if !targetEnvironment(macCatalyst)
|
||||
options.append((13, "Software"))
|
||||
options.append((11, "Null (No Output)"))
|
||||
#endif
|
||||
return options
|
||||
}()
|
||||
|
||||
static let maxAnisotropy: [(id: Int, title: String)] = [
|
||||
(0, "Off"), (2, "2x"), (4, "4x"), (8, "8x"), (16, "16x")
|
||||
]
|
||||
|
||||
static let hardwareDownloadMode: [(id: Int, title: String)] = [
|
||||
(0, "Enabled"), (1, "Force Full"), (2, "No Readbacks"),
|
||||
(3, "Unsynchronized"), (4, "Disabled")
|
||||
]
|
||||
|
||||
static let cpuClutRender: [(id: Int, title: String)] = [
|
||||
(0, "Disabled"), (1, "Normal"), (2, "Aggressive")
|
||||
]
|
||||
|
||||
static let gpuTargetClut: [(id: Int, title: String)] = [
|
||||
(0, "Off"), (1, "Enabled (Exact Match)"), (2, "Enabled (Inside Target)")
|
||||
]
|
||||
|
||||
static let textureInsideRT: [(id: Int, title: String)] = [
|
||||
(0, "Off"), (1, "Inside Targets"), (2, "Merge Targets")
|
||||
]
|
||||
|
||||
// -1 is a real TriFiltering value (Automatic), which is why the per-game "use global" marker for
|
||||
// this one has to be Int32.min rather than the usual -1.
|
||||
static let trilinearFiltering: [(id: Int, title: String)] = [
|
||||
(-1, "Automatic / Default"), (0, "Off"), (1, "PS2"), (2, "Forced")
|
||||
]
|
||||
|
||||
static let halfPixelOffset: [(id: Int, title: String)] = [
|
||||
(0, "Off"), (1, "Normal / Vertex"), (2, "Special / Texture"),
|
||||
(3, "Special / Texture Aggressive"), (4, "Align to Native"),
|
||||
(5, "Align to Native + Texture Offset")
|
||||
]
|
||||
|
||||
static let roundSprite: [(id: Int, title: String)] = [
|
||||
(0, "Off"), (1, "Half"), (2, "Full")
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user