Merge branch 'stenzek:master' into master
This commit is contained in:
commit
c64eda8fea
|
@ -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:
|
||||
|
|
|
@ -50,6 +50,7 @@
|
|||
X(PlatformMisc) \
|
||||
X(PostProcessing) \
|
||||
X(ProgressCallback) \
|
||||
X(PIO) \
|
||||
X(ReShadeFXShader) \
|
||||
X(Recompiler) \
|
||||
X(SDL) \
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 = {};
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.",
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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();
|
||||
}
|
|
@ -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;
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
|
||||
#include "common/types.h"
|
||||
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 23;
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 24;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -303,3 +303,10 @@ enum class ForceVideoTimingMode : u8
|
|||
|
||||
Count,
|
||||
};
|
||||
|
||||
enum class PIODeviceType : u8
|
||||
{
|
||||
None,
|
||||
XplorerCart,
|
||||
MaxCount,
|
||||
};
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,8 @@ public:
|
|||
private Q_SLOTS:
|
||||
void refreshList();
|
||||
void onFastBootChanged();
|
||||
void onPIODeviceTypeChanged();
|
||||
void onPIOImagePathBrowseClicked();
|
||||
|
||||
private:
|
||||
Ui::BIOSSettingsWidget m_ui;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -186,6 +186,7 @@ private Q_SLOTS:
|
|||
void onCheckForUpdatesActionTriggered();
|
||||
void onToolsMemoryCardEditorTriggered();
|
||||
void onToolsMemoryScannerTriggered();
|
||||
void onToolsISOBrowserTriggered();
|
||||
void onToolsCoverDownloaderTriggered();
|
||||
void onToolsMediaCaptureToggled(bool checked);
|
||||
void onToolsOpenDataDirectoryTriggered();
|
||||
|
|
|
@ -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"/>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
@ -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++;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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>
|
||||
|
|
Loading…
Reference in New Issue