Merge branch 'stenzek:master' into master

This commit is contained in:
Daniel Nylander 2024-12-15 13:05:24 +01:00 committed by GitHub
commit c64eda8fea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 3863 additions and 2167 deletions

View File

@ -5299,6 +5299,8 @@ SLPS-00173:
name: "Alnam no Kiba - Juuzoku Juuni Shinto Densetsu (Japan)"
controllers:
- DigitalController
codes:
- HASH-9D5D552F17E0F7E8
metadata:
publisher: "Right Stuff"
developer: "Right Stuff"
@ -30515,8 +30517,9 @@ SCUS-94244:
- AnalogController
- DigitalController
traits:
- ForceRoundTextureCoordinates
- DisablePGXPColorCorrection
- ForceRoundTextureCoordinates # Fixes alignment of flipped textures when upscaling.
- DisablePGXPColorCorrection # Shadows break with perspective correct colour.
- ForceRecompilerICache # Sound effects during intro are lost if the CPU is too fast.
metadata:
publisher: "Sony"
developer: "Naughty Dog, Inc"
@ -30624,8 +30627,9 @@ SCPS-10073:
- AnalogController
- DigitalController
traits:
- ForceRoundTextureCoordinates
- DisablePGXPColorCorrection
- ForceRoundTextureCoordinates # Fixes alignment of flipped textures when upscaling.
- DisablePGXPColorCorrection # Shadows break with perspective correct colour.
- ForceRecompilerICache # Sound effects during intro are lost if the CPU is too fast.
codes:
- SCPS-10073
- SCPS-91164
@ -30652,7 +30656,9 @@ SCPS-45350:
- AnalogController
- DigitalController
traits:
- ForceRoundTextureCoordinates
- ForceRoundTextureCoordinates # Fixes alignment of flipped textures when upscaling.
- DisablePGXPColorCorrection # Shadows break with perspective correct colour.
- ForceRecompilerICache # Sound effects during intro are lost if the CPU is too fast.
metadata:
publisher: "Sony"
developer: "Naughty Dog, Inc"
@ -30675,8 +30681,9 @@ SCES-01420:
- AnalogController
- DigitalController
traits:
- ForceRoundTextureCoordinates
- DisablePGXPColorCorrection
- ForceRoundTextureCoordinates # Fixes alignment of flipped textures when upscaling.
- DisablePGXPColorCorrection # Shadows break with perspective correct colour.
- ForceRecompilerICache # Sound effects during intro are lost if the CPU is too fast.
metadata:
publisher: "Sony"
developer: "Naughty Dog, Inc"
@ -55020,6 +55027,9 @@ SLES-02166:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55048,6 +55058,9 @@ SLES-12166:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55076,6 +55089,9 @@ SLES-22166:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55104,6 +55120,9 @@ SLES-32166:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55132,6 +55151,9 @@ SLES-02167:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55158,6 +55180,9 @@ SLES-12167:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55184,6 +55209,9 @@ SLES-22167:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55210,6 +55238,9 @@ SLES-32167:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55236,6 +55267,9 @@ SLES-02168:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55262,6 +55296,9 @@ SLES-12168:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55288,6 +55325,9 @@ SLES-22168:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55314,6 +55354,9 @@ SLES-32168:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55344,6 +55387,9 @@ SLUS-00920:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55374,6 +55420,9 @@ SLUS-01056:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55404,6 +55453,9 @@ SLUS-01057:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55434,6 +55486,9 @@ SLUS-01058:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
- DisableWidescreen # FMV backgrounds.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55464,6 +55519,8 @@ SLES-03386:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc"
@ -55492,6 +55549,8 @@ SLES-13386:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc"
@ -55520,6 +55579,8 @@ SLES-23386:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc"
@ -55548,6 +55609,8 @@ SLES-33386:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc"
@ -55579,6 +55642,8 @@ SLUS-01266:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55605,6 +55670,8 @@ SLUS-01275:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55631,6 +55698,8 @@ SLUS-01276:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -55657,6 +55726,8 @@ SLUS-01277:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -71969,6 +72040,8 @@ SLPS-03340:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -71995,6 +72068,8 @@ SLPS-03341:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -72021,6 +72096,8 @@ SLPS-03342:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -72047,6 +72124,8 @@ SLPS-03343:
controllers:
- AnalogController
- DigitalController
traits:
- DisableMultitap # Sends multitap read command, can't handle response.
metadata:
publisher: "Eidos Interactive"
developer: "Kronos Digital Entertainment, Inc."
@ -171483,6 +171562,7 @@ SLPM-86036:
multitap: false
linkCable: false
SCPS-45451:
# NOTE: No redump records apparently. Unclear if the executable name is different.
name: "Tokimeki Memorial 2"
controllers:
- AnalogController
@ -171512,11 +171592,24 @@ SLPM-86355:
discSet:
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-86355
- SLPM-86356
- SLPM-86357
- SLPM-86358
- SLPM-86359
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
controllers:
- AnalogController
- DigitalController
@ -171537,13 +171630,26 @@ SLPM-86355:
SLPM-86350:
name: "Tokimeki Memorial 2 (Japan) (Disc 1) (Limited Box)"
discSet:
name: "Tokimeki Memorial 2 (Japan) (Limited Box)"
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-86350
- SLPM-86351
- SLPM-86352
- SLPM-86353
- SLPM-86354
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
controllers:
- AnalogController
- DigitalController
@ -171566,11 +171672,24 @@ SLPM-86356:
discSet:
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-86355
- SLPM-86356
- SLPM-86357
- SLPM-86358
- SLPM-86359
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
controllers:
- AnalogController
- DigitalController
@ -171591,13 +171710,26 @@ SLPM-86356:
SLPM-86351:
name: "Tokimeki Memorial 2 (Japan) (Disc 2) (Limited Box)"
discSet:
name: "Tokimeki Memorial 2 (Japan) (Limited Box)"
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-86350
- SLPM-86351
- SLPM-86352
- SLPM-86353
- SLPM-86354
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
controllers:
- AnalogController
- DigitalController
@ -171620,11 +171752,24 @@ SLPM-86357:
discSet:
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-86355
- SLPM-86356
- SLPM-86357
- SLPM-86358
- SLPM-86359
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
controllers:
- AnalogController
- DigitalController
@ -171645,13 +171790,26 @@ SLPM-86357:
SLPM-86352:
name: "Tokimeki Memorial 2 (Japan) (Disc 3) (Limited Box)"
discSet:
name: "Tokimeki Memorial 2 (Japan) (Limited Box)"
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-86350
- SLPM-86351
- SLPM-86352
- SLPM-86353
- SLPM-86354
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
controllers:
- AnalogController
- DigitalController
@ -171674,11 +171832,24 @@ SLPM-86358:
discSet:
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-86355
- SLPM-86356
- SLPM-86357
- SLPM-86358
- SLPM-86359
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
controllers:
- AnalogController
- DigitalController
@ -171699,13 +171870,26 @@ SLPM-86358:
SLPM-86353:
name: "Tokimeki Memorial 2 (Japan) (Disc 4) (Limited Box)"
discSet:
name: "Tokimeki Memorial 2 (Japan) (Limited Box)"
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-86350
- SLPM-86351
- SLPM-86352
- SLPM-86353
- SLPM-86354
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
controllers:
- AnalogController
- DigitalController
@ -171728,11 +171912,24 @@ SLPM-86359:
discSet:
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-86355
- SLPM-86356
- SLPM-86357
- SLPM-86358
- SLPM-86359
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
controllers:
- AnalogController
- DigitalController
@ -171753,13 +171950,26 @@ SLPM-86359:
SLPM-86354:
name: "Tokimeki Memorial 2 (Japan) (Disc 5) (Limited Box)"
discSet:
name: "Tokimeki Memorial 2 (Japan) (Limited Box)"
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-86350
- SLPM-86351
- SLPM-86352
- SLPM-86353
- SLPM-86354
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
controllers:
- AnalogController
- DigitalController
@ -171903,27 +172113,72 @@ SLPM-86753:
SLPM-80527:
name: "Tokimeki Memorial 2 Emotional Voice System Append Disc (Japan) (Disc 1) (Minadzuki - Kotobuki - Sakura)"
discSet:
name: "Tokimeki Memorial 2 Emotional Voice System Append Disc (Japan)"
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-80527
- SLPM-80544
- SLPM-80550
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
SLPM-80544:
name: "Tokimeki Memorial 2 Emotional Voice System Append Disc (Japan) (Disc 2) (Akai - Ichimonji - Yae)"
discSet:
name: "Tokimeki Memorial 2 Emotional Voice System Append Disc (Japan)"
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-80527
- SLPM-80544
- SLPM-80550
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
SLPM-80550:
name: "Tokimeki Memorial 2 Emotional Voice System Append Disc (Japan) (Disc 3) (Shirayuki - Ijuin - Nozaki)"
discSet:
name: "Tokimeki Memorial 2 Emotional Voice System Append Disc (Japan)"
name: "Tokimeki Memorial 2 (Japan)"
serials:
- SLPM-80527
- SLPM-80544
- SLPM-80550
- SLPM-86355 # Original Disc 1
- SLPM-86356 # Original Disc 2
- SLPM-86357 # Original Disc 3
- SLPM-86358 # Original Disc 4
- SLPM-86359 # Original Disc 5
- SLPM-86350 # Limited Box Disc 1
- SLPM-86351 # Limited Box Disc 2
- SLPM-86352 # Limited Box Disc 3
- SLPM-86353 # Limited Box Disc 4
- SLPM-86354 # Limited Box Disc 5
- SCPS-45451 # Undumped Revision Disc 1
- SCPS-45452 # Undumped Revision Disc 2
- SCPS-45453 # Undumped Revision Disc 3
- SCPS-45454 # Undumped Revision Disc 4
- SCPS-45455 # Undumped Revision Disc 5
- SLPM-80527 # Append Disc 1
- SLPM-80544 # Append Disc 2
- SLPM-80550 # Append Disc 3
SLPM-86549:
name: "Tokimeki Memorial 2 Substories - Dancing Summer Vacation (Japan) (Disc 1)"
discSet:
@ -172118,31 +172373,6 @@ SLPM-87308:
vibration: true
multitap: false
linkCable: false
SLPM-86723:
name: "Tokimeki Memorial 2 [Konami the Best]"
controllers:
- AnalogController
- DigitalController
codes:
- SLPM-86723
- SLPM-86724
- SLPM-86725
- SLPM-86726
- SLPM-86727
metadata:
publisher: "Konami"
developer: "KCET"
releaseDate: "2001-01-18"
genre: "Dating / Adventure / Mini Games"
languages:
- Japanese
minPlayers: 1
maxPlayers: 1
minBlocks: 3
maxBlocks: 3
vibration: true
multitap: false
linkCable: false
SLPM-86039:
name: "Tokimeki Memorial Drama Series Vol. 1 - Nijiiro no Seishun (Japan)"
controllers:

View File

@ -50,6 +50,7 @@
X(PlatformMisc) \
X(PostProcessing) \
X(ProgressCallback) \
X(PIO) \
X(ReShadeFXShader) \
X(Recompiler) \
X(SDL) \

View File

@ -106,6 +106,8 @@ add_library(core
pcdrv.h
performance_counters.cpp
performance_counters.h
pio.cpp
pio.h
playstation_mouse.cpp
playstation_mouse.h
psf_loader.cpp

View File

@ -14,6 +14,7 @@
#include "interrupt_controller.h"
#include "mdec.h"
#include "pad.h"
#include "pio.h"
#include "psf_loader.h"
#include "settings.h"
#include "sio.h"
@ -43,8 +44,6 @@
LOG_CHANNEL(Bus);
// TODO: Get rid of page code bits, instead use page faults to track SMC.
// Exports for external debugger access
#ifndef __ANDROID__
namespace Exports {
@ -139,8 +138,6 @@ std::array<TickCount, 3> g_bios_access_time = {};
std::array<TickCount, 3> g_cdrom_access_time = {};
std::array<TickCount, 3> g_spu_access_time = {};
static std::vector<u8> s_exp1_rom;
static MEMCTRL s_MEMCTRL = {};
static RAM_SIZE_REG s_RAM_SIZE = {};
@ -518,6 +515,8 @@ void Bus::RecalculateMemoryTimings()
CalculateMemoryTiming(s_MEMCTRL.cdrom_delay_size, s_MEMCTRL.common_delay);
std::tie(g_spu_access_time[0], g_spu_access_time[1], g_spu_access_time[2]) =
CalculateMemoryTiming(s_MEMCTRL.spu_delay_size, s_MEMCTRL.common_delay);
std::tie(g_exp1_access_time[0], g_exp1_access_time[1], g_exp1_access_time[2]) =
CalculateMemoryTiming(s_MEMCTRL.exp1_delay_size, s_MEMCTRL.common_delay);
TRACE_LOG("BIOS Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
s_MEMCTRL.bios_delay_size.data_bus_16bit ? 16 : 8, g_bios_access_time[0] + 1, g_bios_access_time[1] + 1,
@ -528,6 +527,9 @@ void Bus::RecalculateMemoryTimings()
TRACE_LOG("SPU Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
s_MEMCTRL.spu_delay_size.data_bus_16bit ? 16 : 8, g_spu_access_time[0] + 1, g_spu_access_time[1] + 1,
g_spu_access_time[2] + 1);
TRACE_LOG("EXP1 Memory Timing: {} bit bus, byte={}, halfword={}, word={}",
s_MEMCTRL.spu_delay_size.data_bus_16bit ? 16 : 8, g_spu_access_time[0] + 1, g_spu_access_time[1] + 1,
g_spu_access_time[2] + 1);
}
void* Bus::GetFastmemBase(bool isc)
@ -926,11 +928,6 @@ std::optional<PhysicalMemoryAddress> Bus::SearchMemory(PhysicalMemoryAddress sta
return std::nullopt;
}
void Bus::SetExpansionROM(std::vector<u8> data)
{
s_exp1_rom = std::move(data);
}
void Bus::AddTTYCharacter(char ch)
{
if (ch == '\r')
@ -1476,10 +1473,10 @@ template<MemoryAccessSize size>
u32 Bus::ICacheReadHandler(VirtualMemoryAddress address)
{
const u32 line = CPU::GetICacheLine(address);
const u8* line_data = &CPU::g_state.icache_data[line * CPU::ICACHE_LINE_SIZE];
const u32* line_data = &CPU::g_state.icache_data[line * CPU::ICACHE_WORDS_PER_LINE];
const u32 offset = CPU::GetICacheLineOffset(address);
u32 result;
std::memcpy(&result, &line_data[offset], sizeof(result));
std::memcpy(&result, reinterpret_cast<const u8*>(line_data) + offset, sizeof(result));
return result;
}
@ -1487,14 +1484,15 @@ template<MemoryAccessSize size>
void Bus::ICacheWriteHandler(VirtualMemoryAddress address, u32 value)
{
const u32 line = CPU::GetICacheLine(address);
u32* line_data = &CPU::g_state.icache_data[line * CPU::ICACHE_WORDS_PER_LINE];
const u32 offset = CPU::GetICacheLineOffset(address);
CPU::g_state.icache_tags[line] = CPU::GetICacheTagForAddress(address) | CPU::ICACHE_INVALID_BITS;
if constexpr (size == MemoryAccessSize::Byte)
std::memcpy(&CPU::g_state.icache_data[line * CPU::ICACHE_LINE_SIZE + offset], &value, sizeof(u8));
std::memcpy(reinterpret_cast<u8*>(line_data) + offset, &value, sizeof(u8));
else if constexpr (size == MemoryAccessSize::HalfWord)
std::memcpy(&CPU::g_state.icache_data[line * CPU::ICACHE_LINE_SIZE + offset], &value, sizeof(u16));
std::memcpy(reinterpret_cast<u8*>(line_data) + offset, &value, sizeof(u16));
else
std::memcpy(&CPU::g_state.icache_data[line * CPU::ICACHE_LINE_SIZE + offset], &value, sizeof(u32));
std::memcpy(reinterpret_cast<u8*>(line_data) + offset, &value, sizeof(u32));
}
template<MemoryAccessSize size>
@ -1502,53 +1500,49 @@ u32 Bus::EXP1ReadHandler(VirtualMemoryAddress address)
{
BUS_CYCLES(g_exp1_access_time[static_cast<u32>(size)]);
// TODO: auto-increment should be handled elsewhere...
const u32 offset = address & EXP1_MASK;
u32 value;
if (s_exp1_rom.empty())
u32 ret;
if constexpr (size >= MemoryAccessSize::HalfWord)
{
// EXP1 not present.
value = UINT32_C(0xFFFFFFFF);
}
else if (offset == 0x20018)
{
// Bit 0 - Action Replay On/Off
value = UINT32_C(1);
ret = g_pio_device->ReadHandler(offset);
ret |= ZeroExtend32(g_pio_device->ReadHandler(offset + 1)) << 8;
if constexpr (size == MemoryAccessSize::Word)
{
ret |= ZeroExtend32(g_pio_device->ReadHandler(offset + 2)) << 16;
ret |= ZeroExtend32(g_pio_device->ReadHandler(offset + 3)) << 24;
}
}
else
{
const u32 transfer_size = u32(1) << static_cast<u32>(size);
if ((offset + transfer_size) > s_exp1_rom.size())
{
value = UINT32_C(0);
}
else
{
if constexpr (size == MemoryAccessSize::Byte)
{
value = ZeroExtend32(s_exp1_rom[offset]);
}
else if constexpr (size == MemoryAccessSize::HalfWord)
{
u16 halfword;
std::memcpy(&halfword, &s_exp1_rom[offset], sizeof(halfword));
value = ZeroExtend32(halfword);
}
else
{
std::memcpy(&value, &s_exp1_rom[offset], sizeof(value));
}
// Log_DevPrintf("EXP1 read: 0x%08X -> 0x%08X", address, value);
}
ret = ZeroExtend32(g_pio_device->ReadHandler(offset));
}
return value;
return ret;
}
template<MemoryAccessSize size>
void Bus::EXP1WriteHandler(VirtualMemoryAddress address, u32 value)
{
WARNING_LOG("EXP1 write: 0x{:08X} <- 0x{:08X}", address, value);
// TODO: auto-increment should be handled elsewhere...
const u32 offset = address & EXP1_MASK;
if constexpr (size >= MemoryAccessSize::HalfWord)
{
g_pio_device->WriteHandler(offset, Truncate8(value));
g_pio_device->WriteHandler(offset + 1, Truncate8(value >> 8));
if constexpr (size == MemoryAccessSize::Word)
{
g_pio_device->WriteHandler(offset + 2, Truncate8(value >> 16));
g_pio_device->WriteHandler(offset + 3, Truncate8(value >> 24));
}
}
else
{
g_pio_device->WriteHandler(offset, Truncate8(value));
}
}
template<MemoryAccessSize size>

View File

@ -5,15 +5,11 @@
#include "types.h"
#include "common/bitfield.h"
#include <array>
#include <bitset>
#include <optional>
#include <span>
#include <string>
#include <string_view>
#include <vector>
class Error;
@ -148,8 +144,6 @@ void* GetFastmemBase(bool isc);
void RemapFastmemViews();
bool CanUseFastmemForAddress(VirtualMemoryAddress address);
void SetExpansionROM(std::vector<u8> data);
extern std::bitset<RAM_8MB_CODE_PAGE_COUNT> g_ram_code_bits;
extern u8* g_ram; // 2MB-8MB RAM
extern u8* g_unprotected_ram; // RAM without page protection, use for debugger access.

View File

@ -74,6 +74,8 @@ enum : u32
static constexpr u8 INTERRUPT_REGISTER_MASK = 0x1F;
static constexpr TickCount MIN_SEEK_TICKS = 30000;
enum class Interrupt : u8
{
DataReady = 0x01,
@ -1557,10 +1559,8 @@ u32 CDROM::GetSectorsPerTrack(CDImage::LBA lba)
TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change)
{
static constexpr TickCount MIN_TICKS = 30000;
if (g_settings.cdrom_seek_speedup == 0)
return MIN_TICKS;
return System::ScaleTicksToOverclock(MIN_SEEK_TICKS);
u32 ticks = 0;
@ -1630,7 +1630,7 @@ TickCount CDROM::GetTicksForSeek(CDImage::LBA new_lba, bool ignore_speed_change)
}
if (g_settings.cdrom_seek_speedup > 1)
ticks = std::max<u32>(ticks / g_settings.cdrom_seek_speedup, MIN_TICKS);
ticks = std::max<u32>(ticks / g_settings.cdrom_seek_speedup, MIN_SEEK_TICKS);
if (s_state.drive_state == DriveState::ChangingSpeedOrTOCRead && !ignore_speed_change)
{
@ -2724,6 +2724,8 @@ void CDROM::BeginReading(TickCount ticks_late /* = 0 */, bool after_seek /* = fa
s_state.drive_event.Schedule(first_sector_ticks);
s_state.requested_lba = s_state.current_lba;
s_state.seek_start_lba = 0;
s_state.seek_end_lba = 0;
s_reader.QueueReadSector(s_state.requested_lba);
}
@ -2789,7 +2791,23 @@ void CDROM::BeginSeeking(bool logical, bool read_after_seek, bool play_after_see
logical ? "logical" : "physical");
const CDImage::LBA seek_lba = s_state.setloc_position.ToLBA();
const TickCount seek_time = GetTicksForSeek(seek_lba, play_after_seek);
TickCount seek_time;
// Yay for edge cases. If we repeatedly send SeekL to the same target before a new sector is read, it should complete
// nearly instantly, because it's looking for a valid target of -2. See the note in CompleteSeek(). We gate this with
// the seek target in case another read happened in the interim. Test case: Resident Evil 3.
if (logical && !read_after_seek && s_state.current_subq_lba == (seek_lba - SUBQ_SECTOR_SKEW) &&
s_state.seek_end_lba == seek_lba &&
(System::GetGlobalTickCounter() - s_state.subq_lba_update_tick) < static_cast<GlobalTicks>(GetTicksForRead()))
{
DEV_COLOR_LOG(StrongCyan, "Completing seek instantly due to not passing target {}.",
LBAToMSFString(seek_lba - SUBQ_SECTOR_SKEW));
seek_time = MIN_SEEK_TICKS;
}
else
{
seek_time = GetTicksForSeek(seek_lba, play_after_seek);
}
ClearCommandSecondResponse();
ClearAsyncInterrupt();
@ -2843,7 +2861,6 @@ void CDROM::UpdateSubQPositionWhileSeeking()
current_lba, completed_frac);
s_state.last_subq_needs_update = (s_state.current_subq_lba != current_lba);
s_state.current_lba = current_lba; // TODO: This is probably wrong... hold position shouldn't change.
s_state.current_subq_lba = current_lba;
s_state.subq_lba_update_tick = System::GetGlobalTickCounter();
s_state.subq_lba_update_carry = 0;

View File

@ -274,6 +274,9 @@ bool Controller::InCircularDeadzone(float deadzone, float pos_x, float pos_y)
bool Controller::CanStartInAnalogMode(ControllerType ctype)
{
if (!g_settings.apply_compatibility_settings)
return true;
const GameDatabase::Entry* dbentry = System::GetGameDatabaseEntry();
if (!dbentry)
return false;

View File

@ -69,6 +69,7 @@
</ClCompile>
<ClCompile Include="cpu_pgxp.cpp" />
<ClCompile Include="performance_counters.cpp" />
<ClCompile Include="pio.cpp" />
<ClCompile Include="playstation_mouse.cpp" />
<ClCompile Include="psf_loader.cpp" />
<ClCompile Include="settings.cpp" />
@ -148,6 +149,7 @@
<ClInclude Include="pch.h" />
<ClInclude Include="cpu_pgxp.h" />
<ClInclude Include="performance_counters.h" />
<ClInclude Include="pio.h" />
<ClInclude Include="playstation_mouse.h" />
<ClInclude Include="psf_loader.h" />
<ClInclude Include="save_state_version.h" />

View File

@ -66,6 +66,7 @@
<ClCompile Include="cdrom_subq_replacement.cpp" />
<ClCompile Include="performance_counters.cpp" />
<ClCompile Include="jogcon.cpp" />
<ClCompile Include="pio.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="types.h" />
@ -141,6 +142,7 @@
<ClInclude Include="performance_counters.h" />
<ClInclude Include="system_private.h" />
<ClInclude Include="jogcon.h" />
<ClInclude Include="pio.h" />
</ItemGroup>
<ItemGroup>
<None Include="gpu_sw_rasterizer.inl" />

View File

@ -253,15 +253,15 @@ static constexpr LUTRangeList GetLUTRanges()
{
const LUTRangeList ranges = {{
{0x00000000, 0x00800000}, // RAM
{0x1F000000, 0x1F800000}, // EXP1
{0x1F000000, 0x1F060000}, // EXP1
{0x1FC00000, 0x1FC80000}, // BIOS
{0x80000000, 0x80800000}, // RAM
{0x9F000000, 0x9F800000}, // EXP1
{0x9F000000, 0x9F060000}, // EXP1
{0x9FC00000, 0x9FC80000}, // BIOS
{0xA0000000, 0xA0800000}, // RAM
{0xBF000000, 0xBF800000}, // EXP1
{0xBF000000, 0xBF060000}, // EXP1
{0xBFC00000, 0xBFC80000} // BIOS
}};
return ranges;
@ -752,11 +752,12 @@ template<PGXPMode pgxp_mode>
if (g_state.pending_ticks >= g_state.downcount) \
break;
for (;;)
{
if (g_state.pending_ticks >= g_state.downcount)
TimingEvents::RunEvents();
while (g_state.pending_ticks < g_state.downcount)
for (;;)
{
for (;;)
{
#if 0
LogCurrentState();
@ -830,6 +831,8 @@ template<PGXPMode pgxp_mode>
CHECK_DOWNCOUNT();
continue;
}
TimingEvents::RunEvents();
}
}
@ -1418,6 +1421,22 @@ const void* CPU::CodeCache::CreateBlockLink(Block* block, void* code, u32 newpc)
return dst;
}
const void* CPU::CodeCache::CreateSelfBlockLink(Block* block, void* code, const void* block_start)
{
const void* dst = g_dispatcher;
if (g_settings.cpu_recompiler_block_linking)
{
dst = block_start;
BlockLinkMap::iterator iter = s_block_links.emplace(block->pc, code);
DebugAssert(block->num_exit_links < MAX_BLOCK_EXIT_LINKS);
block->exit_links[block->num_exit_links++] = iter;
}
DEBUG_LOG("Self linking {} with dst pc {:08X} to {}", code, block->pc, dst);
return dst;
}
void CPU::CodeCache::BacklinkBlocks(u32 pc, const void* dst)
{
if (!g_settings.cpu_recompiler_block_linking)
@ -1673,7 +1692,8 @@ PageFaultHandler::HandlerResult CPU::CodeCache::HandleFastmemException(void* exc
// if we're writing to ram, let it go through a few times, and use manual block protection to sort it out
// TODO: path for manual protection to return back to read-only pages
if (is_write && !g_state.cop0_regs.sr.Isc && AddressInRAM(guest_address))
if (is_write && !g_state.cop0_regs.sr.Isc && GetSegmentForAddress(guest_address) != CPU::Segment::KSEG2 &&
AddressInRAM(guest_address))
{
DEV_LOG("Ignoring fault due to RAM write @ 0x{:08X}", guest_address);
InvalidateBlocksWithPageIndex(Bus::GetRAMCodePageIndex(guest_address));

View File

@ -239,6 +239,7 @@ const void* GetInterpretUncachedBlockFunction();
void CompileOrRevalidateBlock(u32 start_pc);
void DiscardAndRecompileBlock(u32 start_pc);
const void* CreateBlockLink(Block* from_block, void* code, u32 newpc);
const void* CreateSelfBlockLink(Block* block, void* code, const void* block_start);
void AddLoadStoreInfo(void* code_address, u32 code_size, u32 guest_pc, const void* thunk_address);
void AddLoadStoreInfo(void* code_address, u32 code_size, u32 guest_pc, u32 guest_block, TickCount cycles,

View File

@ -10,6 +10,7 @@
#include "gte.h"
#include "host.h"
#include "pcdrv.h"
#include "pio.h"
#include "settings.h"
#include "system.h"
#include "timing_event.h"
@ -85,7 +86,7 @@ template<PGXPMode pgxp_mode, bool debug>
static bool FetchInstruction();
static bool FetchInstructionForInterpreterFallback();
template<bool add_ticks, bool icache_read = false, u32 word_count = 1, bool raise_exceptions>
static bool DoInstructionRead(PhysicalMemoryAddress address, void* data);
static bool DoInstructionRead(PhysicalMemoryAddress address, u32* data);
template<MemoryAccessType type, MemoryAccessSize size>
static bool DoSafeMemoryAccess(VirtualMemoryAddress address, u32& value);
template<MemoryAccessType type, MemoryAccessSize size>
@ -2671,7 +2672,7 @@ void CPU::UpdateMemoryPointers()
}
template<bool add_ticks, bool icache_read, u32 word_count, bool raise_exceptions>
ALWAYS_INLINE_RELEASE bool CPU::DoInstructionRead(PhysicalMemoryAddress address, void* data)
ALWAYS_INLINE_RELEASE bool CPU::DoInstructionRead(PhysicalMemoryAddress address, u32* data)
{
using namespace Bus;
@ -2693,6 +2694,14 @@ ALWAYS_INLINE_RELEASE bool CPU::DoInstructionRead(PhysicalMemoryAddress address,
return true;
}
else if (address >= EXP1_BASE && address < (EXP1_BASE + EXP1_SIZE))
{
g_pio_device->CodeReadHandler(address & EXP1_MASK, data, word_count);
if constexpr (add_ticks)
g_state.pending_ticks += g_exp1_access_time[static_cast<u32>(MemoryAccessSize::Word)] * word_count;
return true;
}
else
{
if (raise_exceptions)
@ -2766,34 +2775,33 @@ void CPU::CheckAndUpdateICacheTags(u32 line_count)
u32 CPU::FillICache(VirtualMemoryAddress address)
{
const u32 line = GetICacheLine(address);
u8* line_data = &g_state.icache_data[line * ICACHE_LINE_SIZE];
const u32 line_word_offset = GetICacheLineWordOffset(address);
u32* const line_data = g_state.icache_data.data() + (line * ICACHE_WORDS_PER_LINE);
u32* const offset_line_data = line_data + line_word_offset;
u32 line_tag;
switch ((address >> 2) & 0x03u)
switch (line_word_offset)
{
case 0:
DoInstructionRead<true, true, 4, false>(address & ~(ICACHE_LINE_SIZE - 1u), line_data);
DoInstructionRead<true, true, 4, false>(address & ~(ICACHE_LINE_SIZE - 1u), offset_line_data);
line_tag = GetICacheTagForAddress(address);
break;
case 1:
DoInstructionRead<true, true, 3, false>(address & (~(ICACHE_LINE_SIZE - 1u) | 0x4), line_data + 0x4);
DoInstructionRead<true, true, 3, false>(address & (~(ICACHE_LINE_SIZE - 1u) | 0x4), offset_line_data);
line_tag = GetICacheTagForAddress(address) | 0x1;
break;
case 2:
DoInstructionRead<true, true, 2, false>(address & (~(ICACHE_LINE_SIZE - 1u) | 0x8), line_data + 0x8);
DoInstructionRead<true, true, 2, false>(address & (~(ICACHE_LINE_SIZE - 1u) | 0x8), offset_line_data);
line_tag = GetICacheTagForAddress(address) | 0x3;
break;
case 3:
default:
DoInstructionRead<true, true, 1, false>(address & (~(ICACHE_LINE_SIZE - 1u) | 0xC), line_data + 0xC);
DoInstructionRead<true, true, 1, false>(address & (~(ICACHE_LINE_SIZE - 1u) | 0xC), offset_line_data);
line_tag = GetICacheTagForAddress(address) | 0x7;
break;
}
g_state.icache_tags[line] = line_tag;
const u32 offset = GetICacheLineOffset(address);
u32 result;
std::memcpy(&result, &line_data[offset], sizeof(result));
return result;
g_state.icache_tags[line] = line_tag;
return offset_line_data[0];
}
void CPU::ClearICache()
@ -2806,11 +2814,9 @@ namespace CPU {
ALWAYS_INLINE_RELEASE static u32 ReadICache(VirtualMemoryAddress address)
{
const u32 line = GetICacheLine(address);
const u8* line_data = &g_state.icache_data[line * ICACHE_LINE_SIZE];
const u32 offset = GetICacheLineOffset(address);
u32 result;
std::memcpy(&result, &line_data[offset], sizeof(result));
return result;
const u32 line_word_offset = GetICacheLineWordOffset(address);
const u32* const line_data = g_state.icache_data.data() + (line * ICACHE_WORDS_PER_LINE);
return line_data[line_word_offset];
}
} // namespace CPU

View File

@ -32,7 +32,7 @@ enum : PhysicalMemoryAddress
ICACHE_SLOTS = ICACHE_SIZE / sizeof(u32),
ICACHE_LINE_SIZE = 16,
ICACHE_LINES = ICACHE_SIZE / ICACHE_LINE_SIZE,
ICACHE_SLOTS_PER_LINE = ICACHE_SLOTS / ICACHE_LINES,
ICACHE_WORDS_PER_LINE = ICACHE_SLOTS / ICACHE_LINES,
ICACHE_TAG_ADDRESS_MASK = 0xFFFFFFF0u,
ICACHE_INVALID_BITS = 0x0Fu,
};
@ -117,7 +117,7 @@ struct State
PGXPValue pgxp_gte[64] = {};
std::array<u32, ICACHE_LINES> icache_tags = {};
std::array<u8, ICACHE_SIZE> icache_data = {};
std::array<u32, ICACHE_LINES * ICACHE_WORDS_PER_LINE> icache_data = {};
std::array<u8, SCRATCHPAD_SIZE> scratchpad = {};

View File

@ -48,6 +48,10 @@ ALWAYS_INLINE static u32 GetICacheLineOffset(VirtualMemoryAddress address)
{
return (address & (ICACHE_LINE_SIZE - 1));
}
ALWAYS_INLINE static u32 GetICacheLineWordOffset(VirtualMemoryAddress address)
{
return (address >> 2) & 0x03u;
}
ALWAYS_INLINE static u32 GetICacheTagForAddress(VirtualMemoryAddress address)
{
return (address & ICACHE_TAG_ADDRESS_MASK);

View File

@ -1707,7 +1707,7 @@ void CPU::Recompiler::Recompiler::CompileLoadStoreTemplate(
{
// Get rid of physical aliases.
const u32 phys_spec_addr = VirtualAddressToPhysical(spec_addr.value());
if (phys_spec_addr >= VirtualAddressToPhysical(m_block->pc) &&
if (phys_spec_addr >= VirtualAddressToPhysical(m_compiler_pc) &&
phys_spec_addr < VirtualAddressToPhysical(m_block->pc + (m_block->size * sizeof(Instruction))))
{
WARNING_LOG("Instruction {:08X} speculatively writes to {:08X} inside block {:08X}-{:08X}. Truncating block.",

View File

@ -63,6 +63,8 @@ public:
static constexpr bool HAS_MEMORY_OPERANDS = false;
// A reasonable "maximum" number of bytes per instruction.
// Seems to hover around ~36-48 bytes without PGXP, and ~48-64 bytes with.
// Use an upper bound of 64 bytes to be safe.
static constexpr u32 MAX_NEAR_HOST_BYTES_PER_INSTRUCTION = 64;
static constexpr u32 MIN_CODE_RESERVE_FOR_BLOCK = 512;

View File

@ -178,8 +178,8 @@ void armEmitFarLoad(vixl::aarch32::Assembler* armAsm, const vixl::aarch32::Regis
armAsm->ldr(reg, vixl::aarch32::MemOperand(reg));
}
void armEmitFarStore(vixl::aarch32::Assembler* armAsm, const vixl::aarch32::Register& reg, const void* addr,
const vixl::aarch32::Register& tempreg)
[[maybe_unused]] void armEmitFarStore(vixl::aarch32::Assembler* armAsm, const vixl::aarch32::Register& reg,
const void* addr, const vixl::aarch32::Register& tempreg)
{
armMoveAddressToReg(armAsm, tempreg, addr);
armAsm->str(reg, vixl::aarch32::MemOperand(tempreg));
@ -617,13 +617,19 @@ void CPU::ARM32Recompiler::GenerateICacheCheckAndUpdate()
}
else if (m_block->icache_line_count > 0)
{
VirtualMemoryAddress current_pc = m_block->pc & ICACHE_TAG_ADDRESS_MASK;
const TickCount fill_ticks = GetICacheFillTicks(current_pc);
if (fill_ticks <= 0)
return;
const auto& ticks_reg = RARG1;
const auto& current_tag_reg = RARG2;
const auto& existing_tag_reg = RARG3;
const auto& fill_ticks_reg = r5;
VirtualMemoryAddress current_pc = m_block->pc & ICACHE_TAG_ADDRESS_MASK;
armAsm->ldr(ticks_reg, PTR(&g_state.pending_ticks));
armEmitMov(armAsm, current_tag_reg, current_pc);
armEmitMov(armAsm, fill_ticks_reg, fill_ticks);
for (u32 i = 0; i < m_block->icache_line_count; i++, current_pc += ICACHE_LINE_SIZE)
{
@ -634,14 +640,19 @@ void CPU::ARM32Recompiler::GenerateICacheCheckAndUpdate()
const u32 line = GetICacheLine(current_pc);
const u32 offset = OFFSETOF(State, icache_tags) + (line * sizeof(u32));
Label cache_hit;
armAsm->ldr(existing_tag_reg, MemOperand(RSTATE, offset));
armAsm->cmp(existing_tag_reg, current_tag_reg);
armAsm->b(eq, &cache_hit);
// Offsets must be <4K on ARM.
MemOperand line_addr = MemOperand(RSTATE, offset);
if (offset >= 4096)
{
armEmitMov(armAsm, RSCRATCH, offset);
line_addr = MemOperand(RSTATE, RSCRATCH);
}
armAsm->str(current_tag_reg, MemOperand(RSTATE, offset));
armAsm->add(ticks_reg, ticks_reg, armCheckAddSubConstant(static_cast<u32>(fill_ticks)));
armAsm->bind(&cache_hit);
Label cache_hit;
armAsm->ldr(existing_tag_reg, line_addr);
armAsm->str(current_tag_reg, line_addr);
armAsm->cmp(existing_tag_reg, current_tag_reg);
armAsm->add(ne, ticks_reg, ticks_reg, fill_ticks_reg);
if (i != (m_block->icache_line_count - 1))
armAsm->add(current_tag_reg, current_tag_reg, armCheckAddSubConstant(ICACHE_LINE_SIZE));
@ -741,17 +752,11 @@ void CPU::ARM32Recompiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool
}
else
{
if (newpc.value() == m_block->pc)
{
// Special case: ourselves! No need to backlink then.
DEBUG_LOG("Linking block at {:08X} to self", m_block->pc);
armEmitJmp(armAsm, armAsm->GetBuffer()->GetStartAddress<const void*>(), true);
}
else
{
const void* target = CodeCache::CreateBlockLink(m_block, armAsm->GetCursorAddress<void*>(), newpc.value());
armEmitJmp(armAsm, target, true);
}
const void* target = (newpc.value() == m_block->pc) ?
CodeCache::CreateSelfBlockLink(m_block, armAsm->GetCursorAddress<void*>(),
armAsm->GetBuffer()->GetStartAddress<const void*>()) :
CodeCache::CreateBlockLink(m_block, armAsm->GetCursorAddress<void*>(), newpc.value());
armEmitJmp(armAsm, target, true);
}
}

View File

@ -780,28 +780,29 @@ void CPU::ARM64Recompiler::GenerateICacheCheckAndUpdate()
const auto& ticks_reg = RWARG1;
const auto& current_tag_reg = RWARG2;
const auto& existing_tag_reg = RWARG3;
const auto& fill_ticks_reg = w4;
const auto& ticks_to_add_reg = w5;
VirtualMemoryAddress current_pc = m_block->pc & ICACHE_TAG_ADDRESS_MASK;
const TickCount fill_ticks = GetICacheFillTicks(current_pc);
if (fill_ticks <= 0)
return;
armAsm->ldr(ticks_reg, PTR(&g_state.pending_ticks));
armEmitMov(armAsm, current_tag_reg, current_pc);
armEmitMov(armAsm, fill_ticks_reg, fill_ticks);
for (u32 i = 0; i < m_block->icache_line_count; i++, current_pc += ICACHE_LINE_SIZE)
{
const TickCount fill_ticks = GetICacheFillTicks(current_pc);
if (fill_ticks <= 0)
continue;
const u32 line = GetICacheLine(current_pc);
const u32 offset = OFFSETOF(State, icache_tags) + (line * sizeof(u32));
Label cache_hit;
armAsm->ldr(existing_tag_reg, MemOperand(RSTATE, offset));
armAsm->cmp(existing_tag_reg, current_tag_reg);
armAsm->b(&cache_hit, eq);
armAsm->str(current_tag_reg, MemOperand(RSTATE, offset));
armAsm->add(ticks_reg, ticks_reg, armCheckAddSubConstant(static_cast<u32>(fill_ticks)));
armAsm->bind(&cache_hit);
armAsm->cmp(existing_tag_reg, current_tag_reg);
armAsm->csel(ticks_to_add_reg, fill_ticks_reg, wzr, ne);
armAsm->add(ticks_reg, ticks_reg, ticks_to_add_reg);
if (i != (m_block->icache_line_count - 1))
armAsm->add(current_tag_reg, current_tag_reg, armCheckAddSubConstant(ICACHE_LINE_SIZE));
@ -901,17 +902,11 @@ void CPU::ARM64Recompiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool
}
else
{
if (newpc.value() == m_block->pc)
{
// Special case: ourselves! No need to backlink then.
DEBUG_LOG("Linking block at {:08X} to self", m_block->pc);
armEmitJmp(armAsm, armAsm->GetBuffer()->GetStartAddress<const void*>(), true);
}
else
{
const void* target = CodeCache::CreateBlockLink(m_block, armAsm->GetCursorAddress<void*>(), newpc.value());
armEmitJmp(armAsm, target, true);
}
const void* target = (newpc.value() == m_block->pc) ?
CodeCache::CreateSelfBlockLink(m_block, armAsm->GetCursorAddress<void*>(),
armAsm->GetBuffer()->GetStartAddress<const void*>()) :
CodeCache::CreateBlockLink(m_block, armAsm->GetCursorAddress<void*>(), newpc.value());
armEmitJmp(armAsm, target, true);
}
}

View File

@ -66,7 +66,7 @@ using namespace biscuit;
RISCV64Recompiler s_instance;
Recompiler* g_compiler = &s_instance;
} // namespace CPU::Recompiler
} // namespace CPU
bool rvIsCallerSavedRegister(u32 id)
{
@ -150,7 +150,8 @@ void rvEmitFarLoad(biscuit::Assembler* rvAsm, const biscuit::GPR& reg, const voi
rvAsm->LWU(reg, lo, reg);
}
[[maybe_unused]] void rvEmitFarStore(biscuit::Assembler* rvAsm, const biscuit::GPR& reg, const void* addr, const biscuit::GPR& tempreg)
[[maybe_unused]] void rvEmitFarStore(biscuit::Assembler* rvAsm, const biscuit::GPR& reg, const void* addr,
const biscuit::GPR& tempreg)
{
const auto [hi, lo] = rvGetAddressImmediates(rvAsm->GetCursorPointer(), addr);
rvAsm->AUIPC(tempreg, hi);
@ -697,17 +698,11 @@ void CPU::RISCV64Recompiler::EndAndLinkBlock(const std::optional<u32>& newpc, bo
}
else
{
if (newpc.value() == m_block->pc)
{
// Special case: ourselves! No need to backlink then.
DEBUG_LOG("Linking block at {:08X} to self", m_block->pc);
rvEmitJmp(rvAsm, rvAsm->GetBufferPointer(0));
}
else
{
const void* target = CreateBlockLink(m_block, rvAsm->GetCursorPointer(), newpc.value());
rvEmitJmp(rvAsm, target);
}
const void* target =
(newpc.value() == m_block->pc) ?
CodeCache::CreateSelfBlockLink(m_block, rvAsm->GetCursorPointer(), rvAsm->GetBufferPointer(0)) :
CodeCache::CreateBlockLink(m_block, rvAsm->GetCursorPointer(), newpc.value());
rvEmitJmp(rvAsm, target);
}
}

View File

@ -506,27 +506,32 @@ void CPU::X64Recompiler::GenerateICacheCheckAndUpdate()
}
else if (m_block->icache_line_count > 0)
{
// RAM to ROM is not contiguous, therefore the cost will be the same across the entire block.
VirtualMemoryAddress current_pc = m_block->pc & ICACHE_TAG_ADDRESS_MASK;
const TickCount fill_ticks = GetICacheFillTicks(current_pc);
if (fill_ticks <= 0)
return;
cg->lea(RXARG1, cg->dword[PTR(&g_state.icache_tags)]);
cg->xor_(RWARG2, RWARG2);
cg->mov(RWARG4, fill_ticks);
// TODO: Vectorize this...
VirtualMemoryAddress current_pc = m_block->pc & ICACHE_TAG_ADDRESS_MASK;
for (u32 i = 0; i < m_block->icache_line_count; i++, current_pc += ICACHE_LINE_SIZE)
{
const VirtualMemoryAddress tag = GetICacheTagForAddress(current_pc);
const TickCount fill_ticks = GetICacheFillTicks(current_pc);
if (fill_ticks <= 0)
continue;
const u32 line = GetICacheLine(current_pc);
const u32 offset = (line * sizeof(u32));
Xbyak::Label cache_hit;
cg->xor_(RWARG3, RWARG3);
cg->cmp(cg->dword[RXARG1 + offset], tag);
cg->je(cache_hit);
cg->mov(cg->dword[RXARG1 + offset], tag);
cg->add(cg->dword[PTR(&g_state.pending_ticks)], static_cast<u32>(fill_ticks));
cg->L(cache_hit);
cg->cmovne(RWARG3, RWARG4);
cg->add(RWARG2, RWARG3);
}
cg->add(cg->dword[PTR(&g_state.pending_ticks)], RWARG2);
}
}
@ -629,17 +634,10 @@ void CPU::X64Recompiler::EndAndLinkBlock(const std::optional<u32>& newpc, bool d
}
else
{
if (newpc.value() == m_block->pc)
{
// Special case: ourselves! No need to backlink then.
DEBUG_LOG("Linking block at {:08X} to self", m_block->pc);
cg->jmp(cg->getCode());
}
else
{
const void* target = CodeCache::CreateBlockLink(m_block, cg->getCurr<void*>(), newpc.value());
cg->jmp(target, CodeGenerator::T_NEAR);
}
const void* target = (newpc.value() == m_block->pc) ?
CodeCache::CreateSelfBlockLink(m_block, cg->getCurr<void*>(), cg->getCode()) :
CodeCache::CreateBlockLink(m_block, cg->getCurr<void*>(), newpc.value());
cg->jmp(target, CodeGenerator::T_NEAR);
}
}

View File

@ -334,12 +334,12 @@ static void DrawIntRectSetting(SettingsInterface* bsi, const char* title, const
int min_value, int max_value, const char* format = "%d", bool enabled = true,
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
ImFont* summary_font = g_medium_font);
static void DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,
const char* key, const char* default_value, const char* const* options,
const char* const* option_values, size_t option_count, bool enabled = true,
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT,
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
#endif
static void DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,
const char* key, const char* default_value, std::span<const char* const> options,
std::span<const char* const> option_values, bool enabled = true,
float height = ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT,
ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont);
template<typename DataType, typename SizeType>
static void DrawEnumSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,
const char* key, DataType default_value,
@ -2463,28 +2463,23 @@ void FullscreenUI::DrawIntSpinBoxSetting(SettingsInterface* bsi, const char* tit
ImGui::PopFont();
}
#if 0
void FullscreenUI::DrawStringListSetting(SettingsInterface* bsi, const char* title, const char* summary,
const char* section, const char* key, const char* default_value,
const char* const* options, const char* const* option_values,
size_t option_count, bool enabled, float height, ImFont* font,
ImFont* summary_font)
[[maybe_unused]] void FullscreenUI::DrawStringListSetting(SettingsInterface* bsi, const char* title,
const char* summary, const char* section, const char* key,
const char* default_value,
std::span<const char* const> options,
std::span<const char* const> option_values, bool enabled,
float height, ImFont* font, ImFont* summary_font)
{
const bool game_settings = IsEditingGameSettings(bsi);
const std::optional<SmallString> value(bsi->GetOptionalSmallStringValue(
section, key, game_settings ? std::nullopt : std::optional<const char*>(default_value)));
if (option_count == 0)
{
// select from null entry
while (options && options[option_count] != nullptr)
option_count++;
}
DebugAssert(options.size() == option_values.size());
size_t index = option_count;
size_t index = options.size();
if (value.has_value())
{
for (size_t i = 0; i < option_count; i++)
for (size_t i = 0; i < options.size(); i++)
{
if (value == option_values[i])
{
@ -2495,15 +2490,15 @@ void FullscreenUI::DrawStringListSetting(SettingsInterface* bsi, const char* tit
}
if (MenuButtonWithValue(title, summary,
value.has_value() ? ((index < option_count) ? options[index] : "Unknown") :
"Use Global Setting",
value.has_value() ? ((index < options.size()) ? options[index] : FSUI_CSTR("Unknown")) :
FSUI_CSTR("Use Global Setting"),
enabled, height, font, summary_font))
{
ImGuiFullscreen::ChoiceDialogOptions cd_options;
cd_options.reserve(option_count + 1);
cd_options.reserve(options.size() + 1);
if (game_settings)
cd_options.emplace_back("Use Global Setting", !value.has_value());
for (size_t i = 0; i < option_count; i++)
cd_options.emplace_back(FSUI_CSTR("Use Global Setting"), !value.has_value());
for (size_t i = 0; i < options.size(); i++)
cd_options.emplace_back(options[i], (value.has_value() && i == static_cast<size_t>(index)));
OpenChoiceDialog(title, false, std::move(cd_options),
[game_settings, section, key, option_values](s32 index, const std::string& title, bool checked) {
@ -2530,7 +2525,6 @@ void FullscreenUI::DrawStringListSetting(SettingsInterface* bsi, const char* tit
});
}
}
#endif
template<typename DataType, typename SizeType>
void FullscreenUI::DrawEnumSetting(SettingsInterface* bsi, const char* title, const char* summary, const char* section,

View File

@ -41,7 +41,7 @@ namespace GameDatabase {
enum : u32
{
GAME_DATABASE_CACHE_SIGNATURE = 0x45434C48,
GAME_DATABASE_CACHE_VERSION = 18,
GAME_DATABASE_CACHE_VERSION = 19,
};
static const Entry* GetEntryForId(std::string_view code);
@ -85,6 +85,7 @@ static constexpr const std::array<const char*, static_cast<size_t>(Trait::MaxCou
"ForceDeinterlacing",
"ForceFullBoot",
"DisableAutoAnalogMode",
"DisableMultitap",
"DisableTrueColor",
"DisableUpscaling",
"DisableTextureFiltering",
@ -116,6 +117,7 @@ static constexpr const std::array<const char*, static_cast<size_t>(Trait::MaxCou
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Deinterlacing", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Force Full Boot", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Automatic Analog Mode", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Multitap", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable True Color", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Upscaling", "GameDatabase::Trait"),
TRANSLATE_DISAMBIG_NOOP("GameDatabase", "Disable Texture Filtering", "GameDatabase::Trait"),
@ -437,6 +439,30 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
messages.append_format(__VA_ARGS__); \
} while (0)
if (HasTrait(Trait::ForceInterpreter))
{
if (display_osd_messages && settings.cpu_execution_mode != CPUExecutionMode::Interpreter)
APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "CPU recompiler disabled."));
settings.cpu_execution_mode = CPUExecutionMode::Interpreter;
}
if (HasTrait(Trait::ForceFullBoot))
{
if (display_osd_messages && settings.bios_patch_fast_boot)
APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Fast boot disabled."));
settings.bios_patch_fast_boot = false;
}
if (HasTrait(Trait::DisableMultitap))
{
if (display_osd_messages && settings.multitap_mode != MultitapMode::Disabled)
APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Multitap disabled."));
settings.multitap_mode = MultitapMode::Disabled;
}
if (display_crop_mode.has_value())
{
if (display_osd_messages && settings.display_crop_mode != display_crop_mode.value())
@ -448,14 +474,6 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
settings.display_crop_mode = display_crop_mode.value();
}
if (HasTrait(Trait::ForceInterpreter))
{
if (display_osd_messages && settings.cpu_execution_mode != CPUExecutionMode::Interpreter)
APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "CPU recompiler disabled."));
settings.cpu_execution_mode = CPUExecutionMode::Interpreter;
}
if (HasTrait(Trait::ForceSoftwareRenderer))
{
if (display_osd_messages && settings.gpu_renderer != GPURenderer::Software)
@ -515,14 +533,6 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
}
}
if (HasTrait(Trait::ForceFullBoot))
{
if (display_osd_messages && settings.bios_patch_fast_boot)
APPEND_MESSAGE(TRANSLATE_SV("GameDatabase", "Fast boot disabled."));
settings.bios_patch_fast_boot = false;
}
if (HasTrait(Trait::DisableTrueColor))
{
if (display_osd_messages && settings.gpu_true_color)
@ -749,16 +759,25 @@ void GameDatabase::Entry::ApplySettings(Settings& settings, bool display_osd_mes
supported_controller_string.append(Controller::GetControllerInfo(supported_ctype)->GetDisplayName());
}
Host::AddKeyedOSDMessage(
"gamedb_controller_unsupported",
fmt::format(TRANSLATE_FS("GameDatabase",
"Controller in port {0} ({1}) is not supported for {2}.\nSupported controllers: "
"{3}\nPlease configure a supported controller from the list above."),
i + 1u, Controller::GetControllerInfo(ctype)->GetDisplayName(), System::GetGameTitle(),
supported_controller_string),
Host::AddIconOSDWarning(
fmt::format("GameDBController{}Unsupported", i), ICON_EMOJI_WARNING,
fmt::format(
TRANSLATE_FS("GameDatabase",
"Controller in Port {0} ({1}) is not supported for this game.\nSupported controllers: "
"{2}\nPlease configure a supported controller from the list above."),
i + 1u, Controller::GetControllerInfo(ctype)->GetDisplayName(), supported_controller_string),
Host::OSD_CRITICAL_ERROR_DURATION);
}
}
if (g_settings.multitap_mode != MultitapMode::Disabled && !(supported_controllers & SUPPORTS_MULTITAP_BIT))
{
Host::AddIconOSDMessage("GameDBMultitapUnsupported", ICON_EMOJI_WARNING,
TRANSLATE_STR("GameDatabase",
"This game does not support multitap, but multitap is enabled.\n"
" This may result in dropped controller inputs."),
Host::OSD_CRITICAL_ERROR_DURATION);
}
}
#undef BIT_FOR
@ -841,6 +860,9 @@ std::string GameDatabase::Entry::GenerateCompatibilityReport() const
ret.append_format(" - {}\n", Controller::GetControllerInfo(static_cast<ControllerType>(j))->GetDisplayName());
}
if (supported_controllers & SUPPORTS_MULTITAP_BIT)
ret.append(" - Multitap\n");
ret.append("\n");
}
@ -1303,7 +1325,7 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
if (const std::optional libcrypt_val = StringUtil::FromChars<bool>(to_stringview(libcrypt.val()));
libcrypt_val.has_value())
{
entry->traits[static_cast<size_t>(Trait::IsLibCryptProtected)] = true;
entry->traits[static_cast<size_t>(Trait::IsLibCryptProtected)] = libcrypt_val.value();
}
else
{
@ -1311,6 +1333,20 @@ bool GameDatabase::ParseYamlEntry(Entry* entry, const ryml::ConstNodeRef& value)
}
}
if (const ryml::ConstNodeRef& multitap = value.find_child(to_csubstr("multitap")); multitap.valid())
{
if (const std::optional multitap_val = StringUtil::FromChars<bool>(to_stringview(multitap.val()));
multitap_val.has_value())
{
if (multitap_val.value())
entry->supported_controllers |= Entry::SUPPORTS_MULTITAP_BIT;
}
else
{
WARNING_LOG("Invalid multitap value in {}", entry->serial);
}
}
if (const ryml::ConstNodeRef settings = value.find_child(to_csubstr("settings"));
settings.valid() && settings.has_children())
{

View File

@ -41,6 +41,7 @@ enum class Trait : u32
ForceDeinterlacing,
ForceFullBoot,
DisableAutoAnalogMode,
DisableMultitap,
DisableTrueColor,
DisableUpscaling,
DisableTextureFiltering,
@ -94,6 +95,8 @@ enum class Language : u8
struct Entry
{
static constexpr u16 SUPPORTS_MULTITAP_BIT = (1u << static_cast<u8>(ControllerType::Count));
std::string_view serial;
std::string_view title;
std::string_view genre;

View File

@ -431,7 +431,8 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
(features.noperspective_interpolation && g_settings.gpu_pgxp_enable &&
g_settings.gpu_pgxp_color_correction != old_settings.gpu_pgxp_color_correction) ||
m_allow_sprite_mode !=
ShouldAllowSpriteMode(m_resolution_scale, g_settings.gpu_texture_filter, g_settings.gpu_sprite_texture_filter));
ShouldAllowSpriteMode(m_resolution_scale, g_settings.gpu_texture_filter, g_settings.gpu_sprite_texture_filter) ||
(!old_settings.gpu_texture_cache && g_settings.gpu_texture_cache));
const bool resolution_dependent_shaders_changed =
(m_resolution_scale != resolution_scale || m_multisamples != multisamples);
const bool downsampling_shaders_changed =
@ -1024,6 +1025,7 @@ bool GPU_HW::CompilePipelines(Error* error)
const bool true_color = g_settings.gpu_true_color;
const bool scaled_dithering = (!m_true_color && upscaled && g_settings.gpu_scaled_dithering);
const bool disable_color_perspective = (features.noperspective_interpolation && ShouldDisableColorPerspective());
const bool needs_page_texture = m_use_texture_cache;
// Determine when to use shader blending.
// FBFetch is free, we need it for filtering without DSB, or when accurate blending is forced.
@ -1065,15 +1067,16 @@ bool GPU_HW::CompilePipelines(Error* error)
const GPU_HW_ShaderGen shadergen(g_gpu_device->GetRenderAPI(), m_supports_dual_source_blend,
m_supports_framebuffer_fetch);
const u32 active_texture_modes =
m_allow_sprite_mode ? NUM_TEXTURE_MODES :
(NUM_TEXTURE_MODES - (NUM_TEXTURE_MODES - static_cast<u32>(BatchTextureMode::SpriteStart)));
const u32 total_vertex_shaders = (m_allow_sprite_mode ? 7 : 4);
const u32 total_fragment_shaders = ((1 + BoolToUInt32(needs_rov_depth)) * 5 * 5 * active_texture_modes * 2 *
const u32 max_active_texture_modes =
(m_allow_sprite_mode ? NUM_TEXTURE_MODES :
(NUM_TEXTURE_MODES - (NUM_TEXTURE_MODES - static_cast<u32>(BatchTextureMode::SpriteStart))));
const u32 num_active_texture_modes = (max_active_texture_modes - BoolToUInt32(!needs_page_texture));
const u32 total_vertex_shaders = ((m_allow_sprite_mode ? 7 : 4) - BoolToUInt32(!needs_page_texture));
const u32 total_fragment_shaders = ((1 + BoolToUInt32(needs_rov_depth)) * 5 * 5 * num_active_texture_modes * 2 *
(1 + BoolToUInt32(!true_color)) * (1 + BoolToUInt32(!m_force_progressive_scan)));
const u32 total_items =
total_vertex_shaders + total_fragment_shaders +
((m_pgxp_depth_buffer ? 2 : 1) * 5 * 5 * active_texture_modes * 2 * (1 + BoolToUInt32(!true_color)) *
((m_pgxp_depth_buffer ? 2 : 1) * 5 * 5 * num_active_texture_modes * 2 * (1 + BoolToUInt32(!true_color)) *
(1 + BoolToUInt32(!m_force_progressive_scan))) + // batch pipelines
((m_wireframe_mode != GPUWireframeMode::Disabled) ? 1 : 0) + // wireframe
(2 * 2) + // vram fill
@ -1116,6 +1119,8 @@ bool GPU_HW::CompilePipelines(Error* error)
{
if (palette && !textured)
continue;
if (palette == 2 && !needs_page_texture)
continue;
for (u8 sprite = 0; sprite < 2; sprite++)
{
@ -1161,13 +1166,16 @@ bool GPU_HW::CompilePipelines(Error* error)
// If using ROV depth, we only draw with shader blending.
(needs_rov_depth && render_mode != static_cast<u8>(BatchRenderMode::ShaderBlend)))
{
progress.Increment(active_texture_modes * 2 * (1 + BoolToUInt32(!true_color)) *
progress.Increment(num_active_texture_modes * 2 * (1 + BoolToUInt32(!true_color)) *
(1 + BoolToUInt32(!m_force_progressive_scan)));
continue;
}
for (u8 texture_mode = 0; texture_mode < active_texture_modes; texture_mode++)
for (u8 texture_mode = 0; texture_mode < max_active_texture_modes; texture_mode++)
{
if (texture_mode == static_cast<u8>(BatchTextureMode::PageTexture) && !needs_page_texture)
continue;
for (u8 check_mask = 0; check_mask < 2; check_mask++)
{
if (check_mask && render_mode != static_cast<u8>(BatchRenderMode::ShaderBlend))
@ -1268,13 +1276,16 @@ bool GPU_HW::CompilePipelines(Error* error)
// If using ROV depth, we only draw with shader blending.
(needs_rov_depth && render_mode != static_cast<u8>(BatchRenderMode::ShaderBlend)))
{
progress.Increment(active_texture_modes * 2 * (1 + BoolToUInt32(!true_color)) *
progress.Increment(num_active_texture_modes * 2 * (1 + BoolToUInt32(!true_color)) *
(1 + BoolToUInt32(!m_force_progressive_scan)));
continue;
}
for (u8 texture_mode = 0; texture_mode < active_texture_modes; texture_mode++)
for (u8 texture_mode = 0; texture_mode < max_active_texture_modes; texture_mode++)
{
if (texture_mode == static_cast<u8>(BatchTextureMode::PageTexture) && !needs_page_texture)
continue;
for (u8 dithering = 0; dithering < 2; dithering++)
{
// Never going to draw with dithering on in true color.
@ -3853,7 +3864,7 @@ void GPU_HW::UpdateDisplay()
{
if (IsUsingMultisampling())
{
UpdateVRAMReadTexture(true, true);
UpdateVRAMReadTexture(!m_vram_dirty_draw_rect.eq(INVALID_RECT), !m_vram_dirty_write_rect.eq(INVALID_RECT));
SetDisplayTexture(m_vram_read_texture.get(), nullptr, 0, 0, m_vram_read_texture->GetWidth(),
m_vram_read_texture->GetHeight());
}

View File

@ -883,7 +883,7 @@ float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
#if !UPSCALED
uint2 icoord = ApplyTextureWindow(FloatToIntegerCoords(coords));
uint2 vicoord = (texpage.xy + icoord) & uint2(1023, 511);
return LOAD_TEXTURE(samp0, int2(vicoord), 0);
return SAMPLE_TEXTURE_LEVEL(samp0, float2(vicoord) * RCP_VRAM_SIZE, 0.0);
#else
// Coordinates are already upscaled, we need to downscale them to apply the texture
// window, then re-upscale/offset. We can't round here, because it could result in
@ -979,7 +979,7 @@ float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
ialpha = 1.0;
#elif TEXTURE_FILTERING
#if PAGE_TEXTURE
FilteredSampleFromVRAM(int2(0, 0), v_tex0, v_uv_limits, texcol, ialpha);
FilteredSampleFromVRAM(VECTOR_BROADCAST(TEXPAGE_VALUE, 0u), v_tex0, v_uv_limits, texcol, ialpha);
#else
FilteredSampleFromVRAM(v_texpage, v_tex0, v_uv_limits, texcol, ialpha);
#endif

View File

@ -2942,6 +2942,9 @@ bool GPUTextureCache::HasValidReplacementExtension(const std::string_view path)
void GPUTextureCache::FindTextureReplacements(bool load_vram_write_replacements, bool load_texture_replacements)
{
if (s_state.game_id.empty())
return;
FileSystem::FindResultsArray files;
FileSystem::FindFiles(GetTextureReplacementDirectory().c_str(), "*",
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE, &files);
@ -3028,6 +3031,9 @@ void GPUTextureCache::LoadTextureReplacementAliases(const ryml::ConstNodeRef& ro
bool load_vram_write_replacement_aliases,
bool load_texture_replacement_aliases)
{
if (s_state.game_id.empty())
return;
const std::string source_dir = GetTextureReplacementDirectory();
for (const ryml::ConstNodeRef& current : root.cchildren())
@ -3303,8 +3309,18 @@ bool GPUTextureCache::EnsureGameDirectoryExists()
std::string GPUTextureCache::GetTextureReplacementDirectory()
{
return Path::Combine(EmuFolders::Textures,
SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "replacements", s_state.game_id));
std::string dir = Path::Combine(
EmuFolders::Textures, SmallString::from_format("{}" FS_OSPATH_SEPARATOR_STR "replacements", s_state.game_id));
if (!FileSystem::DirectoryExists(dir.c_str()))
{
// Check for the old directory structure without a replacements subdirectory.
std::string altdir = Path::Combine(EmuFolders::Textures, s_state.game_id);
if (FileSystem::DirectoryExists(altdir.c_str()))
WARNING_LOG("Using deprecated texture replacement directory {}", altdir);
dir = std::move(altdir);
}
return dir;
}
std::string GPUTextureCache::GetTextureDumpDirectory()

863
src/core/pio.cpp Normal file
View File

@ -0,0 +1,863 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "pio.h"
#include "bus.h"
#include "settings.h"
#include "system.h"
#include "types.h"
#include "util/state_wrapper.h"
#include "common/assert.h"
#include "common/bitfield.h"
#include "common/bitutils.h"
#include "common/error.h"
#include "common/file_system.h"
#include "common/heap_array.h"
#include "common/log.h"
#include "common/path.h"
#include <limits>
LOG_CHANNEL(PIO);
namespace PIO {
// Atmel AT29C040A
class Flash
{
public:
static constexpr u32 TOTAL_SIZE = 512 * 1024;
Flash();
~Flash();
ALWAYS_INLINE bool IsImageModified() const { return m_image_modified; }
bool LoadImage(const char* path, Error* error);
bool SaveImage(const char* path, Error* error);
void Reset();
bool DoState(StateWrapper& sw);
u8 Read(u32 offset);
void CodeRead(u32 offset, u32* words, u32 word_count);
void Write(u32 offset, u8 value);
private:
static constexpr u32 SECTOR_SIZE = 256;
static constexpr u32 SECTOR_COUNT = TOTAL_SIZE / SECTOR_SIZE;
static constexpr TickCount PROGRAM_TIMER_CYCLES = 5080; // ~150us
enum : u8
{
// 3 byte commands
FLASH_CMD_ENTER_ID_MODE = 0x90,
FLASH_CMD_EXIT_ID_MODE = 0xF0,
FLASH_CMD_WRITE_SECTOR_WITH_SDP = 0xA0,
FLASH_CMD_BEGIN_5_BYTE_COMMAND = 0x80,
// 5 byte commands
FLASH_CMD_WRITE_SECTOR_WITHOUT_SDP = 0x20,
FLASH_CMD_ALT_ENTER_ID_MODE = 0x60,
};
u8* SectorPtr(u32 sector);
void PushCommandByte(u8 value);
void ProgramWrite(u32 offset, u8 value);
bool CheckForProgramTimeout();
void EndProgramming();
DynamicHeapArray<u8> m_data;
GlobalTicks m_program_write_timeout = 0;
std::array<u8, 6> m_command_buffer = {};
bool m_flash_id_mode = false;
bool m_write_enable = false;
bool m_image_modified = false;
u8 m_write_toggle_result = 0;
u16 m_write_position = 0;
u32 m_sector_address = 0;
u32 m_max_data_address = 0;
};
} // namespace PIO
PIO::Flash::Flash() = default;
PIO::Flash::~Flash() = default;
u8 PIO::Flash::Read(u32 offset)
{
if (m_flash_id_mode) [[unlikely]]
{
// Atmel AT29C040A
static constexpr std::array<u8, 2> flash_id = {0x1F, 0xA4};
return flash_id[offset & 1];
}
// WARNING_LOG("FLASH READ 0x{:X} 0x{:X} @ {}", offset, g_exp1_rom[offset], System::GetGlobalTickCounter());
if (m_write_enable && !CheckForProgramTimeout()) [[unlikely]]
{
m_write_toggle_result ^= 0x40;
WARNING_LOG("read while programming 0x{:02X}", m_write_toggle_result);
EndProgramming();
return m_write_toggle_result | 0x80;
}
return (offset >= TOTAL_SIZE) ? 0xFFu : m_data[offset];
}
void PIO::Flash::CodeRead(u32 offset, u32* words, u32 word_count)
{
DebugAssert((offset + (word_count * sizeof(u32))) < TOTAL_SIZE);
std::memcpy(words, m_data.data() + offset, word_count * sizeof(u32));
}
void PIO::Flash::PushCommandByte(u8 value)
{
for (u32 i = static_cast<u32>(std::size(m_command_buffer) - 1); i > 0; i--)
m_command_buffer[i] = m_command_buffer[i - 1];
m_command_buffer[0] = value;
}
void PIO::Flash::Write(u32 offset, u8 value)
{
if (m_write_enable && !CheckForProgramTimeout())
{
ProgramWrite(offset, value);
return;
}
DEV_LOG("FLASH WRITE 0x{:X} 0x{:X}", offset, value);
// Ignore banked addresses
offset &= 0x3FFFF;
if (offset == 0x2AAA || offset == 0x5555)
{
PushCommandByte(value);
const auto& buf = m_command_buffer;
if (buf[2] == 0xAA && buf[1] == 0x55)
{
if (value == FLASH_CMD_ENTER_ID_MODE)
{
DEV_LOG("Flash enter ID mode");
m_flash_id_mode = true;
}
else if (value == FLASH_CMD_EXIT_ID_MODE)
{
DEV_LOG("Flash exit ID mode");
m_flash_id_mode = false;
}
else if (value == FLASH_CMD_WRITE_SECTOR_WITH_SDP)
{
DEV_LOG("Flash write sector with SDP @ {}", System::GetGlobalTickCounter());
m_write_enable = true;
m_program_write_timeout = System::GetGlobalTickCounter() + PROGRAM_TIMER_CYCLES;
}
else if (buf[5] == 0xAA && buf[4] == 0x55 && buf[3] == 0x80)
{
if (value == FLASH_CMD_ALT_ENTER_ID_MODE)
{
DEV_LOG("Flash Alt Enter ID mode");
m_flash_id_mode = true;
}
if (value == FLASH_CMD_WRITE_SECTOR_WITHOUT_SDP)
{
DEV_LOG("Flash Write sector WITHOUT SDP");
m_write_enable = true;
m_program_write_timeout = std::numeric_limits<GlobalTicks>::max();
}
else
{
ERROR_LOG("Unhandled 5-cycle flash command 0x{:02X}", value);
}
}
else if (value != 0x80)
{
ERROR_LOG("Unhandled 3-cycle flash command 0x{:02X}", value);
}
}
}
}
void PIO::Flash::ProgramWrite(u32 offset, u8 value)
{
// reset the timeout.. event system suckage, we need it from _this_ cycle, not the first
m_program_write_timeout = std::max(m_program_write_timeout, System::GetGlobalTickCounter() + PROGRAM_TIMER_CYCLES);
static_assert((0x800 * 0x100) == TOTAL_SIZE);
const u32 byte_address = (offset & 0xFFu);
const u32 sector_address = (offset >> 8) & 0x7FFu;
if (m_write_position == 0)
{
DEV_LOG("Writing to flash sector {} (offset 0x{:06X})", sector_address, sector_address * SECTOR_SIZE);
m_sector_address = sector_address;
const u32 sector_data_end = (sector_address * SECTOR_SIZE) + SECTOR_SIZE;
if (sector_data_end > m_max_data_address)
{
m_max_data_address = sector_data_end;
m_image_modified = true;
}
}
if (sector_address == m_sector_address) [[likely]]
{
u8* byte_ptr = SectorPtr(sector_address) + byte_address;
m_image_modified |= (*byte_ptr != value);
*byte_ptr = value;
}
else
{
WARNING_LOG("Flash write: unexpected sector address of {}, expected {} (addr 0x{:05X}", sector_address,
m_sector_address, offset);
}
m_write_position++;
if (m_write_position == SECTOR_SIZE)
{
// end of flash write
EndProgramming();
}
}
bool PIO::Flash::CheckForProgramTimeout()
{
DebugAssert(m_write_enable);
if (System::GetGlobalTickCounter() < m_program_write_timeout)
return false;
WARNING_LOG("Flash program timeout at byte {}", m_write_position);
// kinda cheating here, the sector would normally get buffered and then written
// but the flash isn't supposed to be readable during programming anyway...
if (m_write_position > 0)
{
const u32 bytes_to_erase = SECTOR_SIZE - m_write_position;
if (bytes_to_erase > 0)
{
WARNING_LOG("Erasing {} unwritten bytes in sector {} (0x{:05X})", bytes_to_erase, m_write_position,
m_sector_address * SECTOR_SIZE);
u8* sector = SectorPtr(m_sector_address) + m_write_position;
bool image_modified = false;
for (u32 i = 0; i < bytes_to_erase; i++)
{
image_modified |= (sector[i] != 0xFF);
sector[i] = 0xFF;
}
m_image_modified |= image_modified;
}
}
else
{
WARNING_LOG("No sector address set, skipping programming.");
}
EndProgramming();
return true;
}
void PIO::Flash::EndProgramming()
{
m_write_enable = false;
m_write_position = 0;
m_sector_address = 0;
m_write_toggle_result = 0;
}
u8* PIO::Flash::SectorPtr(u32 sector)
{
DebugAssert(sector < SECTOR_COUNT);
return (m_data.data() + sector * SECTOR_SIZE);
}
bool PIO::Flash::LoadImage(const char* path, Error* error)
{
const FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(path, "rb", error);
if (!fp)
{
Error::AddPrefixFmt(error, "Failed to open PIO flash image '{}': ", Path::GetFileName(path));
return false;
}
const s64 file_size = std::max<s64>(FileSystem::FSize64(fp.get(), error), 0);
if (file_size > TOTAL_SIZE)
{
WARNING_LOG("PIO flash image is too large ({} bytes), only {} bytes will be read.", file_size, TOTAL_SIZE);
}
else if (file_size < TOTAL_SIZE)
{
DEV_LOG("PIO flash image is too small ({} bytes), {} bytes of padding will be added.", file_size,
TOTAL_SIZE - file_size);
}
const u32 read_size = static_cast<u32>(std::min<s64>(file_size, TOTAL_SIZE));
m_data.resize(TOTAL_SIZE);
if (read_size > 0 && std::fread(m_data.data(), read_size, 1, fp.get()) != 1)
{
Error::SetErrno(error, "Failed to read PIO flash image: ", errno);
m_data.deallocate();
return false;
}
const u32 padding_size = TOTAL_SIZE - read_size;
if (padding_size > 0)
std::memset(m_data.data() + read_size, 0, padding_size);
m_max_data_address = read_size;
return true;
}
bool PIO::Flash::SaveImage(const char* path, Error* error)
{
WARNING_LOG("Writing PIO flash image '{}'", Path::GetFileName(path));
if (!FileSystem::WriteBinaryFile(path, m_data.cspan(0, m_max_data_address), error))
{
Error::AddPrefixFmt(error, "Failed to write PIO flash image '{}': ", Path::GetFileName(path));
return false;
}
return true;
}
void PIO::Flash::Reset()
{
m_command_buffer.fill(0);
m_flash_id_mode = false;
m_write_enable = false;
m_write_position = 0;
m_sector_address = 0;
m_program_write_timeout = 0;
m_write_toggle_result = 0;
}
bool PIO::Flash::DoState(StateWrapper& sw)
{
sw.DoBytes(m_data.data(), m_data.size());
sw.DoBytes(m_command_buffer.data(), m_command_buffer.size());
sw.Do(&m_flash_id_mode);
sw.Do(&m_write_enable);
sw.Do(&m_write_position);
sw.Do(&m_sector_address);
sw.Do(&m_program_write_timeout);
sw.Do(&m_write_toggle_result);
sw.Do(&m_image_modified);
sw.Do(&m_max_data_address);
return !sw.HasError();
}
namespace PIO {
class NullDevice : public Device
{
public:
NullDevice();
~NullDevice() override;
bool Initialize(Error* error) override;
void Reset() override;
void UpdateSettings(const Settings& old_settings) override;
bool DoState(StateWrapper& sw) override;
u8 ReadHandler(u32 offset) override;
void CodeReadHandler(u32 offset, u32* words, u32 word_count) override;
void WriteHandler(u32 offset, u8 value) override;
};
} // namespace PIO
PIO::NullDevice::NullDevice() = default;
PIO::NullDevice::~NullDevice() = default;
bool PIO::NullDevice::Initialize(Error* error)
{
return true;
}
void PIO::NullDevice::Reset()
{
}
void PIO::NullDevice::UpdateSettings(const Settings& old_settings)
{
}
bool PIO::NullDevice::DoState(StateWrapper& sw)
{
return true;
}
u8 PIO::NullDevice::ReadHandler(u32 offset)
{
return 0xFFu;
}
void PIO::NullDevice::CodeReadHandler(u32 offset, u32* words, u32 word_count)
{
std::memset(words, 0xFF, sizeof(u32) * word_count);
}
void PIO::NullDevice::WriteHandler(u32 offset, u8 value)
{
}
#if 0
namespace PIO {
namespace {
class DatelCartDevice : public Device
{
public:
DatelCartDevice();
~DatelCartDevice() override;
bool Initialize(Error* error) override;
void Reset() override;
void UpdateSettings(const Settings& old_settings) override;
bool DoState(StateWrapper& sw) override;
u8 ReadHandler(u32 offset) override;
void CodeReadHandler(u32 offset, u32* words, u32 word_count) override;
void WriteHandler(u32 offset, u8 value) override;
private:
Flash m_flash;
};
}
} // namespace PIO
PIO::DatelCartDevice::DatelCartDevice() = default;
PIO::DatelCartDevice::~DatelCartDevice() = default;
bool PIO::DatelCartDevice::Initialize(Error* error)
{
return false;
}
void PIO::DatelCartDevice::Reset()
{
}
void PIO::DatelCartDevice::UpdateSettings(const Settings& old_settings)
{
}
bool PIO::DatelCartDevice::DoState(StateWrapper& sw)
{
return false;
}
u8 PIO::DatelCartDevice::ReadHandler(u32 offset)
{
WARNING_LOG("Datel EXP1 read 0x{:08X}", offset);
if (offset < 0x20000)
{
// first 128KB of flash
return m_flash.Read(offset);
}
else if (offset >= 0x40000 && offset < 0x60000) // 1F040000->1F05FFFF
{
// second 128KB of flash
return m_flash.Read((offset - 0x40000) + 0x20000);
}
else if (offset == 0x20018)
{
// switch setting
return 1u;
}
else if (offset == 0x20010)
{
// comms link STB pin state (bit 0)
return 0u;
}
else if (offset == 0x60000)
{
// comms link data in
return 0u;
}
else
{
WARNING_LOG("Unhandled Datel EXP1 read: 0x{:08X}", offset);
return 0xFFu;
}
}
void PIO::DatelCartDevice::CodeReadHandler(u32 offset, u32* words, u32 word_count)
{
if (offset < 0x20000)
m_flash.CodeRead(offset, words, word_count);
else if (offset >= 0x40000 && offset < 0x60000) // 1F040000->1F05FFFF
m_flash.CodeRead((offset - 0x40000) + 0x20000, words, word_count);
else
std::memset(words, 0xFF, sizeof(u32) * word_count);
}
void PIO::DatelCartDevice::WriteHandler(u32 offset, u8 value)
{
WARNING_LOG("DATEL WRITE 0x{:08X} 0x{:08X}", offset, value);
}
#endif
// Xplorer/Xploder
namespace PIO {
namespace {
class XplorerCart : public Device
{
public:
static constexpr u32 SRAM_SIZE = 128 * 1024;
XplorerCart();
~XplorerCart() override;
bool Initialize(Error* error) override;
void UpdateSettings(const Settings& old_settings) override;
void Reset() override;
bool DoState(StateWrapper& sw) override;
u8 ReadHandler(u32 offset) override;
void CodeReadHandler(u32 offset, u32* words, u32 word_count) override;
void WriteHandler(u32 offset, u8 value) override;
private:
ALWAYS_INLINE u32 GetFlashUpperBank() const { return m_memory_map.sram_bank ? (384 * 1024) : (256 * 1024); }
union MemoryMappingRegister
{
u8 bits;
BitField<u8, bool, 0, 1> pc_slct;
BitField<u8, bool, 1, 1> pc_pe;
BitField<u8, bool, 2, 1> pc_busy;
BitField<u8, bool, 3, 1> pc_ack;
BitField<u8, bool, 4, 1> sram_select;
BitField<u8, bool, 5, 1> flash_bank;
BitField<u8, bool, 6, 1> sram_bank;
BitField<u8, bool, 7, 1> sram_bank_2;
};
DynamicHeapArray<u8> m_sram;
Flash m_flash;
MemoryMappingRegister m_memory_map = {};
bool m_switch_state = false;
};
} // namespace
} // namespace PIO
PIO::XplorerCart::XplorerCart()
{
m_sram.resize(SRAM_SIZE);
}
PIO::XplorerCart::~XplorerCart()
{
if (g_settings.pio_flash_write_enable && m_flash.IsImageModified())
{
Error error;
if (!m_flash.SaveImage(g_settings.pio_flash_image_path.c_str(), &error))
ERROR_LOG("Failed to update Xplorer flash image: {}", error.GetDescription());
}
}
bool PIO::XplorerCart::Initialize(Error* error)
{
if (!m_flash.LoadImage(g_settings.pio_flash_image_path.c_str(), error))
return false;
m_switch_state = g_settings.pio_switch_active;
return true;
}
void PIO::XplorerCart::UpdateSettings(const Settings& old_settings)
{
m_switch_state = g_settings.pio_switch_active;
}
void PIO::XplorerCart::Reset()
{
m_flash.Reset();
std::memset(m_sram.data(), 0, m_sram.size());
m_memory_map.bits = 0;
}
bool PIO::XplorerCart::DoState(StateWrapper& sw)
{
m_flash.DoState(sw);
sw.DoBytes(m_sram.data(), m_sram.size());
sw.Do(&m_memory_map.bits);
return !sw.HasError();
}
u8 PIO::XplorerCart::ReadHandler(u32 offset)
{
// WARNING_LOG("Xplorer EXP1 read size {}: 0x{:08X}", 1u << (u32)size, address);
if (offset < 0x40000) // 1F000000->1F03FFFF
{
// first 256KB of flash
return m_flash.Read(offset);
}
else if (offset < 0x60000) // 1F040000->1F05FFFF
{
// second 256KB of flash or SRAM
offset &= 0x3FFFF;
if (m_memory_map.sram_select)
{
DebugAssert(offset < SRAM_SIZE);
return m_sram[offset];
}
else
{
return m_flash.Read(offset | GetFlashUpperBank());
}
}
else if (offset >= 0x60000 && offset < 0x70000)
{
// I/O, mirrored
switch (offset & 0x07)
{
case 0:
{
// switch setting
return 0xFEu | BoolToUInt8(m_switch_state);
}
case 1:
{
// data from PC
return 0u;
}
case 2:
{
// handshake from PC
return 0xFEu;
}
case 3:
case 4:
case 5:
case 6:
case 7:
{
// unknown
WARNING_LOG("Unhandled Xplorer I/O register read: 0x{:08X}", offset);
return 0xFFu;
}
DefaultCaseIsUnreachable()
}
}
else
{
WARNING_LOG("Unhandled Xplorer EXP1 read: 0x{:08X}", offset);
return 0xFFu;
}
}
void PIO::XplorerCart::CodeReadHandler(u32 offset, u32* words, u32 word_count)
{
if ((offset + word_count) <= 0x40000)
{
m_flash.CodeRead(offset, words, word_count);
}
else if (offset >= 0x40000 && (offset + word_count) <= 0x60000)
{
// second 256KB of flash or SRAM
const u32 bank_offset = offset - 0x40000;
if (m_memory_map.sram_select)
{
DebugAssert(bank_offset < SRAM_SIZE);
std::memcpy(words, &m_sram[bank_offset], word_count * sizeof(u32));
}
else
{
m_flash.CodeRead(bank_offset, words, word_count * sizeof(u32));
}
}
else if (offset < 0x60000) [[unlikely]]
{
// partial read of both banks
if (offset < 0x40000)
{
const u32 words_from_first = (0x40000 - offset) / sizeof(u32);
m_flash.CodeRead(offset, words, words_from_first);
words += words_from_first;
word_count -= words_from_first;
offset += words_from_first * sizeof(u32);
}
const u32 words_from_second = std::min(0x60000 - offset, word_count);
const u32 second_bank_offset = offset - 0x40000;
if (m_memory_map.sram_bank)
{
std::memcpy(words, &m_sram[second_bank_offset], words_from_second * sizeof(u32));
}
else
{
m_flash.CodeRead(second_bank_offset + GetFlashUpperBank(), words, words_from_second);
}
words += words_from_second;
word_count -= words_from_second;
if (word_count > 0)
std::memset(words, 0xFF, sizeof(u32) * word_count);
}
else
{
std::memset(words, 0xFF, sizeof(u32) * word_count);
}
}
void PIO::XplorerCart::WriteHandler(u32 offset, u8 value)
{
if (offset < 0x40000)
{
m_flash.Write(offset, value);
}
else if (offset < 0x60000)
{
const u32 bank_offset = offset - 0x40000;
if (m_memory_map.sram_bank)
{
m_sram[bank_offset] = value;
}
else
{
const u32 flash_offset = m_memory_map.sram_bank ? (384 * 1024) : (256 * 1024);
m_flash.Write(flash_offset + bank_offset, value);
}
}
else if (offset == 0x60001)
{
DEV_LOG("Memory map <- 0x{:02X}", value);
m_memory_map.bits = value;
}
else
{
WARNING_LOG("Unhandled Xplorer WRITE 0x{:08X} 0x{:08X}", offset, value);
}
}
namespace PIO {
static std::unique_ptr<PIO::Device> CreateDevice(PIODeviceType type);
} // namespace PIO
std::unique_ptr<PIO::Device> g_pio_device;
PIO::Device::~Device() = default;
bool PIO::Initialize(Error* error)
{
g_pio_device = CreateDevice(g_settings.pio_device_type);
Assert(g_pio_device);
if (!g_pio_device->Initialize(error))
{
g_pio_device.reset();
return false;
}
return true;
}
void PIO::UpdateSettings(const Settings& old_settings)
{
if (g_settings.pio_device_type != old_settings.pio_device_type)
{
Error error;
g_pio_device.reset();
g_pio_device = CreateDevice(g_settings.pio_device_type);
if (!g_pio_device->Initialize(&error))
{
ERROR_LOG("Failed to create new PIO device: {}", error.GetDescription());
g_pio_device = CreateDevice(PIODeviceType::None);
}
}
else
{
g_pio_device->UpdateSettings(old_settings);
}
}
void PIO::Shutdown()
{
g_pio_device.reset();
}
std::unique_ptr<PIO::Device> PIO::CreateDevice(PIODeviceType type)
{
switch (type)
{
case PIODeviceType::None:
return std::make_unique<NullDevice>();
case PIODeviceType::XplorerCart:
return std::make_unique<XplorerCart>();
default:
return nullptr;
}
}
void PIO::Reset()
{
g_pio_device->Reset();
}
bool PIO::DoState(StateWrapper& sw)
{
PIODeviceType device_type = g_settings.pio_device_type;
sw.Do(&device_type);
const size_t pio_state_pos = sw.GetPosition();
u32 pio_state_size = 0;
sw.Do(&pio_state_size);
if (device_type == g_settings.pio_device_type) [[likely]]
{
if (!g_pio_device->DoState(sw))
return false;
// rewrite size field
if (sw.IsWriting())
{
const size_t new_pos = sw.GetPosition();
sw.SetPosition(pio_state_pos);
pio_state_size = static_cast<u32>(new_pos - pio_state_pos);
sw.Do(&pio_state_size);
sw.SetPosition(new_pos);
}
}
else
{
WARNING_LOG("State contains PIO device {}, expected {}", Settings::GetPIODeviceTypeModeName(device_type),
Settings::GetPIODeviceTypeModeName(g_settings.pio_device_type));
g_pio_device->Reset();
sw.SkipBytes(pio_state_size);
}
return !sw.HasError();
}

48
src/core/pio.h Normal file
View File

@ -0,0 +1,48 @@
// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin <stenzek@gmail.com>
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#pragma once
#include "types.h"
#include <memory>
class Error;
class StateWrapper;
struct Settings;
enum class PIODevice : u8;
namespace PIO {
// Exposed so Bus can call the handlers directly. Otherwise calls should go through the functions below.
class Device
{
public:
virtual ~Device();
virtual bool Initialize(Error* error) = 0;
virtual void UpdateSettings(const Settings& old_settings) = 0;
virtual void Reset() = 0;
virtual bool DoState(StateWrapper& sw) = 0;
virtual u8 ReadHandler(u32 offset) = 0;
virtual void CodeReadHandler(u32 offset, u32* words, u32 word_count) = 0;
virtual void WriteHandler(u32 offset, u8 value) = 0;
};
extern bool Initialize(Error* error);
extern void UpdateSettings(const Settings& old_settings);
extern void Shutdown();
extern void Reset();
extern bool DoState(StateWrapper& sw);
} // namespace PIO
extern std::unique_ptr<PIO::Device> g_pio_device;

View File

@ -6,7 +6,7 @@
#include "common/types.h"
static constexpr u32 SAVE_STATE_MAGIC = 0x43435544;
static constexpr u32 SAVE_STATE_VERSION = 76;
static constexpr u32 SAVE_STATE_VERSION = 77;
static constexpr u32 SAVE_STATE_MINIMUM_VERSION = 42;
static_assert(SAVE_STATE_VERSION >= SAVE_STATE_MINIMUM_VERSION);

View File

@ -361,9 +361,6 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
use_old_mdec_routines = si.GetBoolValue("Hacks", "UseOldMDECRoutines", false);
export_shared_memory = si.GetBoolValue("Hacks", "ExportSharedMemory", false);
pcdrv_enable = si.GetBoolValue("PCDrv", "Enabled", false);
pcdrv_enable_writes = si.GetBoolValue("PCDrv", "EnableWrites", false);
pcdrv_root = si.GetStringValue("PCDrv", "Root");
dma_max_slice_ticks = si.GetIntValue("Hacks", "DMAMaxSliceTicks", DEFAULT_DMA_MAX_SLICE_TICKS);
dma_halt_ticks = si.GetIntValue("Hacks", "DMAHaltTicks", DEFAULT_DMA_HALT_TICKS);
@ -481,6 +478,17 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
texture_replacements.config.vram_write_dump_height_threshold =
si.GetUIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold", 128);
pio_device_type = ParsePIODeviceTypeName(
si.GetTinyStringValue("PIO", "DeviceType", GetPIODeviceTypeModeName(DEFAULT_PIO_DEVICE_TYPE)))
.value_or(DEFAULT_PIO_DEVICE_TYPE);
pio_flash_image_path = si.GetStringValue("PIO", "FlashImagePath");
pio_flash_write_enable = si.GetBoolValue("PIO", "FlashImageWriteEnable", false);
pio_switch_active = si.GetBoolValue("PIO", "SwitchActive", true);
pcdrv_enable = si.GetBoolValue("PCDrv", "Enabled", false);
pcdrv_enable_writes = si.GetBoolValue("PCDrv", "EnableWrites", false);
pcdrv_root = si.GetStringValue("PCDrv", "Root");
#ifdef __ANDROID__
// Android users are incredibly silly and don't understand that stretch is in the aspect ratio list...
if (si.GetBoolValue("Display", "Stretch", false))
@ -553,9 +561,7 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetBoolValue("GPU", "ForceRoundTextureCoordinates", gpu_force_round_texcoords);
si.SetBoolValue("GPU", "AccurateBlending", gpu_accurate_blending);
si.SetStringValue("GPU", "TextureFilter", GetTextureFilterName(gpu_texture_filter));
si.SetStringValue(
"GPU", "SpriteTextureFilter",
(gpu_sprite_texture_filter != gpu_texture_filter) ? GetTextureFilterName(gpu_sprite_texture_filter) : "");
si.SetStringValue("GPU", "SpriteTextureFilter", GetTextureFilterName(gpu_sprite_texture_filter));
si.SetStringValue("GPU", "LineDetectMode", GetLineDetectModeName(gpu_line_detect_mode));
si.SetStringValue("GPU", "DownsampleMode", GetDownsampleModeName(gpu_downsample_mode));
si.SetUIntValue("GPU", "DownsampleScale", gpu_downsample_scale);
@ -651,10 +657,6 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetIntValue("Hacks", "GPUMaxRunAhead", gpu_max_run_ahead);
}
si.SetBoolValue("PCDrv", "Enabled", pcdrv_enable);
si.SetBoolValue("PCDrv", "EnableWrites", pcdrv_enable_writes);
si.SetStringValue("PCDrv", "Root", pcdrv_root.c_str());
si.SetBoolValue("BIOS", "TTYLogging", bios_tty_logging);
si.SetBoolValue("BIOS", "PatchFastBoot", bios_patch_fast_boot);
si.SetBoolValue("BIOS", "FastForwardBoot", bios_fast_forward_boot);
@ -748,6 +750,15 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
texture_replacements.config.vram_write_dump_width_threshold);
si.SetUIntValue("TextureReplacements", "DumpVRAMWriteHeightThreshold",
texture_replacements.config.vram_write_dump_height_threshold);
si.SetStringValue("PIO", "DeviceType", GetPIODeviceTypeModeName(pio_device_type));
si.SetStringValue("PIO", "FlashImagePath", pio_flash_image_path.c_str());
si.SetBoolValue("PIO", "FlashImageWriteEnable", pio_flash_write_enable);
si.SetBoolValue("PIO", "SwitchActive", pio_switch_active);
si.SetBoolValue("PCDrv", "Enabled", pcdrv_enable);
si.SetBoolValue("PCDrv", "EnableWrites", pcdrv_enable_writes);
si.SetStringValue("PCDrv", "Root", pcdrv_root.c_str());
}
void Settings::Clear(SettingsInterface& si)
@ -966,10 +977,11 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
g_settings.cdrom_mute_cd_audio = false;
g_settings.texture_replacements.enable_vram_write_replacements = false;
g_settings.use_old_mdec_routines = false;
g_settings.pcdrv_enable = false;
g_settings.bios_patch_fast_boot = false;
g_settings.runahead_frames = 0;
g_settings.rewind_enable = false;
g_settings.pio_device_type = PIODeviceType::None;
g_settings.pcdrv_enable = false;
}
// fast forward boot requires fast boot
@ -2178,6 +2190,42 @@ const char* Settings::GetSaveStateCompressionModeDisplayName(SaveStateCompressio
"SaveStateCompressionMode");
}
static constexpr const std::array s_pio_device_type_names = {
"None",
"XplorerCart",
};
static constexpr const std::array s_pio_device_type_display_names = {
TRANSLATE_DISAMBIG_NOOP("Settings", "None", "PIODeviceType"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Xplorer/Xploder Cartridge", "PIODeviceType"),
};
static_assert(s_pio_device_type_names.size() == static_cast<size_t>(PIODeviceType::MaxCount));
static_assert(s_pio_device_type_display_names.size() == static_cast<size_t>(PIODeviceType::MaxCount));
std::optional<PIODeviceType> Settings::ParsePIODeviceTypeName(const char* str)
{
u32 index = 0;
for (const char* name : s_pio_device_type_names)
{
if (StringUtil::Strcasecmp(name, str) == 0)
return static_cast<PIODeviceType>(index);
index++;
}
return std::nullopt;
}
const char* Settings::GetPIODeviceTypeModeName(PIODeviceType type)
{
return s_pio_device_type_names[static_cast<size_t>(type)];
}
const char* Settings::GetPIODeviceTypeModeDisplayName(PIODeviceType type)
{
return Host::TranslateToCString("Settings", s_pio_device_type_display_names[static_cast<size_t>(type)],
"PIODeviceType");
}
std::string EmuFolders::AppRoot;
std::string EmuFolders::DataRoot;
std::string EmuFolders::Bios;

View File

@ -297,6 +297,11 @@ struct Settings
MultitapMode multitap_mode = DEFAULT_MULTITAP_MODE;
PIODeviceType pio_device_type = DEFAULT_PIO_DEVICE_TYPE;
std::string pio_flash_image_path;
bool pio_switch_active = true;
bool pio_flash_write_enable = false;
std::string pcdrv_root;
bool pcdrv_enable_writes = false;
@ -486,6 +491,10 @@ struct Settings
static const char* GetSaveStateCompressionModeName(SaveStateCompressionMode mode);
static const char* GetSaveStateCompressionModeDisplayName(SaveStateCompressionMode mode);
static std::optional<PIODeviceType> ParsePIODeviceTypeName(const char* str);
static const char* GetPIODeviceTypeModeName(PIODeviceType type);
static const char* GetPIODeviceTypeModeDisplayName(PIODeviceType type);
static constexpr GPURenderer DEFAULT_GPU_RENDERER = GPURenderer::Automatic;
static constexpr GPUTextureFilter DEFAULT_GPU_TEXTURE_FILTER = GPUTextureFilter::Nearest;
static constexpr GPULineDetectMode DEFAULT_GPU_LINE_DETECT_MODE = GPULineDetectMode::Disabled;
@ -535,6 +544,7 @@ struct Settings
static constexpr MemoryCardType DEFAULT_MEMORY_CARD_1_TYPE = MemoryCardType::PerGameTitle;
static constexpr MemoryCardType DEFAULT_MEMORY_CARD_2_TYPE = MemoryCardType::None;
static constexpr MultitapMode DEFAULT_MULTITAP_MODE = MultitapMode::Disabled;
static constexpr PIODeviceType DEFAULT_PIO_DEVICE_TYPE = PIODeviceType::None;
static constexpr s32 DEFAULT_ACHIEVEMENT_NOTIFICATION_TIME = 5;
static constexpr s32 DEFAULT_LEADERBOARD_NOTIFICATION_TIME = 10;

View File

@ -5,4 +5,4 @@
#include "common/types.h"
static constexpr u32 SHADER_CACHE_VERSION = 23;
static constexpr u32 SHADER_CACHE_VERSION = 24;

View File

@ -29,6 +29,7 @@
#include "pad.h"
#include "pcdrv.h"
#include "performance_counters.h"
#include "pio.h"
#include "psf_loader.h"
#include "save_state_version.h"
#include "sio.h"
@ -1321,12 +1322,8 @@ void System::LoadSettings(bool display_osd_messages)
WarnAboutUnsafeSettings();
// apply compatibility settings
if (g_settings.apply_compatibility_settings && !s_state.running_game_serial.empty())
{
const GameDatabase::Entry* entry = GameDatabase::GetEntryForSerial(s_state.running_game_serial);
if (entry)
entry->ApplySettings(g_settings, display_osd_messages);
}
if (g_settings.apply_compatibility_settings && s_state.running_game_entry)
s_state.running_game_entry->ApplySettings(g_settings, display_osd_messages);
// patch overrides take precedence over compat settings
Cheats::ApplySettingOverrides();
@ -1863,6 +1860,15 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
s_state.state = State::Running;
SPU::GetOutputStream()->SetPaused(false);
// try to load the state, if it fails, bail out
if (!parameters.save_state.empty() && !LoadState(parameters.save_state.c_str(), error, false))
{
Error::AddPrefixFmt(error, "Failed to load save state file '{}' for booting:\n",
Path::GetFileName(parameters.save_state));
DestroySystem();
return false;
}
FullscreenUI::OnSystemStarted();
InputManager::UpdateHostMouseMode();
@ -1878,15 +1884,6 @@ bool System::BootSystem(SystemBootParameters parameters, Error* error)
Host::OnSystemStarted();
Host::OnIdleStateChanged();
// try to load the state, if it fails, bail out
if (!parameters.save_state.empty() && !LoadState(parameters.save_state.c_str(), error, false))
{
Error::AddPrefixFmt(error, "Failed to load save state file '{}' for booting:\n",
Path::GetFileName(parameters.save_state));
DestroySystem();
return false;
}
if (parameters.load_image_to_ram || g_settings.cdrom_load_image_to_ram)
CDROM::PrecacheMedia();
@ -1930,6 +1927,9 @@ bool System::Initialize(std::unique_ptr<CDImage> disc, DiscRegion disc_region, b
CPU::Initialize();
CDROM::Initialize();
if (!PIO::Initialize(error))
return false;
// CDROM before GPU, that way we don't modeswitch.
if (disc &&
!CDROM::InsertMedia(std::move(disc), disc_region, s_state.running_game_serial, s_state.running_game_title, error))
@ -2015,6 +2015,7 @@ void System::DestroySystem()
CDROM::Shutdown();
g_gpu.reset();
DMA::Shutdown();
PIO::Shutdown();
CPU::CodeCache::Shutdown();
CPU::PGXP::Shutdown();
CPU::Shutdown();
@ -2552,6 +2553,12 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
if (!sw.DoMarker("SIO") || !SIO::DoState(sw))
return false;
if (sw.GetVersion() >= 77)
{
if (!sw.DoMarker("PIO") || !PIO::DoState(sw))
return false;
}
if (!sw.DoMarker("Events") || !TimingEvents::DoState(sw))
return false;
@ -2632,6 +2639,7 @@ void System::InternalReset()
CPU::PGXP::Initialize();
Bus::Reset();
PIO::Reset();
DMA::Reset();
InterruptController::Reset();
g_gpu->Reset(true);
@ -4495,6 +4503,17 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
UpdateSpeedLimiterState();
}
if (g_settings.multitap_mode != old_settings.multitap_mode)
UpdateMultitaps();
if (g_settings.pio_device_type != old_settings.pio_device_type ||
g_settings.pio_flash_image_path != old_settings.pio_flash_image_path ||
g_settings.pio_flash_write_enable != old_settings.pio_flash_write_enable ||
g_settings.pio_switch_active != old_settings.pio_switch_active)
{
PIO::UpdateSettings(old_settings);
}
if (g_settings.display_show_gpu_usage != old_settings.display_show_gpu_usage)
g_gpu_device->SetGPUTimingEnabled(g_settings.display_show_gpu_usage);
@ -4541,9 +4560,6 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
ImGuiManager::SetScreenMargin(g_settings.display_osd_margin);
}
if (g_settings.multitap_mode != old_settings.multitap_mode)
UpdateMultitaps();
Achievements::UpdateSettings(old_settings);
FullscreenUI::CheckForConfigChanges(old_settings);
@ -4743,6 +4759,8 @@ void System::WarnAboutUnsafeSettings()
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "VRAM write texture replacements disabled."));
if (g_settings.use_old_mdec_routines)
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Use old MDEC routines disabled."));
if (g_settings.pio_device_type != PIODeviceType::None)
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "PIO device removed."));
if (g_settings.pcdrv_enable)
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "PCDrv disabled."));
if (g_settings.bios_patch_fast_boot)

View File

@ -303,3 +303,10 @@ enum class ForceVideoTimingMode : u8
Count,
};
enum class PIODeviceType : u8
{
None,
XplorerCart,
MaxCount,
};

View File

@ -10,6 +10,7 @@
#include "core/bios.h"
#include "core/settings.h"
#include <QtCore/QDir>
#include <QtWidgets/QFileDialog>
#include <algorithm>
@ -25,13 +26,37 @@ BIOSSettingsWidget::BIOSSettingsWidget(SettingsWindow* dialog, QWidget* parent)
connect(m_ui.fastBoot, &QCheckBox::checkStateChanged, this, &BIOSSettingsWidget::onFastBootChanged);
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.pioDeviceType, "PIO", "DeviceType",
&Settings::ParsePIODeviceTypeName, &Settings::GetPIODeviceTypeModeName,
&Settings::GetPIODeviceTypeModeDisplayName,
Settings::DEFAULT_PIO_DEVICE_TYPE, PIODeviceType::MaxCount);
SettingWidgetBinder::BindWidgetToStringSetting(sif, m_ui.pioImagePath, "PIO", "FlashImagePath");
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pioSwitchActive, "PIO", "SwitchActive", true);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.pioImageWrites, "PIO", "FlashImageWriteEnable", false);
connect(m_ui.pioDeviceType, QOverload<int>::of(&QComboBox::currentIndexChanged), this,
&BIOSSettingsWidget::onPIODeviceTypeChanged);
connect(m_ui.pioImagePathBrowse, &QPushButton::clicked, this, &BIOSSettingsWidget::onPIOImagePathBrowseClicked);
onFastBootChanged();
onPIODeviceTypeChanged();
dialog->registerWidgetHelp(m_ui.fastBoot, tr("Fast Boot"), tr("Unchecked"),
tr("Patches the BIOS to skip the console's boot animation. Does not work with all games, "
"but usually safe to enable."));
dialog->registerWidgetHelp(m_ui.enableTTYLogging, tr("Enable TTY Logging"), tr("Unchecked"),
tr("Logs BIOS calls to printf(). Not all games contain debugging messages."));
dialog->registerWidgetHelp(m_ui.pioDeviceType, tr("Device Type"), tr("None"),
tr("Simulates a device plugged into the console's parallel port. Usually these are flash "
"cartridges, and require some sort of image dump to function."));
dialog->registerWidgetHelp(m_ui.pioImagePath, tr("Image Path"), tr("Empty"),
tr("Sets the path to the image used for flash cartridges."));
dialog->registerWidgetHelp(m_ui.pioSwitchActive, tr("Cartridge Switch On"), tr("Checked"),
tr("Simulates the position of the switch on the cartridge. Most cartridges require the "
"switch to be on for it to activate on startup."));
dialog->registerWidgetHelp(
m_ui.pioImageWrites, tr("Allow Image Writes"), tr("None"),
tr("Stores any images made to the cartridge's flash storage back to the host's file system. <strong>This will "
"overwrite your cartridge dump,</strong> you should ensure you have a backup first."));
connect(m_ui.imageNTSCJ, QOverload<int>::of(&QComboBox::currentIndexChanged), [this](int index) {
if (m_dialog->isPerGameSettings() && index == 0)
@ -67,13 +92,13 @@ BIOSSettingsWidget::BIOSSettingsWidget(SettingsWindow* dialog, QWidget* parent)
}
});
connect(m_ui.refresh, &QPushButton::clicked, this, &BIOSSettingsWidget::refreshList);
connect(m_ui.rescan, &QPushButton::clicked, this, &BIOSSettingsWidget::refreshList);
if (!m_dialog->isPerGameSettings())
{
SettingWidgetBinder::BindWidgetToFolderSetting(
sif, m_ui.searchDirectory, m_ui.browseSearchDirectory, tr("Select BIOS Directory"), m_ui.openSearchDirectory,
nullptr, "BIOS", "SearchDirectory", Path::Combine(EmuFolders::DataRoot, "bios"));
sif, m_ui.searchDirectory, m_ui.browseSearchDirectory, tr("Select BIOS Directory"), m_ui.searchDirectoryOpen,
m_ui.searchDirectoryReset, "BIOS", "SearchDirectory", Path::Combine(EmuFolders::DataRoot, "bios"));
connect(m_ui.searchDirectory, &QLineEdit::textChanged, this, &BIOSSettingsWidget::refreshList);
}
else
@ -180,3 +205,31 @@ void BIOSSettingsWidget::setDropDownValue(QComboBox* cb, const std::optional<std
cb->addItem(qname, QVariant(qname));
cb->setCurrentIndex(cb->count() - 1);
}
void BIOSSettingsWidget::onPIODeviceTypeChanged()
{
const PIODeviceType type =
Settings::ParsePIODeviceTypeName(
m_dialog
->getEffectiveStringValue("PIO", "DeviceType",
Settings::GetPIODeviceTypeModeName(Settings::DEFAULT_PIO_DEVICE_TYPE))
.c_str())
.value_or(Settings::DEFAULT_PIO_DEVICE_TYPE);
const bool has_image = (type == PIODeviceType::XplorerCart);
const bool has_switch = (type == PIODeviceType::XplorerCart);
m_ui.pioImagePathLabel->setEnabled(has_image);
m_ui.pioImagePath->setEnabled(has_image);
m_ui.pioImagePathBrowse->setEnabled(has_image);
m_ui.pioImageWrites->setEnabled(has_image);
m_ui.pioSwitchActive->setEnabled(has_switch);
}
void BIOSSettingsWidget::onPIOImagePathBrowseClicked()
{
const QString path = QDir::toNativeSeparators(
QFileDialog::getOpenFileName(QtUtils::GetRootWidget(this), tr("Select PIO Image"), m_ui.pioImagePath->text()));
if (path.isEmpty())
return;
m_ui.pioImagePath->setText(path);
}

View File

@ -30,6 +30,8 @@ public:
private Q_SLOTS:
void refreshList();
void onFastBootChanged();
void onPIODeviceTypeChanged();
void onPIOImagePathBrowseClicked();
private:
Ui::BIOSSettingsWidget m_ui;

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>529</width>
<height>330</height>
<width>732</width>
<height>470</height>
</rect>
</property>
<layout class="QVBoxLayout" name="mainLayout">
@ -29,8 +29,32 @@
<string>BIOS Selection</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="1">
<widget class="QComboBox" name="imageNTSCJ">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>NTSC-J (Japan):</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>NTSC-U/C (US/Canada):</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="imageNTSCU">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="imagePAL">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -46,22 +70,8 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>NTSC-J (Japan):</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>NTSC-U/C (US/Canada):</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QComboBox" name="imagePAL">
<item row="0" column="1">
<widget class="QComboBox" name="imageNTSCJ">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
@ -70,47 +80,6 @@
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QComboBox" name="imageNTSCU">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="openSearchDirectory">
<property name="text">
<string>Open in Explorer...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="refresh">
<property name="text">
<string>Refresh List</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
@ -130,7 +99,17 @@
</property>
</widget>
</item>
<item row="1" column="0" colspan="2">
<item row="0" column="2">
<widget class="QToolButton" name="rescan">
<property name="toolTip">
<string>Refresh BIOS List</string>
</property>
<property name="icon">
<iconset theme="refresh-line"/>
</property>
</widget>
</item>
<item row="1" column="0" colspan="3">
<layout class="QHBoxLayout" name="directoryGroupBoxHorizontalLayout">
<item>
<widget class="QLineEdit" name="searchDirectory"/>
@ -142,6 +121,20 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="searchDirectoryOpen">
<property name="text">
<string>Open...</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="searchDirectoryReset">
<property name="text">
<string>Reset</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
@ -177,6 +170,64 @@
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="pioGroupBox">
<property name="title">
<string>Parallel Port</string>
</property>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Device Type:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QComboBox" name="pioDeviceType"/>
</item>
<item row="1" column="0" colspan="2">
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="0,1,0">
<item>
<widget class="QLabel" name="pioImagePathLabel">
<property name="text">
<string>Image Path:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="pioImagePath"/>
</item>
<item>
<widget class="QPushButton" name="pioImagePathBrowse">
<property name="text">
<string>Browse...</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QCheckBox" name="pioSwitchActive">
<property name="text">
<string>Cartridge Switch On</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QCheckBox" name="pioImageWrites">
<property name="text">
<string>Allow Image Writes</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
@ -185,7 +236,7 @@
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
<height>1</height>
</size>
</property>
</spacer>

View File

@ -26,6 +26,7 @@ ISOBrowserWindow::ISOBrowserWindow(QWidget* parent) : QWidget(parent)
m_ui.setupUi(this);
m_ui.splitter->setSizes({200, 600});
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
enableUi(false);
connect(m_ui.openFile, &QAbstractButton::clicked, this, &ISOBrowserWindow::onOpenFileClicked);
connect(m_ui.extract, &QAbstractButton::clicked, this, &ISOBrowserWindow::onExtractClicked);
@ -69,6 +70,7 @@ bool ISOBrowserWindow::tryOpenFile(const QString& path, Error* error /*= nullptr
m_iso = std::move(new_reader);
m_ui.openPath->setText(QString::fromStdString(native_path));
setWindowTitle(tr("ISO Browser - %1").arg(QtUtils::StringViewToQString(Path::GetFileName(native_path))));
enableUi(true);
populateDirectories();
populateFiles(QString());
return true;
@ -248,6 +250,15 @@ QTreeWidgetItem* ISOBrowserWindow::findDirectoryItemForPath(const QString& path,
return nullptr;
}
void ISOBrowserWindow::enableUi(bool enabled)
{
m_ui.directoryView->setEnabled(enabled);
m_ui.fileView->setEnabled(enabled);
if (!enabled)
m_ui.extract->setEnabled(enabled);
}
void ISOBrowserWindow::populateDirectories()
{
m_ui.directoryView->clear();

View File

@ -33,6 +33,7 @@ private Q_SLOTS:
void resizeFileListColumns();
private:
void enableUi(bool enabled);
void populateDirectories();
void populateSubdirectories(std::string_view dir, QTreeWidgetItem* parent);
void populateFiles(const QString& path);

View File

@ -11,7 +11,7 @@
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
<string>ISO Browser</string>
</property>
<property name="windowIcon">
<iconset resource="resources/duckstation-qt.qrc">

View File

@ -2008,6 +2008,7 @@ void MainWindow::connectSignals()
connect(m_ui.actionCheckForUpdates, &QAction::triggered, this, &MainWindow::onCheckForUpdatesActionTriggered);
connect(m_ui.actionMemoryCardEditor, &QAction::triggered, this, &MainWindow::onToolsMemoryCardEditorTriggered);
connect(m_ui.actionMemoryScanner, &QAction::triggered, this, &MainWindow::onToolsMemoryScannerTriggered);
connect(m_ui.actionISOBrowser, &QAction::triggered, this, &MainWindow::onToolsISOBrowserTriggered);
connect(m_ui.actionCoverDownloader, &QAction::triggered, this, &MainWindow::onToolsCoverDownloaderTriggered);
connect(m_ui.actionMediaCapture, &QAction::toggled, this, &MainWindow::onToolsMediaCaptureToggled);
connect(m_ui.actionCaptureGPUFrame, &QAction::triggered, g_emu_thread, &EmuThread::captureGPUFrameDump);
@ -2081,6 +2082,10 @@ void MainWindow::connectSignals()
&Settings::GetLogLevelName, &Settings::GetLogLevelDisplayName,
Settings::DEFAULT_LOG_LEVEL, Log::Level::MaxCount);
connect(m_ui.menuLogChannels, &QMenu::aboutToShow, this, &MainWindow::onDebugLogChannelsMenuAboutToShow);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionLogToSystemConsole, "Logging", "LogToConsole",
false);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionLogToWindow, "Logging", "LogToWindow", false);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionLogTimestamps, "Logging", "LogTimestamps", true);
SettingWidgetBinder::BindWidgetToBoolSetting(nullptr, m_ui.actionEnableSafeMode, "Main", "DisableAllEnhancements",
false);
@ -2706,6 +2711,13 @@ void MainWindow::onToolsMemoryScannerTriggered()
QtUtils::ShowOrRaiseWindow(m_memory_scanner_window);
}
void MainWindow::onToolsISOBrowserTriggered()
{
ISOBrowserWindow* ib = new ISOBrowserWindow();
ib->setAttribute(Qt::WA_DeleteOnClose);
ib->show();
}
void MainWindow::openCPUDebugger()
{
if (!m_debugger_window)

View File

@ -186,6 +186,7 @@ private Q_SLOTS:
void onCheckForUpdatesActionTriggered();
void onToolsMemoryCardEditorTriggered();
void onToolsMemoryScannerTriggered();
void onToolsISOBrowserTriggered();
void onToolsCoverDownloaderTriggered();
void onToolsMediaCaptureToggled(bool checked);
void onToolsOpenDataDirectoryTriggered();

View File

@ -170,6 +170,10 @@
<addaction name="separator"/>
<addaction name="menuLogLevel"/>
<addaction name="menuLogChannels"/>
<addaction name="actionLogToSystemConsole"/>
<addaction name="actionLogToWindow"/>
<addaction name="actionLogToFile"/>
<addaction name="actionLogTimestamps"/>
<addaction name="separator"/>
<addaction name="actionCPUDebugger"/>
<addaction name="separator"/>
@ -225,6 +229,7 @@
<addaction name="actionCoverDownloader"/>
<addaction name="separator"/>
<addaction name="actionMemoryScanner"/>
<addaction name="actionISOBrowser"/>
<addaction name="separator"/>
<addaction name="actionMediaCapture"/>
<addaction name="actionCaptureGPUFrame"/>
@ -925,6 +930,43 @@
<string>Capture GPU Frame</string>
</property>
</action>
<action name="actionLogTimestamps">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Log Timestamps</string>
</property>
</action>
<action name="actionLogToSystemConsole">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Log To System Console</string>
</property>
</action>
<action name="actionLogToWindow">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Log To Window</string>
</property>
</action>
<action name="actionLogToFile">
<property name="checkable">
<bool>true</bool>
</property>
<property name="text">
<string>Log To File</string>
</property>
</action>
<action name="actionISOBrowser">
<property name="text">
<string>ISO Browser</string>
</property>
</action>
</widget>
<resources>
<include location="resources/duckstation-qt.qrc"/>

View File

@ -284,16 +284,17 @@ PostProcessingShaderConfigWidget::~PostProcessingShaderConfigWidget() = default;
void PostProcessingShaderConfigWidget::updateConfigForOption(const PostProcessing::ShaderOption& option)
{
const auto lock = Host::GetSettingsLock();
auto lock = Host::GetSettingsLock();
SettingsInterface& si = m_widget->getSettingsInterfaceToUpdate();
PostProcessing::Config::SetStageOption(si, m_section, m_stage_index, option);
lock.unlock();
m_widget->commitSettingsUpdate();
}
void PostProcessingShaderConfigWidget::onResetDefaultsClicked()
{
{
const auto lock = Host::GetSettingsLock();
auto lock = Host::GetSettingsLock();
SettingsInterface& si = m_widget->getSettingsInterfaceToUpdate();
for (PostProcessing::ShaderOption& option : m_options)
{
@ -303,6 +304,7 @@ void PostProcessingShaderConfigWidget::onResetDefaultsClicked()
option.value = option.default_value;
PostProcessing::Config::UnsetStageOption(si, m_section, m_stage_index, option);
}
lock.unlock();
m_widget->commitSettingsUpdate();
}

View File

@ -1475,28 +1475,6 @@ void EmuThread::setAudioOutputMuted(bool muted)
System::UpdateVolume();
}
void EmuThread::startDumpingAudio()
{
if (!isCurrentThread())
{
QMetaObject::invokeMethod(this, "startDumpingAudio", Qt::QueuedConnection);
return;
}
// System::StartDumpingAudio();
}
void EmuThread::stopDumpingAudio()
{
if (!isCurrentThread())
{
QMetaObject::invokeMethod(this, "stopDumpingAudio", Qt::QueuedConnection);
return;
}
// System::StopDumpingAudio();
}
void EmuThread::singleStepCPU()
{
if (!isCurrentThread())
@ -2045,10 +2023,7 @@ std::optional<std::time_t> Host::GetResourceFileTimestamp(std::string_view filen
void Host::CommitBaseSettingChanges()
{
if (g_emu_thread->isCurrentThread())
QtHost::RunOnUIThread([]() { QtHost::QueueSettingsSave(); });
else
QtHost::QueueSettingsSave();
QtHost::QueueSettingsSave();
}
std::optional<WindowInfo> Host::AcquireRenderWindow(RenderAPI render_api, bool fullscreen, bool exclusive_fullscreen,
@ -2186,7 +2161,7 @@ void QtHost::SaveSettings()
void QtHost::QueueSettingsSave()
{
if (g_emu_thread->isCurrentThread())
if (!QThread::isMainThread())
{
QtHost::RunOnUIThread(QueueSettingsSave);
return;

View File

@ -194,8 +194,6 @@ public Q_SLOTS:
void undoLoadState();
void setAudioOutputVolume(int volume, int fast_forward_volume);
void setAudioOutputMuted(bool muted);
void startDumpingAudio();
void stopDumpingAudio();
void singleStepCPU();
void dumpRAM(const QString& filename);
void dumpVRAM(const QString& filename);

File diff suppressed because it is too large Load Diff

View File

@ -282,20 +282,10 @@ bool OpenGLDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, Featur
return false;
}
#if 0
// Is this needed?
m_window_info = m_gl_context->GetWindowInfo();
m_vsync_mode = ;
#endif
const bool opengl_is_available =
((!m_gl_context->IsGLES() && (GLAD_GL_VERSION_3_0 || GLAD_GL_ARB_uniform_buffer_object)) ||
(m_gl_context->IsGLES() && GLAD_GL_ES_VERSION_3_1));
if (!opengl_is_available)
// Context version restrictions are mostly fine here, but we still need to check for UBO for GL3.0.
if (!m_gl_context->IsGLES() && !GLAD_GL_ARB_uniform_buffer_object)
{
Host::ReportErrorAsync(TRANSLATE_SV("GPUDevice", "Error"),
TRANSLATE_SV("GPUDevice", "OpenGL renderer unavailable, your driver or hardware is not "
"recent enough. OpenGL 3.1 or OpenGL ES 3.1 is required."));
Error::SetStringView(error, "OpenGL 3.1 or GL_ARB_uniform_buffer_object is required.");
m_gl_context.reset();
return false;
}
@ -1043,17 +1033,6 @@ void OpenGLDevice::UnbindPipeline(const OpenGLPipeline* pl)
}
}
ALWAYS_INLINE_RELEASE void OpenGLDevice::SetVertexBufferOffsets(u32 base_vertex)
{
const OpenGLPipeline::VertexArrayCacheKey& va = m_last_vao->first;
const size_t stride = va.vertex_attribute_stride;
for (u32 i = 0; i < va.num_vertex_attributes; i++)
{
glBindVertexBuffer(i, m_vertex_buffer->GetGLBufferId(), base_vertex * stride + va.vertex_attributes[i].offset,
static_cast<GLsizei>(stride));
}
}
void OpenGLDevice::Draw(u32 vertex_count, u32 base_vertex)
{
s_stats.num_draws++;

View File

@ -23,6 +23,8 @@
LOG_CHANNEL(GPUDevice);
namespace {
struct PipelineDiskCacheFooter
{
u32 version;
@ -43,6 +45,28 @@ struct PipelineDiskCacheIndexEntry
};
static_assert(sizeof(PipelineDiskCacheIndexEntry) == 112); // No padding
struct VAMapping
{
GLenum type;
GLboolean normalized;
GLboolean integer;
};
} // namespace
static constexpr const std::array<VAMapping, static_cast<u8>(GPUPipeline::VertexAttribute::Type::MaxCount)>
s_vao_format_mapping = {{
{GL_FLOAT, GL_FALSE, GL_FALSE}, // Float
{GL_UNSIGNED_BYTE, GL_FALSE, GL_TRUE}, // UInt8
{GL_BYTE, GL_FALSE, GL_TRUE}, // SInt8
{GL_UNSIGNED_BYTE, GL_TRUE, GL_FALSE}, // UNorm8
{GL_UNSIGNED_SHORT, GL_FALSE, GL_TRUE}, // UInt16
{GL_SHORT, GL_FALSE, GL_TRUE}, // SInt16
{GL_UNSIGNED_SHORT, GL_TRUE, GL_FALSE}, // UNorm16
{GL_UNSIGNED_INT, GL_FALSE, GL_TRUE}, // UInt32
{GL_INT, GL_FALSE, GL_TRUE}, // SInt32
}};
static GLenum GetGLShaderType(GPUShaderStage stage)
{
static constexpr std::array<GLenum, static_cast<u32>(GPUShaderStage::MaxCount)> mapping = {{
@ -375,14 +399,25 @@ GLuint OpenGLDevice::CompileProgram(const GPUPipeline::GraphicsConfig& plconfig,
}
}
glBindFragDataLocation(program_id, 0, "o_col0");
// Output colour is implicit in GLES.
const bool is_gles = m_gl_context->IsGLES();
if (!is_gles)
glBindFragDataLocation(program_id, 0, "o_col0");
if (m_features.dual_source_blend)
{
if (GLAD_GL_VERSION_3_3 || GLAD_GL_ARB_blend_func_extended)
{
if (is_gles)
glBindFragDataLocationIndexed(program_id, 0, 0, "o_col0");
glBindFragDataLocationIndexed(program_id, 1, 0, "o_col1");
}
else if (GLAD_GL_EXT_blend_func_extended)
{
if (is_gles)
glBindFragDataLocationIndexedEXT(program_id, 0, 0, "o_col1");
glBindFragDataLocationIndexedEXT(program_id, 1, 0, "o_col1");
}
}
}
@ -515,29 +550,10 @@ GLuint OpenGLDevice::CreateVAO(std::span<const GPUPipeline::VertexAttribute> att
m_vertex_buffer->Bind();
m_index_buffer->Bind();
struct VAMapping
{
GLenum type;
GLboolean normalized;
GLboolean integer;
};
static constexpr const std::array<VAMapping, static_cast<u8>(GPUPipeline::VertexAttribute::Type::MaxCount)>
format_mapping = {{
{GL_FLOAT, GL_FALSE, GL_FALSE}, // Float
{GL_UNSIGNED_BYTE, GL_FALSE, GL_TRUE}, // UInt8
{GL_BYTE, GL_FALSE, GL_TRUE}, // SInt8
{GL_UNSIGNED_BYTE, GL_TRUE, GL_FALSE}, // UNorm8
{GL_UNSIGNED_SHORT, GL_FALSE, GL_TRUE}, // UInt16
{GL_SHORT, GL_FALSE, GL_TRUE}, // SInt16
{GL_UNSIGNED_SHORT, GL_TRUE, GL_FALSE}, // UNorm16
{GL_UNSIGNED_INT, GL_FALSE, GL_TRUE}, // UInt32
{GL_INT, GL_FALSE, GL_TRUE}, // SInt32
}};
for (u32 i = 0; i < static_cast<u32>(attributes.size()); i++)
{
const GPUPipeline::VertexAttribute& va = attributes[i];
const VAMapping& m = format_mapping[static_cast<u8>(va.type.GetValue())];
const VAMapping& m = s_vao_format_mapping[static_cast<u8>(va.type.GetValue())];
const void* ptr = reinterpret_cast<void*>(static_cast<uintptr_t>(va.offset.GetValue()));
glEnableVertexAttribArray(i);
if (m.integer)
@ -552,6 +568,35 @@ GLuint OpenGLDevice::CreateVAO(std::span<const GPUPipeline::VertexAttribute> att
return vao;
}
void OpenGLDevice::SetVertexBufferOffsets(u32 base_vertex)
{
const OpenGLPipeline::VertexArrayCacheKey& va = m_last_vao->first;
const u32 stride = va.vertex_attribute_stride;
const u32 base_vertex_start = base_vertex * stride;
if (glBindVertexBuffer) [[likely]]
{
for (u32 i = 0; i < va.num_vertex_attributes; i++)
{
glBindVertexBuffer(i, m_vertex_buffer->GetGLBufferId(), base_vertex_start + va.vertex_attributes[i].offset,
static_cast<GLsizei>(stride));
}
}
else
{
for (u32 i = 0; i < va.num_vertex_attributes; i++)
{
const GPUPipeline::VertexAttribute& attrib = va.vertex_attributes[i];
const void* ptr = reinterpret_cast<void*>(static_cast<uintptr_t>(base_vertex_start + attrib.offset));
const VAMapping& m = s_vao_format_mapping[static_cast<u8>(attrib.type.GetValue())];
if (m.integer)
glVertexAttribIPointer(i, attrib.components, m.type, stride, ptr);
else
glVertexAttribPointer(i, attrib.components, m.type, m.normalized, stride, ptr);
}
}
}
void OpenGLDevice::UnrefVAO(const OpenGLPipeline::VertexArrayCacheKey& key)
{
auto it = m_vao_cache.find(key);

View File

@ -298,6 +298,7 @@ void ShaderGen::WriteHeader(std::stringstream& ss, bool enable_rov /* = false */
ss << "#define LOAD_TEXTURE_BUFFER(name, index) texelFetch(name, index)\n";
ss << "#define BEGIN_ARRAY(type, size) type[size](\n";
ss << "#define END_ARRAY )\n";
ss << "#define VECTOR_BROADCAST(type, value) (type(value))\n";
ss << "float saturate(float value) { return clamp(value, 0.0, 1.0); }\n";
ss << "float2 saturate(float2 value) { return clamp(value, float2(0.0, 0.0), float2(1.0, 1.0)); }\n";
@ -344,6 +345,7 @@ void ShaderGen::WriteHeader(std::stringstream& ss, bool enable_rov /* = false */
ss << "#define LOAD_TEXTURE_BUFFER(name, index) name.Load(index)\n";
ss << "#define BEGIN_ARRAY(type, size) {\n";
ss << "#define END_ARRAY }\n";
ss << "#define VECTOR_BROADCAST(type, value) ((type)(value))\n";
}
ss << "\n";

View File

@ -35,6 +35,7 @@ public:
ALWAYS_INLINE bool IsWriting() const { return (m_mode == Mode::Write); }
ALWAYS_INLINE u32 GetVersion() const { return m_version; }
ALWAYS_INLINE size_t GetPosition() const { return m_pos; }
ALWAYS_INLINE void SetPosition(size_t pos) { m_pos = pos; }
/// Overload for integral or floating-point types. Writes bytes as-is.
template<typename T, std::enable_if_t<std::is_integral_v<T> || std::is_floating_point_v<T>, int> = 0>