Merge branch 'stenzek:master' into master
This commit is contained in:
commit
ef7d4f3fb7
|
@ -748,12 +748,6 @@ SLPS-02348:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02348
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -3626,12 +3620,6 @@ SLPS-02367:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02367
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -19203,12 +19191,6 @@ SLPS-02375:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02375
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -20380,7 +20362,6 @@ SLPS-00888:
|
|||
codes:
|
||||
- SLPS-00888
|
||||
- SLPS-91103
|
||||
- SLPS-91108
|
||||
metadata:
|
||||
publisher: "Axela"
|
||||
developer: "Break"
|
||||
|
@ -26646,7 +26627,6 @@ SLPM-86241:
|
|||
codes:
|
||||
- SLPM-86241
|
||||
- SLPM-86561
|
||||
- SLPM-86244
|
||||
metadata:
|
||||
publisher: "Success"
|
||||
developer: "Success"
|
||||
|
@ -26675,7 +26655,6 @@ SLPM-86242:
|
|||
codes:
|
||||
- SLPM-86242
|
||||
- SLPM-86562
|
||||
- SLPM-86244
|
||||
metadata:
|
||||
publisher: "Success"
|
||||
developer: "Success"
|
||||
|
@ -26704,7 +26683,6 @@ SLPM-86243:
|
|||
codes:
|
||||
- SLPM-86243
|
||||
- SLPM-86563
|
||||
- SLPM-86244
|
||||
metadata:
|
||||
publisher: "Success"
|
||||
developer: "Success"
|
||||
|
@ -26719,14 +26697,12 @@ SLPM-86243:
|
|||
vibration: false
|
||||
multitap: false
|
||||
linkCable: false
|
||||
SLPM-86564:
|
||||
SLPM-86244:
|
||||
name: "Cinema Eikaiwa Series Dai-6-dan - Ai no Hate ni (Japan) (Disc 4)"
|
||||
discSet:
|
||||
name: "Cinema Eikaiwa Series Dai-6-dan - Ai no Hate ni (Japan)"
|
||||
serials:
|
||||
- SLPM-86241
|
||||
- SLPM-86242
|
||||
- SLPM-86243
|
||||
- SLPM-86244
|
||||
- SLPM-86564
|
||||
controllers:
|
||||
- DigitalController
|
||||
|
@ -27268,12 +27244,6 @@ SLPS-02354:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02354
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -27350,12 +27320,6 @@ SLPS-02353:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02353
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -29742,7 +29706,6 @@ SLPS-00782:
|
|||
codes:
|
||||
- SLPS-00782
|
||||
- SLPS-91104
|
||||
- SLPS-91108
|
||||
metadata:
|
||||
publisher: "Human Entertaiment"
|
||||
developer: "Masterpiece / Access"
|
||||
|
@ -30987,7 +30950,6 @@ LSP-905109:
|
|||
- LSP-905109
|
||||
- LSP90510901
|
||||
- LSP-90510901
|
||||
- LSP90510901
|
||||
metadata:
|
||||
publisher: "Lightspan"
|
||||
developer: "Lightspan"
|
||||
|
@ -36642,7 +36604,6 @@ SLPM-86378:
|
|||
codes:
|
||||
- SLPM-86378
|
||||
- SLPM-86705
|
||||
- SLPM-86375
|
||||
metadata:
|
||||
publisher: "Enix Corporation"
|
||||
developer: "tri-Ace"
|
||||
|
@ -41310,9 +41271,6 @@ SLUS-01537:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLUS-01537
|
||||
- SLUS-07013
|
||||
metadata:
|
||||
publisher: "Disney Interactive"
|
||||
developer: "Traveller's Tales / Eurocom Developments Ltd. / Doki Denki"
|
||||
|
@ -41693,9 +41651,6 @@ SLUS-01538:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLUS-01538
|
||||
- SLUS-07013
|
||||
metadata:
|
||||
publisher: "Disney Interactive"
|
||||
developer: "Traveller's Tales / Eurocom Developments Ltd. / Doki Denki"
|
||||
|
@ -44122,12 +44077,6 @@ SLPS-02369:
|
|||
name: "Doraemon 2 - Sos! Otogi no Kuni [Reprint]"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02369
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -44342,10 +44291,6 @@ SLPS-01385:
|
|||
codes:
|
||||
- SLPS-01385
|
||||
- SLPS-02363
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -55787,12 +55732,6 @@ SLPS-02371:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02371
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -62032,8 +61971,6 @@ SLPM-87331:
|
|||
# Pick your poison here. Disabling true colour fixes the sprite backgrounds,
|
||||
# but if you're upscaling, leaves junk around the edges.
|
||||
- ForceSoftwareRendererForReadbacks
|
||||
codes:
|
||||
- SLPM-87331
|
||||
metadata:
|
||||
publisher: "Squaresoft"
|
||||
developer: "Squaresoft"
|
||||
|
@ -66848,6 +66785,8 @@ SLPS-00048:
|
|||
name: "Gokuu Densetsu - Magic Beast Warriors (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-E2AD78E23425A0AF
|
||||
metadata:
|
||||
publisher: "Alyume System"
|
||||
developer: "Alyume System"
|
||||
|
@ -68647,10 +68586,6 @@ SLPM-87333:
|
|||
- SLPM-87333
|
||||
- SLPS-02380
|
||||
- SLPS-03240
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -71210,10 +71145,6 @@ SLPS-00578:
|
|||
codes:
|
||||
- SLPS-00578
|
||||
- SLPS-02340
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -71241,10 +71172,6 @@ SLPS-00579:
|
|||
codes:
|
||||
- SLPS-00579
|
||||
- SLPS-02341
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -71272,10 +71199,6 @@ SLPS-00580:
|
|||
codes:
|
||||
- SLPS-00580
|
||||
- SLPS-02342
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Ving"
|
||||
developer: "Ving"
|
||||
|
@ -71866,12 +71789,6 @@ SLPS-02374:
|
|||
name: "Heiwa Pachinko Graffiti Vol. 1 (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02374
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Aqua Rouge"
|
||||
developer: "Aqua Rouge"
|
||||
|
@ -72469,8 +72386,7 @@ SCPS-10012:
|
|||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- SCPS-10012
|
||||
- SCPS-91016
|
||||
- HASH-1438F7A2BB29BE8A
|
||||
metadata:
|
||||
publisher: "Sony"
|
||||
developer: "Yuke's / Sugar & Rockets"
|
||||
|
@ -72849,12 +72765,6 @@ SLPS-02351:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02351
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "MediaWorks"
|
||||
developer: "Japan Media Programming"
|
||||
|
@ -73135,12 +73045,6 @@ SLPS-02355:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02355
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Sunsoft"
|
||||
developer: "Sunsoft"
|
||||
|
@ -74569,7 +74473,6 @@ SLPM-86377:
|
|||
codes:
|
||||
- SLPM-86377
|
||||
- SLPS-01547
|
||||
- SLPM-86375
|
||||
metadata:
|
||||
publisher: "Enix Corporation"
|
||||
developer: "tri-Ace"
|
||||
|
@ -75552,12 +75455,6 @@ SLPS-02358:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02358
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "KSS"
|
||||
developer: "KSS"
|
||||
|
@ -85098,12 +84995,6 @@ SLPS-02370:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02370
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Kodansha"
|
||||
developer: "Kodansha"
|
||||
|
@ -90518,10 +90409,6 @@ SLPS-02344:
|
|||
codes:
|
||||
- SLPS-02344
|
||||
- SLPS-02926
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Warashi"
|
||||
developer: "Same Creative Inc"
|
||||
|
@ -90544,10 +90431,6 @@ SLPS-02343:
|
|||
codes:
|
||||
- SLPS-02343
|
||||
- SLPS-02925
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Warashi"
|
||||
developer: "Same Creative Inc"
|
||||
|
@ -92682,12 +92565,6 @@ SLPS-02376:
|
|||
settings:
|
||||
dmaMaxSliceTicks: 100
|
||||
dmaHaltTicks: 200
|
||||
codes:
|
||||
- SLPS-02376
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Nippon Ichi Software"
|
||||
developer: "Nippon Ichi Software"
|
||||
|
@ -96983,6 +96860,8 @@ SLPS-00128:
|
|||
name: "Makeruna! Makendou 2 (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-F267FA5D5A1CB78B
|
||||
metadata:
|
||||
publisher: "Datam Polystar"
|
||||
developer: "Fill In Cafe"
|
||||
|
@ -97167,7 +97046,6 @@ SLPS-01136:
|
|||
codes:
|
||||
- SLPS-01136
|
||||
- SLPS-91100
|
||||
- SLPS-91108
|
||||
metadata:
|
||||
publisher: "Axela"
|
||||
developer: "Break"
|
||||
|
@ -97195,7 +97073,6 @@ SLPS-01137:
|
|||
codes:
|
||||
- SLPS-01137
|
||||
- SLPS-91101
|
||||
- SLPS-91108
|
||||
metadata:
|
||||
publisher: "Axela"
|
||||
developer: "Break"
|
||||
|
@ -97223,7 +97100,6 @@ SLPS-01138:
|
|||
codes:
|
||||
- SLPS-01138
|
||||
- SLPS-91102
|
||||
- SLPS-91108
|
||||
metadata:
|
||||
publisher: "Axela"
|
||||
developer: "Break"
|
||||
|
@ -98137,12 +98013,6 @@ SLPS-02368:
|
|||
- DigitalController
|
||||
traits:
|
||||
- DisableAutoAnalogMode # Analog sticks do nothing.
|
||||
codes:
|
||||
- SLPS-02368
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Capcom"
|
||||
developer: "Capcom / Value Wave"
|
||||
|
@ -101336,7 +101206,6 @@ SLPM-86114:
|
|||
codes:
|
||||
- SLPM-86114
|
||||
- SLPM-87411
|
||||
- SLPM-87413
|
||||
metadata:
|
||||
publisher: "Konami"
|
||||
developer: "KCE Japan"
|
||||
|
@ -101366,7 +101235,6 @@ SLPM-86115:
|
|||
codes:
|
||||
- SLPM-86115
|
||||
- SLPM-87412
|
||||
- SLPM-87413
|
||||
metadata:
|
||||
publisher: "Konami"
|
||||
developer: "KCE Japan"
|
||||
|
@ -101519,6 +101387,7 @@ SCPS-45414:
|
|||
- SLPM-86249
|
||||
- SLPM-86472
|
||||
- SLPM-87326
|
||||
- SLPM-87413
|
||||
metadata:
|
||||
publisher: "Konami"
|
||||
developer: "KCE Japan"
|
||||
|
@ -114866,6 +114735,8 @@ SLPS-00050:
|
|||
name: "Night Striker (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-6AA77469D08E6515
|
||||
metadata:
|
||||
publisher: "Xing Entertainment"
|
||||
developer: "Xing Entertainment/ Fill in Cafe"
|
||||
|
@ -116147,9 +116018,6 @@ SLPM-86376:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPM-86376
|
||||
- SLPM-86375
|
||||
metadata:
|
||||
publisher: "Enix Corporation"
|
||||
developer: "tri-Ace"
|
||||
|
@ -117806,9 +117674,7 @@ SLPS-00093:
|
|||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-00093
|
||||
- SLPS-01568
|
||||
- SLPS-03116
|
||||
- HASH-AFE12D37382D032D
|
||||
metadata:
|
||||
publisher: "Sunsoft"
|
||||
developer: "Sunsoft"
|
||||
|
@ -118510,6 +118376,8 @@ SLPS-00089:
|
|||
name: "Oni Taiji!!, The - Mezase! Nidaime Momotarou (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-2084FC8A435830CD
|
||||
metadata:
|
||||
publisher: "Nippon Ichi Software"
|
||||
developer: "O-Two"
|
||||
|
@ -118992,6 +118860,8 @@ SLPM-87047:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-782D7340E38E804E
|
||||
metadata:
|
||||
publisher: "Takumi"
|
||||
developer: "Takumi"
|
||||
|
@ -120498,12 +120368,6 @@ SLPS-02345:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02345
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Namco Ltd"
|
||||
developer: "Namco Hometek"
|
||||
|
@ -121641,6 +121505,8 @@ SLPS-00037:
|
|||
name: "Pachio-kun - Pachinko Land Adventure (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-70E4238F26DC3BB4
|
||||
metadata:
|
||||
publisher: "Coconuts Japan"
|
||||
developer: "Marionette"
|
||||
|
@ -123228,12 +123094,6 @@ SLPS-02357:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02357
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Nihon Telenet"
|
||||
developer: "Nihon Telenet"
|
||||
|
@ -128904,12 +128764,6 @@ SLPS-02360:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02360
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Arc System Works Co"
|
||||
developer: "Arc System Works Co"
|
||||
|
@ -129195,10 +129049,6 @@ SLPS-02347:
|
|||
codes:
|
||||
- SLPS-02347
|
||||
- SLPS-02938
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Athena"
|
||||
developer: "Athena"
|
||||
|
@ -130253,12 +130103,6 @@ ESPM-70003:
|
|||
controllers:
|
||||
- DigitalController
|
||||
- GunCon
|
||||
codes:
|
||||
- ESPM-70003
|
||||
- ESPM-70004
|
||||
- ESPM-70005
|
||||
- ESPM-70006
|
||||
- ESPM-70007
|
||||
metadata:
|
||||
publisher: "Sony Music Entertainment Incorporated"
|
||||
developer: "Sony Music Entertainment Incorporated"
|
||||
|
@ -138663,10 +138507,6 @@ SLPM-87269:
|
|||
codes:
|
||||
- SLPM-87269
|
||||
- SLPS-02379
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Capcom"
|
||||
developer: "Capcom/ OeRSTED"
|
||||
|
@ -138745,7 +138585,6 @@ SLPS-91094:
|
|||
codes:
|
||||
- SLPS-91094
|
||||
- SLPS-91107
|
||||
- SLPS-91108
|
||||
metadata:
|
||||
publisher: "Capcom"
|
||||
developer: "Capcom"
|
||||
|
@ -138837,7 +138676,6 @@ SLPM-87315:
|
|||
- SLPM-87315
|
||||
- SLPS-00902
|
||||
- SLPS-91106
|
||||
- SLPS-91108
|
||||
metadata:
|
||||
publisher: "Capcom"
|
||||
developer: "Capcom"
|
||||
|
@ -140977,10 +140815,6 @@ SLPS-01257:
|
|||
codes:
|
||||
- SLPS-01257
|
||||
- SLPS-02372
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Visit"
|
||||
developer: "Break"
|
||||
|
@ -153732,6 +153566,8 @@ SLPS-00012:
|
|||
name: "Space Griffon VF-9 (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-A9253E2383B76F5A
|
||||
metadata:
|
||||
publisher: "Panther Software"
|
||||
developer: "Panther Software"
|
||||
|
@ -158829,9 +158665,6 @@ SLPS-91105:
|
|||
name: "Street Fighter Zero 2' (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-91105
|
||||
- SLPS-91108
|
||||
metadata:
|
||||
publisher: "Capcom"
|
||||
developer: "Capcom"
|
||||
|
@ -166370,12 +166203,6 @@ SLPS-02366:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02366
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Techno Soleil"
|
||||
developer: "Hamster Co"
|
||||
|
@ -170893,7 +170720,6 @@ LSP-907363:
|
|||
- LSP-907363
|
||||
- LSP90736300
|
||||
- LSP-90736300
|
||||
- LSP90736300
|
||||
metadata:
|
||||
publisher: "Lightspan"
|
||||
developer: "Lightspan"
|
||||
|
@ -171579,10 +171405,14 @@ SLPM-86053:
|
|||
multitap: false
|
||||
linkCable: false
|
||||
SLPS-00065:
|
||||
name: "Tokimeki Memorial - Forever with You (Japan) (Rev 4)"
|
||||
name: "Tokimeki Memorial - Forever with You (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
- PlayStationMouse
|
||||
codes:
|
||||
- HASH-66FFEBF68485E094 # Rev 1
|
||||
- HASH-D3B543537941D0C9 # Rev 2
|
||||
- HASH-6A9968C8852447A8 # Rev 4
|
||||
metadata:
|
||||
publisher: "Konami"
|
||||
developer: "Konami Computer Entertainment Tokyo (KCET)"
|
||||
|
@ -171598,7 +171428,7 @@ SLPS-00065:
|
|||
multitap: false
|
||||
linkCable: false
|
||||
SLPS-00064:
|
||||
name: "Tokimeki Memorial - Forever with You (Japan) (Shokai Genteiban) (Rev 1)"
|
||||
name: "Tokimeki Memorial - Forever with You (Japan) (Shokai Genteiban)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
- PlayStationMouse
|
||||
|
@ -172504,7 +172334,6 @@ SLPM-86373:
|
|||
codes:
|
||||
- SLPM-86373
|
||||
- SLPM-86374
|
||||
- SLPM-86375
|
||||
metadata:
|
||||
publisher: "Enix Corporation"
|
||||
developer: "tri-Ace"
|
||||
|
@ -174287,12 +174116,6 @@ SLPS-02350:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02350
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Whoopee Camp"
|
||||
developer: "Whoopee Camp"
|
||||
|
@ -175437,12 +175260,6 @@ SLPS-02361:
|
|||
- NeGcon
|
||||
traits:
|
||||
- ForcePGXPVertexCache
|
||||
codes:
|
||||
- SLPS-02361
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Atlus Co"
|
||||
developer: "Cave"
|
||||
|
@ -177107,6 +176924,8 @@ SLPS-00018:
|
|||
name: "Twin Goddesses (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-7C5648B2D2C5AADF
|
||||
metadata:
|
||||
publisher: "Polygram Magic of Japan"
|
||||
developer: "Polygram Magic of Japan"
|
||||
|
@ -178154,6 +177973,8 @@ SLPS-00032:
|
|||
name: "Uchuu Seibutsu Flopon-kun P! (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-17C26DD8A7B6744A
|
||||
metadata:
|
||||
publisher: "Asmik Ace Entertainment, Inc"
|
||||
developer: "Warp"
|
||||
|
@ -179155,12 +178976,6 @@ SLPS-02362:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02362
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Visit"
|
||||
developer: "Billiken Soft"
|
||||
|
@ -179520,6 +179335,8 @@ SLPS-00103:
|
|||
name: "V-Tennis (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-100C572E7CFFDE73
|
||||
metadata:
|
||||
publisher: "Tonkin House"
|
||||
developer: "Tonkin House"
|
||||
|
@ -179954,10 +179771,6 @@ SCPS-45486:
|
|||
- SCPS-45486
|
||||
- SLPM-87393
|
||||
- SLPS-02377
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Squaresoft"
|
||||
developer: "Squaresoft"
|
||||
|
@ -180025,9 +179838,6 @@ SLPM-86379:
|
|||
- DigitalController
|
||||
traits:
|
||||
- ForcePGXPCPUMode # Fixes wobble in screen transitions/battle stages.
|
||||
codes:
|
||||
- SLPM-86379
|
||||
- SLPM-86375
|
||||
metadata:
|
||||
publisher: "Enix Corporation"
|
||||
developer: "tri-Ace"
|
||||
|
@ -180054,9 +179864,6 @@ SLPM-86371:
|
|||
- DigitalController
|
||||
traits:
|
||||
- ForcePGXPCPUMode # Fixes wobble in screen transitions/battle stages.
|
||||
codes:
|
||||
- SLPM-86371
|
||||
- SLPM-86375
|
||||
metadata:
|
||||
publisher: "Enix Corporation"
|
||||
developer: "tri-Ace"
|
||||
|
@ -180083,9 +179890,6 @@ SLPM-86380:
|
|||
- DigitalController
|
||||
traits:
|
||||
- ForcePGXPCPUMode # Fixes wobble in screen transitions/battle stages.
|
||||
codes:
|
||||
- SLPM-86380
|
||||
- SLPM-86375
|
||||
metadata:
|
||||
publisher: "Enix Corporation"
|
||||
developer: "tri-Ace"
|
||||
|
@ -180112,9 +179916,6 @@ SLPM-86372:
|
|||
- DigitalController
|
||||
traits:
|
||||
- ForcePGXPCPUMode # Fixes wobble in screen transitions/battle stages.
|
||||
codes:
|
||||
- SLPM-86372
|
||||
- SLPM-86375
|
||||
metadata:
|
||||
publisher: "Enix Corporation"
|
||||
developer: "tri-Ace"
|
||||
|
@ -183066,10 +182867,6 @@ SLPS-01695:
|
|||
codes:
|
||||
- SLPS-01695
|
||||
- SLPS-02346
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Athena"
|
||||
developer: "Art Co"
|
||||
|
@ -184396,12 +184193,6 @@ SLPS-02352:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02352
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Escot"
|
||||
developer: "Systemsoft / Dual"
|
||||
|
@ -186815,12 +186606,6 @@ SLPS-02349:
|
|||
controllers:
|
||||
- AnalogController
|
||||
- DigitalController
|
||||
codes:
|
||||
- SLPS-02349
|
||||
- SLPS-02356
|
||||
- SLPS-02359
|
||||
- SLPS-02373
|
||||
- SLPS-02378
|
||||
metadata:
|
||||
publisher: "Soliton"
|
||||
developer: "Thunder Stone Japan"
|
||||
|
@ -192382,6 +192167,8 @@ SLPS-00083:
|
|||
name: "Zero Divide (Japan)"
|
||||
controllers:
|
||||
- DigitalController
|
||||
codes:
|
||||
- HASH-D967B8454178FF39
|
||||
metadata:
|
||||
publisher: "Zoom Inc"
|
||||
developer: "Zoom Inc"
|
||||
|
|
|
@ -120,8 +120,8 @@ package() {
|
|||
install -Dm755 scripts/packaging/duckstation-qt "${pkgdir}/usr/bin/duckstation-qt"
|
||||
|
||||
# install desktop file and icon
|
||||
install -Dm644 scripts/${_desktopname}.desktop "${pkgdir}/usr/share/applications/${_desktopname}.desktop"
|
||||
install -Dm644 scripts/${_desktopname}.png "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_desktopname}.png"
|
||||
install -Dm644 scripts/packaging/${_desktopname}.desktop "${pkgdir}/usr/share/applications/${_desktopname}.desktop"
|
||||
install -Dm644 scripts/packaging/${_desktopname}.png "${pkgdir}/usr/share/icons/hicolor/512x512/apps/${_desktopname}.png"
|
||||
|
||||
# install license
|
||||
install -Dm644 LICENSE "${pkgdir}/usr/share/licenses/${pkgname}/LICENSE"
|
||||
|
|
|
@ -71,8 +71,8 @@ ninja -C build %{?_smp_mflags}
|
|||
rm -fr %{buildroot}
|
||||
ninja -C build install
|
||||
install -Dm755 scripts/packaging/duckstation-qt %{buildroot}/usr/bin/duckstation-qt
|
||||
install -Dm644 scripts/org.duckstation.DuckStation.png %{buildroot}/usr/share/icons/hicolor/512x512/apps/org.duckstation.DuckStation.png
|
||||
install -Dm644 scripts/org.duckstation.DuckStation.desktop %{buildroot}/usr/share/applications/org.duckstation.DuckStation.desktop
|
||||
install -Dm644 scripts/packaging/org.duckstation.DuckStation.png %{buildroot}/usr/share/icons/hicolor/512x512/apps/org.duckstation.DuckStation.png
|
||||
install -Dm644 scripts/packaging/org.duckstation.DuckStation.desktop %{buildroot}/usr/share/applications/org.duckstation.DuckStation.desktop
|
||||
|
||||
%files
|
||||
%license LICENSE
|
||||
|
|
|
@ -78,3 +78,59 @@ TEST(Rectangle, RelationalOperators)
|
|||
ASSERT_FALSE(r1.eq(r2));
|
||||
}
|
||||
|
||||
TEST(Rectangle, ValidRectangles)
|
||||
{
|
||||
static constexpr GSVector4i cases[] = {
|
||||
GSVector4i::cxpr(1, 2, 3, 4),
|
||||
GSVector4i::cxpr(-5, -10, -1, -2),
|
||||
GSVector4i::cxpr(0, 0, 1, 1),
|
||||
GSVector4i::cxpr(100, 200, 300, 400),
|
||||
GSVector4i::cxpr(-1000, -2000, 500, 600),
|
||||
GSVector4i::cxpr(5, 10, 6, 12),
|
||||
GSVector4i::cxpr(-10, -20, -5, -15),
|
||||
GSVector4i::cxpr(-5, 0, 5, 10),
|
||||
GSVector4i::cxpr(-100, -200, 100, 200),
|
||||
GSVector4i::cxpr(-1, -2, 0, 1),
|
||||
};
|
||||
|
||||
for (GSVector4i tcase : cases)
|
||||
{
|
||||
ASSERT_TRUE(tcase.rvalid());
|
||||
ASSERT_FALSE(tcase.rempty());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Rectangle, InvalidRectangles)
|
||||
{
|
||||
static constexpr GSVector4i cases[] = {
|
||||
// left < right but not top < bottom
|
||||
GSVector4i::cxpr(1, 4, 3, 2),
|
||||
GSVector4i::cxpr(-5, -2, -1, -10),
|
||||
GSVector4i::cxpr(0, 1, 1, 0),
|
||||
GSVector4i::cxpr(100, 400, 300, 200),
|
||||
GSVector4i::cxpr(-1000, 600, 500, -2000),
|
||||
GSVector4i::cxpr(5, 12, 6, 10),
|
||||
GSVector4i::cxpr(-10, -15, -5, -20),
|
||||
GSVector4i::cxpr(-5, 10, 5, 0),
|
||||
GSVector4i::cxpr(-100, 200, 100, -200),
|
||||
GSVector4i::cxpr(-1, 1, 0, -2),
|
||||
|
||||
// not left < right but top < bottom
|
||||
GSVector4i::cxpr(3, 2, 1, 4),
|
||||
GSVector4i::cxpr(-1, -10, -5, -2),
|
||||
GSVector4i::cxpr(1, 0, 0, 1),
|
||||
GSVector4i::cxpr(300, 200, 100, 400),
|
||||
GSVector4i::cxpr(500, -2000, -1000, 600),
|
||||
GSVector4i::cxpr(6, 10, 5, 12),
|
||||
GSVector4i::cxpr(-5, -20, -10, -15),
|
||||
GSVector4i::cxpr(5, 0, -5, 10),
|
||||
GSVector4i::cxpr(100, -200, -100, 200),
|
||||
GSVector4i::cxpr(0, -2, -1, 1),
|
||||
};
|
||||
|
||||
for (GSVector4i tcase : cases)
|
||||
{
|
||||
ASSERT_FALSE(tcase.rvalid());
|
||||
ASSERT_TRUE(tcase.rempty());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,15 +3,17 @@
|
|||
|
||||
#include "assert.h"
|
||||
#include "crash_handler.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <mutex>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "windows_headers.h"
|
||||
#include <intrin.h>
|
||||
#include <tlhelp32.h>
|
||||
#endif
|
||||
|
||||
#include <mutex>
|
||||
|
||||
#ifdef __clang__
|
||||
#pragma clang diagnostic ignored "-Winvalid-noreturn"
|
||||
|
@ -19,9 +21,8 @@
|
|||
|
||||
static std::mutex s_AssertFailedMutex;
|
||||
|
||||
static inline void FreezeThreads(void** ppHandle)
|
||||
static HANDLE FreezeThreads()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
|
||||
if (hSnapshot != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
|
@ -43,17 +44,12 @@ static inline void FreezeThreads(void** ppHandle)
|
|||
}
|
||||
}
|
||||
|
||||
*ppHandle = (void*)hSnapshot;
|
||||
#else
|
||||
*ppHandle = nullptr;
|
||||
#endif
|
||||
return hSnapshot;
|
||||
}
|
||||
|
||||
static inline void ResumeThreads(void* pHandle)
|
||||
static void ResumeThreads(HANDLE hSnapshot)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
HANDLE hSnapshot = (HANDLE)pHandle;
|
||||
if (pHandle != INVALID_HANDLE_VALUE)
|
||||
if (hSnapshot != INVALID_HANDLE_VALUE)
|
||||
{
|
||||
THREADENTRY32 threadEntry;
|
||||
if (Thread32First(hSnapshot, &threadEntry))
|
||||
|
@ -73,21 +69,42 @@ static inline void ResumeThreads(void* pHandle)
|
|||
}
|
||||
CloseHandle(hSnapshot);
|
||||
}
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
#ifdef __ANDROID__
|
||||
// Define as a weak symbol for ancient devices that don't have it.
|
||||
extern "C" __attribute__((weak)) void android_set_abort_message(const char*);
|
||||
#endif
|
||||
|
||||
[[noreturn]] ALWAYS_INLINE static void AbortWithMessage(const char* szMsg)
|
||||
{
|
||||
#ifndef __ANDROID__
|
||||
std::fputs(szMsg, stderr);
|
||||
CrashHandler::WriteDumpForCaller();
|
||||
std::fputs("Aborting application.\n", stderr);
|
||||
std::fflush(stderr);
|
||||
std::abort();
|
||||
#else
|
||||
if (&android_set_abort_message)
|
||||
android_set_abort_message(szMsg);
|
||||
|
||||
std::abort();
|
||||
#endif
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
void Y_OnAssertFailed(const char* szMessage, const char* szFunction, const char* szFile, unsigned uLine)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_AssertFailedMutex);
|
||||
|
||||
void* pHandle;
|
||||
FreezeThreads(&pHandle);
|
||||
|
||||
char szMsg[512];
|
||||
std::snprintf(szMsg, sizeof(szMsg), "%s in function %s (%s:%u)\n", szMessage, szFunction, szFile, uLine);
|
||||
|
||||
#if defined(_WIN32)
|
||||
std::unique_lock lock(s_AssertFailedMutex);
|
||||
HANDLE pHandle = FreezeThreads();
|
||||
|
||||
SetConsoleTextAttribute(GetStdHandle(STD_ERROR_HANDLE), FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
WriteConsoleA(GetStdHandle(STD_ERROR_HANDLE), szMsg, static_cast<DWORD>(std::strlen(szMsg)), NULL, NULL);
|
||||
OutputDebugStringA(szMsg);
|
||||
|
@ -107,28 +124,22 @@ void Y_OnAssertFailed(const char* szMessage, const char* szFunction, const char*
|
|||
CrashHandler::WriteDumpForCaller();
|
||||
TerminateProcess(GetCurrentProcess(), 0xBAADC0DE);
|
||||
}
|
||||
#else
|
||||
std::fputs(szMsg, stderr);
|
||||
CrashHandler::WriteDumpForCaller();
|
||||
std::fputs("Aborting application.\n", stderr);
|
||||
std::fflush(stderr);
|
||||
std::abort();
|
||||
#endif
|
||||
|
||||
ResumeThreads(pHandle);
|
||||
#else
|
||||
AbortWithMessage(szMsg);
|
||||
#endif
|
||||
}
|
||||
|
||||
[[noreturn]] void Y_OnPanicReached(const char* szMessage, const char* szFunction, const char* szFile, unsigned uLine)
|
||||
{
|
||||
std::lock_guard<std::mutex> guard(s_AssertFailedMutex);
|
||||
|
||||
void* pHandle;
|
||||
FreezeThreads(&pHandle);
|
||||
|
||||
char szMsg[512];
|
||||
std::snprintf(szMsg, sizeof(szMsg), "%s in function %s (%s:%u)\n", szMessage, szFunction, szFile, uLine);
|
||||
|
||||
#if defined(_WIN32)
|
||||
std::unique_lock guard(s_AssertFailedMutex);
|
||||
HANDLE pHandle = FreezeThreads();
|
||||
|
||||
SetConsoleTextAttribute(GetStdHandle(STD_ERROR_HANDLE), FOREGROUND_BLUE | FOREGROUND_GREEN | FOREGROUND_INTENSITY);
|
||||
WriteConsoleA(GetStdHandle(STD_ERROR_HANDLE), szMsg, static_cast<DWORD>(std::strlen(szMsg)), NULL, NULL);
|
||||
OutputDebugStringA(szMsg);
|
||||
|
@ -145,13 +156,9 @@ void Y_OnAssertFailed(const char* szMessage, const char* szFunction, const char*
|
|||
CrashHandler::WriteDumpForCaller();
|
||||
|
||||
TerminateProcess(GetCurrentProcess(), 0xBAADC0DE);
|
||||
#else
|
||||
std::fputs(szMsg, stderr);
|
||||
CrashHandler::WriteDumpForCaller();
|
||||
std::fputs("Aborting application.\n", stderr);
|
||||
std::fflush(stderr);
|
||||
std::abort();
|
||||
#endif
|
||||
|
||||
ResumeThreads(pHandle);
|
||||
#else
|
||||
AbortWithMessage(szMsg);
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -9,27 +9,31 @@ void Y_OnAssertFailed(const char* szMessage, const char* szFunction, const char*
|
|||
[[noreturn]] void Y_OnPanicReached(const char* szMessage, const char* szFunction, const char* szFile, unsigned uLine);
|
||||
|
||||
#define Assert(expr) \
|
||||
if (!(expr)) \
|
||||
do \
|
||||
{ \
|
||||
Y_OnAssertFailed("Assertion failed: '" #expr "'", __FUNCTION__, __FILE__, __LINE__); \
|
||||
}
|
||||
if (!(expr)) \
|
||||
Y_OnAssertFailed("Assertion failed: '" #expr "'", __FUNCTION__, __FILE__, __LINE__); \
|
||||
} while (0)
|
||||
#define AssertMsg(expr, msg) \
|
||||
if (!(expr)) \
|
||||
do \
|
||||
{ \
|
||||
Y_OnAssertFailed("Assertion failed: '" msg "'", __FUNCTION__, __FILE__, __LINE__); \
|
||||
}
|
||||
if (!(expr)) \
|
||||
Y_OnAssertFailed("Assertion failed: '" msg "'", __FUNCTION__, __FILE__, __LINE__); \
|
||||
} while (0)
|
||||
|
||||
#if defined(_DEBUG) || defined(_DEVEL)
|
||||
#define DebugAssert(expr) \
|
||||
if (!(expr)) \
|
||||
do \
|
||||
{ \
|
||||
Y_OnAssertFailed("Debug assertion failed: '" #expr "'", __FUNCTION__, __FILE__, __LINE__); \
|
||||
}
|
||||
if (!(expr)) \
|
||||
Y_OnAssertFailed("Debug assertion failed: '" #expr "'", __FUNCTION__, __FILE__, __LINE__); \
|
||||
} while (0)
|
||||
#define DebugAssertMsg(expr, msg) \
|
||||
if (!(expr)) \
|
||||
do \
|
||||
{ \
|
||||
Y_OnAssertFailed("Debug assertion failed: '" msg "'", __FUNCTION__, __FILE__, __LINE__); \
|
||||
}
|
||||
if (!(expr)) \
|
||||
Y_OnAssertFailed("Debug assertion failed: '" msg "'", __FUNCTION__, __FILE__, __LINE__); \
|
||||
} while (0)
|
||||
#else
|
||||
#define DebugAssert(expr)
|
||||
#define DebugAssertMsg(expr, msg)
|
||||
|
|
|
@ -367,7 +367,7 @@ void CrashHandler::WriteDumpForCaller()
|
|||
LogCallstack(0, nullptr);
|
||||
}
|
||||
|
||||
#else
|
||||
#elif !defined(__ANDROID__)
|
||||
|
||||
bool CrashHandler::Install(CleanupHandler cleanup_handler)
|
||||
{
|
||||
|
|
|
@ -2788,6 +2788,10 @@ bool FileSystem::SetPathCompression(const char* path, bool enable)
|
|||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
|
||||
static bool SetLock(int fd, bool lock, bool block, Error* error)
|
||||
{
|
||||
// We want to lock the whole file.
|
||||
|
@ -2814,7 +2818,7 @@ static bool SetLock(int fd, bool lock, bool block, Error* error)
|
|||
bool res;
|
||||
for (;;)
|
||||
{
|
||||
res = (lockf(fd, lock ? (block ? F_TLOCK : F_LOCK) : F_ULOCK, 0) == 0);
|
||||
res = (lockf(fd, lock ? (block ? F_LOCK : F_TLOCK) : F_ULOCK, 0) == 0);
|
||||
if (!res && errno == EINTR)
|
||||
continue;
|
||||
else
|
||||
|
|
|
@ -155,7 +155,12 @@ bool CommitAtomicRenamedFile(AtomicRenamedFile& file, Error* error);
|
|||
void DiscardAtomicRenamedFile(AtomicRenamedFile& file);
|
||||
|
||||
/// Abstracts a POSIX file lock.
|
||||
#ifndef _WIN32
|
||||
#if !defined(_WIN32) && !defined(__ANDROID__)
|
||||
#define HAS_POSIX_FILE_LOCK 1
|
||||
#endif
|
||||
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
|
||||
class POSIXLock
|
||||
{
|
||||
public:
|
||||
|
@ -175,6 +180,7 @@ public:
|
|||
private:
|
||||
int m_fd;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
std::optional<DynamicHeapArray<u8>> ReadBinaryFile(const char* path, Error* error = nullptr);
|
||||
|
|
|
@ -65,3 +65,164 @@ void GSMatrix2x2::store(void* m)
|
|||
{
|
||||
std::memcpy(m, E, sizeof(E));
|
||||
}
|
||||
|
||||
GSMatrix4x4::GSMatrix4x4(float e00, float e01, float e02, float e03, float e10, float e11, float e12, float e13,
|
||||
float e20, float e21, float e22, float e23, float e30, float e31, float e32, float e33)
|
||||
{
|
||||
E[0][0] = e00;
|
||||
E[0][1] = e01;
|
||||
E[0][2] = e02;
|
||||
E[0][3] = e03;
|
||||
E[1][0] = e10;
|
||||
E[1][1] = e11;
|
||||
E[1][2] = e12;
|
||||
E[1][3] = e13;
|
||||
E[2][0] = e20;
|
||||
E[2][1] = e21;
|
||||
E[2][2] = e22;
|
||||
E[2][3] = e23;
|
||||
E[3][0] = e30;
|
||||
E[3][1] = e31;
|
||||
E[3][2] = e32;
|
||||
E[3][3] = e33;
|
||||
}
|
||||
|
||||
GSMatrix4x4::GSMatrix4x4(const GSMatrix2x2& m)
|
||||
{
|
||||
E[0][0] = m.E[0][0];
|
||||
E[0][1] = m.E[0][1];
|
||||
E[0][2] = 0.0f;
|
||||
E[0][3] = 0.0f;
|
||||
E[1][0] = m.E[1][0];
|
||||
E[1][1] = m.E[1][1];
|
||||
E[1][2] = 0.0f;
|
||||
E[1][3] = 0.0f;
|
||||
E[2][0] = 0.0f;
|
||||
E[2][1] = 0.0f;
|
||||
E[2][2] = 1.0f;
|
||||
E[2][3] = 0.0f;
|
||||
E[3][0] = 0.0f;
|
||||
E[3][1] = 0.0f;
|
||||
E[3][2] = 0.0f;
|
||||
E[3][3] = 1.0f;
|
||||
}
|
||||
|
||||
GSMatrix4x4 GSMatrix4x4::operator*(const GSMatrix4x4& m) const
|
||||
{
|
||||
// This isn't speedy by any means, but it's not hot code either.
|
||||
GSMatrix4x4 res;
|
||||
|
||||
#define MultRC(rw, cl) E[rw][0] * m.E[0][cl] + E[rw][1] * m.E[1][cl] + E[rw][2] * m.E[2][cl] + E[rw][3] * m.E[3][cl]
|
||||
|
||||
res.E[0][0] = MultRC(0, 0);
|
||||
res.E[0][1] = MultRC(0, 1);
|
||||
res.E[0][2] = MultRC(0, 2);
|
||||
res.E[0][3] = MultRC(0, 3);
|
||||
res.E[1][0] = MultRC(1, 0);
|
||||
res.E[1][1] = MultRC(1, 1);
|
||||
res.E[1][2] = MultRC(1, 2);
|
||||
res.E[1][3] = MultRC(1, 3);
|
||||
res.E[2][0] = MultRC(2, 0);
|
||||
res.E[2][1] = MultRC(2, 1);
|
||||
res.E[2][2] = MultRC(2, 2);
|
||||
res.E[2][3] = MultRC(2, 3);
|
||||
res.E[3][0] = MultRC(3, 0);
|
||||
res.E[3][1] = MultRC(3, 1);
|
||||
res.E[3][2] = MultRC(3, 2);
|
||||
res.E[3][3] = MultRC(3, 3);
|
||||
|
||||
#undef MultRC
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
GSVector4 GSMatrix4x4::operator*(const GSVector4& v) const
|
||||
{
|
||||
const GSVector4 r0 = row(0);
|
||||
const GSVector4 r1 = row(1);
|
||||
const GSVector4 r2 = row(2);
|
||||
const GSVector4 r3 = row(4);
|
||||
|
||||
return GSVector4(r0.dot(v), r1.dot(v), r2.dot(v), r3.dot(v));
|
||||
}
|
||||
|
||||
GSMatrix4x4 GSMatrix4x4::Identity()
|
||||
{
|
||||
GSMatrix4x4 res;
|
||||
|
||||
#define MultRC(rw, cl) E[rw][0] * m.E[0][cl] + E[rw][1] * m.E[1][cl] + E[rw][2] * m.E[2][cl] + E[rw][3] * m.E[3][cl]
|
||||
|
||||
res.E[0][0] = 1.0f;
|
||||
res.E[0][1] = 0.0f;
|
||||
res.E[0][2] = 0.0f;
|
||||
res.E[0][3] = 0.0f;
|
||||
res.E[1][0] = 0.0f;
|
||||
res.E[1][1] = 1.0f;
|
||||
res.E[1][2] = 0.0f;
|
||||
res.E[1][3] = 0.0f;
|
||||
res.E[2][0] = 0.0f;
|
||||
res.E[2][1] = 0.0f;
|
||||
res.E[2][2] = 1.0f;
|
||||
res.E[2][3] = 0.0f;
|
||||
res.E[3][0] = 0.0f;
|
||||
res.E[3][1] = 0.0f;
|
||||
res.E[3][2] = 0.0f;
|
||||
res.E[3][3] = 1.0f;
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
GSMatrix4x4 GSMatrix4x4::RotationX(float angle_in_radians)
|
||||
{
|
||||
const float sin_angle = std::sin(angle_in_radians);
|
||||
const float cos_angle = std::cos(angle_in_radians);
|
||||
|
||||
return GSMatrix4x4(1.0f, 0.0f, 0.0f, 0.0f, 0.0f, cos_angle, -sin_angle, 0.0f, 0.0f, sin_angle, cos_angle, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
GSMatrix4x4 GSMatrix4x4::RotationY(float angle_in_radians)
|
||||
{
|
||||
const float sin_angle = std::sin(angle_in_radians);
|
||||
const float cos_angle = std::cos(angle_in_radians);
|
||||
|
||||
return GSMatrix4x4(cos_angle, 0.0f, sin_angle, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f, -sin_angle, 0.0f, cos_angle, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
GSMatrix4x4 GSMatrix4x4::RotationZ(float angle_in_radians)
|
||||
{
|
||||
const float sin_angle = std::sin(angle_in_radians);
|
||||
const float cos_angle = std::cos(angle_in_radians);
|
||||
|
||||
return GSMatrix4x4(cos_angle, -sin_angle, 0.0f, 0.0f, sin_angle, cos_angle, 0.0f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
|
||||
0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
GSMatrix4x4 GSMatrix4x4::OffCenterOrthographicProjection(float left, float top, float right, float bottom, float zNear,
|
||||
float zFar)
|
||||
{
|
||||
return GSMatrix4x4(2.0f / (right - left), 0.0f, 0.0f, (left + right) / (left - right), 0.0f, 2.0f / (top - bottom),
|
||||
0.0f, (top + bottom) / (bottom - top), 0.0f, 0.0f, 1.0f / (zNear - zFar), zNear / (zNear - zFar),
|
||||
0.0f, 0.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
GSMatrix4x4 GSMatrix4x4::OffCenterOrthographicProjection(float width, float height, float zNear, float zFar)
|
||||
{
|
||||
return OffCenterOrthographicProjection(0.0f, 0.0f, width, height, zNear, zFar);
|
||||
}
|
||||
|
||||
GSVector4 GSMatrix4x4::row(size_t i) const
|
||||
{
|
||||
return GSVector4::load<true>(&E[i][0]);
|
||||
}
|
||||
|
||||
GSVector4 GSMatrix4x4::col(size_t i) const
|
||||
{
|
||||
return GSVector4(E[0][i], E[1][i], E[2][i], E[3][i]);
|
||||
}
|
||||
|
||||
void GSMatrix4x4::store(void* m)
|
||||
{
|
||||
std::memcpy(m, &E[0][0], sizeof(E));
|
||||
}
|
||||
|
|
|
@ -37,3 +37,33 @@ public:
|
|||
|
||||
alignas(8) float E[2][2];
|
||||
};
|
||||
|
||||
class alignas(VECTOR_ALIGNMENT) GSMatrix4x4
|
||||
{
|
||||
public:
|
||||
GSMatrix4x4() = default;
|
||||
GSMatrix4x4(float e00, float e01, float e02, float e03, float e10, float e11, float e12, float e13, float e20,
|
||||
float e21, float e22, float e23, float e30, float e31, float e32, float e33);
|
||||
GSMatrix4x4(const GSMatrix2x2& m);
|
||||
|
||||
GSMatrix4x4 operator*(const GSMatrix4x4& m) const;
|
||||
|
||||
GSVector4 operator*(const GSVector4& v) const;
|
||||
|
||||
static GSMatrix4x4 Identity();
|
||||
|
||||
static GSMatrix4x4 RotationX(float angle_in_radians);
|
||||
static GSMatrix4x4 RotationY(float angle_in_radians);
|
||||
static GSMatrix4x4 RotationZ(float angle_in_radians);
|
||||
|
||||
static GSMatrix4x4 OffCenterOrthographicProjection(float left, float top, float right, float bottom, float zNear,
|
||||
float zFar);
|
||||
static GSMatrix4x4 OffCenterOrthographicProjection(float width, float height, float zNear, float zFar);
|
||||
|
||||
GSVector4 row(size_t i) const;
|
||||
GSVector4 col(size_t i) const;
|
||||
|
||||
void store(void* m);
|
||||
|
||||
float E[4][4];
|
||||
};
|
||||
|
|
|
@ -1171,17 +1171,21 @@ public:
|
|||
|
||||
ALWAYS_INLINE bool rempty() const
|
||||
{
|
||||
#ifdef CPU_ARCH_ARM64
|
||||
return (vminv_u32(vreinterpret_u32_s32(vget_low_s32(lt32(zwzw())))) == 0);
|
||||
#else
|
||||
return (vget_lane_u64(vreinterpret_u64_u32(vreinterpret_u32_s32(vget_low_s32(lt32(zwzw())))), 0) == 0);
|
||||
#endif
|
||||
// !any((x, y) < (z, w)) i.e. !not_empty
|
||||
return (vget_lane_u64(vreinterpret_u64_u32(vclt_s32(vget_low_s32(v4s), vget_high_s32(v4s))), 0) !=
|
||||
0xFFFFFFFFFFFFFFFFULL);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool rvalid() const
|
||||
{
|
||||
// !all((x, y) >= (z, w))
|
||||
return (vget_lane_u64(vreinterpret_u64_u32(vcge_s32(vget_low_s32(v4s), vget_high_s32(v4s))), 0) == 0);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE GSVector4i runion(const GSVector4i& a) const { return min_s32(a).upl64(max_s32(a).srl<8>()); }
|
||||
|
||||
ALWAYS_INLINE GSVector4i rintersect(const GSVector4i& a) const { return sat_s32(a); }
|
||||
ALWAYS_INLINE bool rintersects(const GSVector4i& v) const { return !rintersect(v).rempty(); }
|
||||
ALWAYS_INLINE bool rintersects(const GSVector4i& v) const { return rintersect(v).rvalid(); }
|
||||
ALWAYS_INLINE bool rcontains(const GSVector4i& v) const { return rintersect(v).eq(v); }
|
||||
|
||||
ALWAYS_INLINE u32 rgba32() const { return static_cast<u32>(ps32().pu16().extract32<0>()); }
|
||||
|
@ -2574,6 +2578,17 @@ public:
|
|||
|
||||
#endif
|
||||
|
||||
ALWAYS_INLINE float dot(const GSVector4& v) const
|
||||
{
|
||||
#ifdef CPU_ARCH_ARM64
|
||||
return vaddvq_f32(vmulq_f32(v4s, v.v4s));
|
||||
#else
|
||||
const float32x4_t dp = vmulq_f32(v4s, v.v4s);
|
||||
float32x2_t tmp = vadd_f32(vget_low_f32(dp), vget_high_f32(dp)); // (x+z, y+w)
|
||||
return vget_lane_f32(vadd_f32(tmp, vdup_lane_f32(tmp, 1)), 0);
|
||||
#endif
|
||||
}
|
||||
|
||||
ALWAYS_INLINE GSVector4 sat(const GSVector4& a, const GSVector4& b) const { return max(a).min(b); }
|
||||
|
||||
ALWAYS_INLINE GSVector4 sat(const GSVector4& a) const
|
||||
|
|
|
@ -958,7 +958,8 @@ public:
|
|||
ALWAYS_INLINE s32 width() const { return right - left; }
|
||||
ALWAYS_INLINE s32 height() const { return bottom - top; }
|
||||
|
||||
ALWAYS_INLINE bool rempty() const { return lt32(zwzw()).mask() != 0x00ff; }
|
||||
ALWAYS_INLINE bool rempty() const { return (lt32(zwzw()).mask() != 0x00ff); }
|
||||
ALWAYS_INLINE bool rvalid() const { return ((ge32(zwzw()).mask() & 0xff) == 0); }
|
||||
|
||||
GSVector4i runion(const GSVector4i& v) const
|
||||
{
|
||||
|
@ -966,7 +967,7 @@ public:
|
|||
}
|
||||
|
||||
ALWAYS_INLINE GSVector4i rintersect(const GSVector4i& v) const { return sat_s32(v); }
|
||||
ALWAYS_INLINE bool rintersects(const GSVector4i& v) const { return !rintersect(v).rempty(); }
|
||||
ALWAYS_INLINE bool rintersects(const GSVector4i& v) const { return rintersect(v).rvalid(); }
|
||||
ALWAYS_INLINE bool rcontains(const GSVector4i& v) const { return rintersect(v).eq(v); }
|
||||
|
||||
ALWAYS_INLINE u32 rgba32() const { return static_cast<u32>(ps32().pu16().extract32<0>()); }
|
||||
|
@ -1845,20 +1846,9 @@ public:
|
|||
|
||||
GSVector4 hsub(const GSVector4& v) const { return GSVector4(x - y, z - w, v.x - v.y, v.z - v.w); }
|
||||
|
||||
template<int i>
|
||||
GSVector4 dp(const GSVector4& v) const
|
||||
ALWAYS_INLINE float dot(const GSVector4& v) const
|
||||
{
|
||||
float res = 0.0f;
|
||||
if constexpr (i & 0x10)
|
||||
res += x * v.x;
|
||||
if constexpr (i & 0x20)
|
||||
res += y * v.y;
|
||||
if constexpr (i & 0x40)
|
||||
res += z * v.z;
|
||||
if constexpr (i & 0x80)
|
||||
res += w * v.w;
|
||||
return GSVector4((i & 0x01) ? res : 0.0f, (i & 0x02) ? res : 0.0f, (i & 0x04) ? res : 0.0f,
|
||||
(i & 0x08) ? res : 0.0f);
|
||||
return (x * v.x) + (y * v.y) + (z * v.z) + (w * v.w);
|
||||
}
|
||||
|
||||
GSVector4 sat(const GSVector4& min, const GSVector4& max) const
|
||||
|
|
|
@ -1071,12 +1071,13 @@ public:
|
|||
ALWAYS_INLINE s32 width() const { return right - left; }
|
||||
ALWAYS_INLINE s32 height() const { return bottom - top; }
|
||||
|
||||
ALWAYS_INLINE bool rempty() const { return lt32(zwzw()).mask() != 0x00ff; }
|
||||
ALWAYS_INLINE bool rempty() const { return (lt32(zwzw()).mask() != 0x00ff); }
|
||||
ALWAYS_INLINE bool rvalid() const { return ((ge32(zwzw()).mask() & 0xff) == 0); }
|
||||
|
||||
ALWAYS_INLINE GSVector4i runion(const GSVector4i& v) const { return min_s32(v).blend32<0xc>(max_s32(v)); }
|
||||
|
||||
ALWAYS_INLINE GSVector4i rintersect(const GSVector4i& v) const { return sat_s32(v); }
|
||||
ALWAYS_INLINE bool rintersects(const GSVector4i& v) const { return !rintersect(v).rempty(); }
|
||||
ALWAYS_INLINE bool rintersects(const GSVector4i& v) const { return rintersect(v).rvalid(); }
|
||||
ALWAYS_INLINE bool rcontains(const GSVector4i& v) const { return rintersect(v).eq(v); }
|
||||
|
||||
ALWAYS_INLINE u32 rgba32() const { return static_cast<u32>(ps32().pu16().extract32<0>()); }
|
||||
|
@ -2007,10 +2008,16 @@ public:
|
|||
|
||||
ALWAYS_INLINE GSVector4 hsub(const GSVector4& v) const { return GSVector4(_mm_hsub_ps(m, v.m)); }
|
||||
|
||||
template<int i>
|
||||
ALWAYS_INLINE GSVector4 dp(const GSVector4& v) const
|
||||
ALWAYS_INLINE float dot(const GSVector4& v) const
|
||||
{
|
||||
return GSVector4(_mm_dp_ps(m, v.m, i));
|
||||
#ifdef CPU_ARCH_SSE41
|
||||
return _mm_cvtss_f32(_mm_dp_ps(m, v.m, 0xf1));
|
||||
#else
|
||||
__m128 tmp = _mm_mul_ps(m, v.m);
|
||||
tmp = _mm_add_ps(tmp, _mm_unpackhi_ps(tmp, tmp)); // (x+z, y+w, ..., ...)
|
||||
tmp = _mm_add_ss(tmp, _mm_shuffle_ps(tmp, tmp, _MM_SHUFFLE(3, 2, 1, 1)));
|
||||
return _mm_cvtss_f32(tmp);
|
||||
#endif
|
||||
}
|
||||
|
||||
ALWAYS_INLINE GSVector4 sat(const GSVector4& min, const GSVector4& max) const
|
||||
|
@ -2393,6 +2400,11 @@ public:
|
|||
|
||||
ALWAYS_INLINE GSVector2 zw() const { return GSVector2(_mm_shuffle_ps(m, m, _MM_SHUFFLE(3, 2, 3, 2))); }
|
||||
|
||||
ALWAYS_INLINE static GSVector4 xyxy(const GSVector2& l, const GSVector2& h)
|
||||
{
|
||||
return GSVector4(_mm_movelh_ps(l.m, h.m));
|
||||
}
|
||||
|
||||
#define VECTOR4_SHUFFLE_4(xs, xn, ys, yn, zs, zn, ws, wn) \
|
||||
ALWAYS_INLINE GSVector4 xs##ys##zs##ws() const \
|
||||
{ \
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <cctype>
|
||||
#include <codecvt>
|
||||
#include <cstdio>
|
||||
#include <memory>
|
||||
#include <sstream>
|
||||
|
||||
#ifndef __APPLE__
|
||||
|
@ -442,7 +443,7 @@ bool StringUtil::ParseAssignmentString(const std::string_view str, std::string_v
|
|||
|
||||
void StringUtil::EncodeAndAppendUTF8(std::string& s, char32_t ch)
|
||||
{
|
||||
if (ch <= 0x7F)
|
||||
if (ch <= 0x7F) [[likely]]
|
||||
{
|
||||
s.push_back(static_cast<char>(static_cast<u8>(ch)));
|
||||
}
|
||||
|
@ -472,17 +473,84 @@ void StringUtil::EncodeAndAppendUTF8(std::string& s, char32_t ch)
|
|||
}
|
||||
}
|
||||
|
||||
size_t StringUtil::GetEncodedUTF8Length(char32_t ch)
|
||||
{
|
||||
if (ch <= 0x7F) [[likely]]
|
||||
return 1;
|
||||
else if (ch <= 0x07FF)
|
||||
return 2;
|
||||
else if (ch <= 0xFFFF)
|
||||
return 3;
|
||||
else if (ch <= 0x10FFFF)
|
||||
return 4;
|
||||
else
|
||||
return 3;
|
||||
}
|
||||
|
||||
size_t StringUtil::EncodeAndAppendUTF8(void* utf8, size_t pos, size_t size, char32_t ch)
|
||||
{
|
||||
u8* utf8_bytes = static_cast<u8*>(utf8) + pos;
|
||||
if (ch <= 0x7F) [[likely]]
|
||||
{
|
||||
if (pos == size) [[unlikely]]
|
||||
return 0;
|
||||
|
||||
utf8_bytes[0] = static_cast<u8>(ch);
|
||||
return 1;
|
||||
}
|
||||
else if (ch <= 0x07FF)
|
||||
{
|
||||
if ((pos + 1) >= size) [[unlikely]]
|
||||
return 0;
|
||||
|
||||
utf8_bytes[0] = static_cast<u8>(0xc0 | static_cast<u8>((ch >> 6) & 0x1f));
|
||||
utf8_bytes[1] = static_cast<u8>(0x80 | static_cast<u8>((ch & 0x3f)));
|
||||
return 2;
|
||||
}
|
||||
else if (ch <= 0xFFFF)
|
||||
{
|
||||
if ((pos + 3) >= size) [[unlikely]]
|
||||
return 0;
|
||||
|
||||
utf8_bytes[0] = static_cast<u8>(0xe0 | static_cast<u8>(((ch >> 12) & 0x0f)));
|
||||
utf8_bytes[1] = static_cast<u8>(0x80 | static_cast<u8>(((ch >> 6) & 0x3f)));
|
||||
utf8_bytes[2] = static_cast<u8>(0x80 | static_cast<u8>((ch & 0x3f)));
|
||||
return 3;
|
||||
}
|
||||
else if (ch <= 0x10FFFF)
|
||||
{
|
||||
if ((pos + 4) >= size) [[unlikely]]
|
||||
return 0;
|
||||
|
||||
utf8_bytes[0] = static_cast<u8>(0xf0 | static_cast<u8>(((ch >> 18) & 0x07)));
|
||||
utf8_bytes[1] = static_cast<u8>(0x80 | static_cast<u8>(((ch >> 12) & 0x3f)));
|
||||
utf8_bytes[2] = static_cast<u8>(0x80 | static_cast<u8>(((ch >> 6) & 0x3f)));
|
||||
utf8_bytes[3] = static_cast<u8>(0x80 | static_cast<u8>((ch & 0x3f)));
|
||||
return 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
if ((pos + 3) >= size) [[unlikely]]
|
||||
return 0;
|
||||
|
||||
utf8_bytes[0] = 0xefu;
|
||||
utf8_bytes[1] = 0xbfu;
|
||||
utf8_bytes[2] = 0xbdu;
|
||||
return 3;
|
||||
}
|
||||
}
|
||||
|
||||
size_t StringUtil::DecodeUTF8(const void* bytes, size_t length, char32_t* ch)
|
||||
{
|
||||
const u8* s = reinterpret_cast<const u8*>(bytes);
|
||||
if (s[0] < 0x80)
|
||||
if (s[0] < 0x80) [[likely]]
|
||||
{
|
||||
*ch = s[0];
|
||||
return 1;
|
||||
}
|
||||
else if ((s[0] & 0xe0) == 0xc0)
|
||||
{
|
||||
if (length < 2)
|
||||
if (length < 2) [[unlikely]]
|
||||
goto invalid;
|
||||
|
||||
*ch = static_cast<char32_t>((static_cast<u32>(s[0] & 0x1f) << 6) | (static_cast<u32>(s[1] & 0x3f) << 0));
|
||||
|
@ -490,7 +558,7 @@ size_t StringUtil::DecodeUTF8(const void* bytes, size_t length, char32_t* ch)
|
|||
}
|
||||
else if ((s[0] & 0xf0) == 0xe0)
|
||||
{
|
||||
if (length < 3)
|
||||
if (length < 3) [[unlikely]]
|
||||
goto invalid;
|
||||
|
||||
*ch = static_cast<char32_t>((static_cast<u32>(s[0] & 0x0f) << 12) | (static_cast<u32>(s[1] & 0x3f) << 6) |
|
||||
|
@ -499,7 +567,7 @@ size_t StringUtil::DecodeUTF8(const void* bytes, size_t length, char32_t* ch)
|
|||
}
|
||||
else if ((s[0] & 0xf8) == 0xf0 && (s[0] <= 0xf4))
|
||||
{
|
||||
if (length < 4)
|
||||
if (length < 4) [[unlikely]]
|
||||
goto invalid;
|
||||
|
||||
*ch = static_cast<char32_t>((static_cast<u32>(s[0] & 0x07) << 18) | (static_cast<u32>(s[1] & 0x3f) << 12) |
|
||||
|
@ -512,6 +580,82 @@ invalid:
|
|||
return 1;
|
||||
}
|
||||
|
||||
size_t StringUtil::EncodeAndAppendUTF16(void* utf16, size_t pos, size_t size, char32_t codepoint)
|
||||
{
|
||||
u8* const utf16_bytes = std::assume_aligned<sizeof(u16)>(static_cast<u8*>(utf16)) + (pos * sizeof(u16));
|
||||
if (codepoint <= 0xFFFF) [[likely]]
|
||||
{
|
||||
if (pos == size) [[unlikely]]
|
||||
return 0;
|
||||
|
||||
// surrogates are invalid
|
||||
const u16 codepoint16 =
|
||||
static_cast<u16>((codepoint >= 0xD800 && codepoint <= 0xDFFF) ? UNICODE_REPLACEMENT_CHARACTER : codepoint);
|
||||
std::memcpy(utf16_bytes, &codepoint16, sizeof(codepoint16));
|
||||
return 1;
|
||||
}
|
||||
else if (codepoint <= 0x10FFFF)
|
||||
{
|
||||
if ((pos + 1) >= size) [[unlikely]]
|
||||
return 0;
|
||||
|
||||
codepoint -= 0x010000;
|
||||
|
||||
const u16 low = static_cast<u16>(((static_cast<u32>(codepoint) >> 10) & 0x3FFu) + 0xD800);
|
||||
const u16 high = static_cast<u16>((static_cast<u32>(codepoint) & 0x3FFu) + 0xDC00);
|
||||
std::memcpy(utf16_bytes, &low, sizeof(high));
|
||||
std::memcpy(utf16_bytes + sizeof(u16), &high, sizeof(high));
|
||||
return 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// unrepresentable
|
||||
constexpr u16 value = static_cast<u16>(UNICODE_REPLACEMENT_CHARACTER);
|
||||
std::memcpy(utf16_bytes, &value, sizeof(value));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
size_t StringUtil::DecodeUTF16(const void* bytes, size_t pos, size_t length, char32_t* ch)
|
||||
{
|
||||
const u8* const utf16_bytes = std::assume_aligned<sizeof(u16)>(static_cast<const u8*>(bytes)) + pos * sizeof(u16);
|
||||
|
||||
u16 high;
|
||||
std::memcpy(&high, utf16_bytes, sizeof(high));
|
||||
|
||||
// High surrogate?
|
||||
if (high >= 0xD800 && high <= 0xDBFF) [[unlikely]]
|
||||
{
|
||||
if (length < 2) [[unlikely]]
|
||||
{
|
||||
// Missing low surrogate.
|
||||
*ch = UNICODE_REPLACEMENT_CHARACTER;
|
||||
return 1;
|
||||
}
|
||||
|
||||
u16 low;
|
||||
std::memcpy(&low, utf16_bytes + sizeof(u16), sizeof(low));
|
||||
if (low >= 0xDC00 && low <= 0xDFFF) [[likely]]
|
||||
{
|
||||
*ch = static_cast<char32_t>(((static_cast<u32>(high) - 0xD800u) << 10) + ((static_cast<u32>(low) - 0xDC00)) +
|
||||
0x10000u);
|
||||
return 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Invalid high surrogate.
|
||||
*ch = UNICODE_REPLACEMENT_CHARACTER;
|
||||
return 2;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Single 16-bit value.
|
||||
*ch = static_cast<char32_t>(high);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
std::string StringUtil::Ellipsise(const std::string_view str, u32 max_length, const char* ellipsis /*= "..."*/)
|
||||
{
|
||||
std::string ret;
|
||||
|
|
|
@ -361,13 +361,22 @@ static constexpr char32_t UNICODE_REPLACEMENT_CHARACTER = 0xFFFD;
|
|||
|
||||
/// Appends a UTF-16/UTF-32 codepoint to a UTF-8 string.
|
||||
void EncodeAndAppendUTF8(std::string& s, char32_t ch);
|
||||
size_t EncodeAndAppendUTF8(void* utf8, size_t pos, size_t size, char32_t ch);
|
||||
size_t GetEncodedUTF8Length(char32_t ch);
|
||||
|
||||
/// Decodes UTF-8 to a single codepoint, updating the position parameter.
|
||||
/// Decodes UTF-8 to a single unicode codepoint.
|
||||
/// Returns the number of bytes the codepoint took in the original string.
|
||||
size_t DecodeUTF8(const void* bytes, size_t length, char32_t* ch);
|
||||
size_t DecodeUTF8(const std::string_view str, size_t offset, char32_t* ch);
|
||||
size_t DecodeUTF8(const std::string& str, size_t offset, char32_t* ch);
|
||||
|
||||
/// Appends a unicode codepoint to a UTF-16 string.
|
||||
size_t EncodeAndAppendUTF16(void* utf16, size_t pos, size_t size, char32_t codepoint);
|
||||
|
||||
/// Decodes UTF-16 to a single unicode codepoint.
|
||||
/// Returns the number of bytes the codepoint took in the original string.
|
||||
size_t DecodeUTF16(const void* bytes, size_t pos, size_t size, char32_t* codepoint);
|
||||
|
||||
// Replaces the end of a string with ellipsis if it exceeds the specified length.
|
||||
std::string Ellipsise(const std::string_view str, u32 max_length, const char* ellipsis = "...");
|
||||
void EllipsiseInPlace(std::string& str, u32 max_length, const char* ellipsis = "...");
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include "log.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#if !defined(_WIN32) && !defined(__APPLE__)
|
||||
#ifndef _GNU_SOURCE
|
||||
|
@ -164,8 +165,9 @@ Threading::ThreadHandle Threading::ThreadHandle::GetForCallingThread()
|
|||
{
|
||||
ThreadHandle ret;
|
||||
#ifdef _WIN32
|
||||
ret.m_native_id = GetCurrentThreadId();
|
||||
ret.m_native_handle =
|
||||
(void*)OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, GetCurrentThreadId());
|
||||
(void*)OpenThread(THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, ret.m_native_id);
|
||||
#else
|
||||
ret.m_native_handle = (void*)pthread_self();
|
||||
#ifdef __linux__
|
||||
|
@ -181,7 +183,9 @@ Threading::ThreadHandle& Threading::ThreadHandle::operator=(ThreadHandle&& handl
|
|||
if (m_native_handle)
|
||||
CloseHandle((HANDLE)m_native_handle);
|
||||
m_native_handle = handle.m_native_handle;
|
||||
m_native_id = handle.m_native_id;
|
||||
handle.m_native_handle = nullptr;
|
||||
handle.m_native_id = 0;
|
||||
#else
|
||||
m_native_handle = handle.m_native_handle;
|
||||
handle.m_native_handle = nullptr;
|
||||
|
@ -207,6 +211,12 @@ Threading::ThreadHandle& Threading::ThreadHandle::operator=(const ThreadHandle&
|
|||
THREAD_QUERY_INFORMATION | THREAD_SET_LIMITED_INFORMATION, FALSE, 0))
|
||||
{
|
||||
m_native_handle = (void*)new_handle;
|
||||
m_native_id = handle.m_native_id;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_native_handle = nullptr;
|
||||
m_native_id = 0;
|
||||
}
|
||||
#else
|
||||
m_native_handle = handle.m_native_handle;
|
||||
|
@ -275,6 +285,15 @@ bool Threading::ThreadHandle::SetAffinity(u64 processor_mask) const
|
|||
#endif
|
||||
}
|
||||
|
||||
bool Threading::ThreadHandle::IsCallingThread() const
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return (GetCurrentThreadId() == m_native_id);
|
||||
#else
|
||||
return pthread_equal(pthread_self(), (pthread_t)m_native_handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef __APPLE__
|
||||
|
||||
bool Threading::ThreadHandle::SetTimeConstraints(bool enabled, u64 period, u64 typical_time, u64 maximum_time)
|
||||
|
@ -317,9 +336,9 @@ bool Threading::ThreadHandle::SetTimeConstraints(bool enabled, u64 period, u64 t
|
|||
|
||||
Threading::Thread::Thread() = default;
|
||||
|
||||
Threading::Thread::Thread(Thread&& thread) : ThreadHandle(thread), m_stack_size(thread.m_stack_size)
|
||||
Threading::Thread::Thread(Thread&& thread) : ThreadHandle(thread)
|
||||
{
|
||||
thread.m_stack_size = 0;
|
||||
m_stack_size = std::exchange(thread.m_stack_size, 0);
|
||||
}
|
||||
|
||||
Threading::Thread::Thread(EntryPoint func) : ThreadHandle()
|
||||
|
|
|
@ -53,6 +53,9 @@ public:
|
|||
/// Obviously, only works up to 64 processors.
|
||||
bool SetAffinity(u64 processor_mask) const;
|
||||
|
||||
/// Returns true if the calling thread matches this handle.
|
||||
bool IsCallingThread() const;
|
||||
|
||||
#ifdef __APPLE__
|
||||
/// Only available on MacOS, sets a period/maximum time for the scheduler.
|
||||
bool SetTimeConstraints(bool enabled, u64 period, u64 typical_time, u64 maximum_time);
|
||||
|
@ -62,8 +65,9 @@ protected:
|
|||
void* m_native_handle = nullptr;
|
||||
|
||||
// We need the thread ID for affinity adjustments on Linux.
|
||||
#if defined(__linux__)
|
||||
#if defined(_WIN32) || defined(__linux__)
|
||||
unsigned int m_native_id = 0;
|
||||
u32 m_stack_size = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
@ -104,7 +108,10 @@ protected:
|
|||
static void* ThreadProc(void* param);
|
||||
#endif
|
||||
|
||||
#if !defined(_WIN32) && !defined(__linux__)
|
||||
// Stored in ThreadHandle to save 8 bytes.
|
||||
u32 m_stack_size = 0;
|
||||
#endif
|
||||
};
|
||||
|
||||
/// A semaphore that requires a system call to wake/sleep.
|
||||
|
|
|
@ -2153,8 +2153,8 @@ static float IndicatorOpacity(const T& i)
|
|||
|
||||
void Achievements::DrawGameOverlays()
|
||||
{
|
||||
using ImGuiFullscreen::g_medium_font;
|
||||
using ImGuiFullscreen::LayoutScale;
|
||||
using ImGuiFullscreen::UIStyle;
|
||||
|
||||
if (!HasActiveGame() || !g_settings.achievements_overlays)
|
||||
return;
|
||||
|
@ -2210,7 +2210,8 @@ void Achievements::DrawGameOverlays()
|
|||
|
||||
const char* text_start = s_state.active_progress_indicator->achievement->measured_progress;
|
||||
const char* text_end = text_start + std::strlen(text_start);
|
||||
const ImVec2 text_size = g_medium_font->CalcTextSizeA(g_medium_font->FontSize, FLT_MAX, 0.0f, text_start, text_end);
|
||||
const ImVec2 text_size =
|
||||
UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f, text_start, text_end);
|
||||
|
||||
const ImVec2 box_min = ImVec2(position.x - image_size.x - text_size.x - spacing - padding * 2.0f,
|
||||
position.y - image_size.y - padding * 2.0f);
|
||||
|
@ -2230,7 +2231,8 @@ void Achievements::DrawGameOverlays()
|
|||
const ImVec2 text_pos =
|
||||
box_min + ImVec2(padding + image_size.x + spacing, (box_max.y - box_min.y - text_size.y) * 0.5f);
|
||||
const ImVec4 text_clip_rect(text_pos.x, text_pos.y, box_max.x, box_max.y);
|
||||
dl->AddText(g_medium_font, g_medium_font->FontSize, text_pos, col, text_start, text_end, 0.0f, &text_clip_rect);
|
||||
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, col, text_start, text_end, 0.0f,
|
||||
&text_clip_rect);
|
||||
|
||||
if (!indicator.active && opacity <= 0.01f)
|
||||
{
|
||||
|
@ -2252,8 +2254,8 @@ void Achievements::DrawGameOverlays()
|
|||
width_string.append(ICON_FA_STOPWATCH);
|
||||
for (u32 i = 0; i < indicator.text.length(); i++)
|
||||
width_string.append('0');
|
||||
const ImVec2 size = ImGuiFullscreen::g_medium_font->CalcTextSizeA(
|
||||
ImGuiFullscreen::g_medium_font->FontSize, FLT_MAX, 0.0f, width_string.c_str(), width_string.end_ptr());
|
||||
const ImVec2 size = ImGuiFullscreen::UIStyle.MediumFont->CalcTextSizeA(
|
||||
ImGuiFullscreen::UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f, width_string.c_str(), width_string.end_ptr());
|
||||
|
||||
const ImVec2 box_min = ImVec2(position.x - size.x - padding * 2.0f, position.y - size.y - padding * 2.0f);
|
||||
const ImVec2 box_max = position;
|
||||
|
@ -2263,17 +2265,17 @@ void Achievements::DrawGameOverlays()
|
|||
dl->AddRect(box_min, box_max, ImGui::GetColorU32(ImVec4(0.8f, 0.8f, 0.8f, opacity)), box_rounding);
|
||||
|
||||
const u32 text_col = ImGui::GetColorU32(ImVec4(1.0f, 1.0f, 1.0f, opacity));
|
||||
const ImVec2 text_size = ImGuiFullscreen::g_medium_font->CalcTextSizeA(
|
||||
ImGuiFullscreen::g_medium_font->FontSize, FLT_MAX, 0.0f, indicator.text.c_str(),
|
||||
const ImVec2 text_size = ImGuiFullscreen::UIStyle.MediumFont->CalcTextSizeA(
|
||||
ImGuiFullscreen::UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f, indicator.text.c_str(),
|
||||
indicator.text.c_str() + indicator.text.length());
|
||||
const ImVec2 text_pos = ImVec2(box_max.x - padding - text_size.x, box_min.y + padding);
|
||||
const ImVec4 text_clip_rect(box_min.x, box_min.y, box_max.x, box_max.y);
|
||||
dl->AddText(g_medium_font, g_medium_font->FontSize, text_pos, text_col, indicator.text.c_str(),
|
||||
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos, text_col, indicator.text.c_str(),
|
||||
indicator.text.c_str() + indicator.text.length(), 0.0f, &text_clip_rect);
|
||||
|
||||
const ImVec2 icon_pos = ImVec2(box_min.x + padding, box_min.y + padding);
|
||||
dl->AddText(g_medium_font, g_medium_font->FontSize, icon_pos, text_col, ICON_FA_STOPWATCH, nullptr, 0.0f,
|
||||
&text_clip_rect);
|
||||
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, icon_pos, text_col, ICON_FA_STOPWATCH, nullptr,
|
||||
0.0f, &text_clip_rect);
|
||||
|
||||
if (!indicator.active && opacity <= 0.01f)
|
||||
{
|
||||
|
@ -2297,9 +2299,8 @@ void Achievements::DrawGameOverlays()
|
|||
|
||||
void Achievements::DrawPauseMenuOverlays()
|
||||
{
|
||||
using ImGuiFullscreen::g_large_font;
|
||||
using ImGuiFullscreen::g_medium_font;
|
||||
using ImGuiFullscreen::LayoutScale;
|
||||
using ImGuiFullscreen::UIStyle;
|
||||
|
||||
if (!HasActiveGame())
|
||||
return;
|
||||
|
@ -2310,11 +2311,12 @@ void Achievements::DrawPauseMenuOverlays()
|
|||
return;
|
||||
|
||||
const ImGuiIO& io = ImGui::GetIO();
|
||||
ImFont* font = g_medium_font;
|
||||
ImFont* font = UIStyle.MediumFont;
|
||||
|
||||
const ImVec2 image_size(LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
|
||||
ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY));
|
||||
const float start_y = LayoutScale(10.0f + 4.0f + 4.0f) + g_large_font->FontSize + (g_medium_font->FontSize * 2.0f);
|
||||
const float start_y =
|
||||
LayoutScale(10.0f + 4.0f + 4.0f) + UIStyle.LargeFont->FontSize + (UIStyle.MediumFont->FontSize * 2.0f);
|
||||
const float margin = LayoutScale(10.0f);
|
||||
const float spacing = LayoutScale(10.0f);
|
||||
const float padding = LayoutScale(10.0f);
|
||||
|
@ -2398,9 +2400,8 @@ bool Achievements::PrepareAchievementsWindow()
|
|||
|
||||
void Achievements::DrawAchievementsWindow()
|
||||
{
|
||||
using ImGuiFullscreen::g_large_font;
|
||||
using ImGuiFullscreen::g_medium_font;
|
||||
using ImGuiFullscreen::LayoutScale;
|
||||
using ImGuiFullscreen::UIStyle;
|
||||
|
||||
if (!s_state.achievement_list)
|
||||
return;
|
||||
|
@ -2411,10 +2412,10 @@ void Achievements::DrawAchievementsWindow()
|
|||
static constexpr float heading_alpha = 0.95f;
|
||||
static constexpr float heading_height_unscaled = 110.0f;
|
||||
|
||||
const ImVec4 background = ImGuiFullscreen::ModAlpha(ImGuiFullscreen::UIBackgroundColor, alpha);
|
||||
const ImVec4 heading_background = ImGuiFullscreen::ModAlpha(ImGuiFullscreen::UIBackgroundColor, heading_alpha);
|
||||
const ImVec4 background = ImGuiFullscreen::ModAlpha(UIStyle.BackgroundColor, alpha);
|
||||
const ImVec4 heading_background = ImGuiFullscreen::ModAlpha(UIStyle.BackgroundColor, heading_alpha);
|
||||
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
|
||||
const float heading_height = ImGuiFullscreen::LayoutScale(heading_height_unscaled);
|
||||
const float heading_height = LayoutScale(heading_height_unscaled);
|
||||
bool close_window = false;
|
||||
|
||||
if (ImGuiFullscreen::BeginFullscreenWindow(
|
||||
|
@ -2427,9 +2428,9 @@ void Achievements::DrawAchievementsWindow()
|
|||
&bb.Min, &bb.Max, 0, heading_alpha);
|
||||
if (visible)
|
||||
{
|
||||
const float padding = ImGuiFullscreen::LayoutScale(10.0f);
|
||||
const float spacing = ImGuiFullscreen::LayoutScale(10.0f);
|
||||
const float image_height = ImGuiFullscreen::LayoutScale(85.0f);
|
||||
const float padding = LayoutScale(10.0f);
|
||||
const float spacing = LayoutScale(10.0f);
|
||||
const float image_height = LayoutScale(85.0f);
|
||||
|
||||
const ImVec2 icon_min(bb.Min + ImVec2(padding, padding));
|
||||
const ImVec2 icon_max(icon_min + ImVec2(image_height, image_height));
|
||||
|
@ -2452,23 +2453,23 @@ void Achievements::DrawAchievementsWindow()
|
|||
ImVec2 text_size;
|
||||
|
||||
close_window = (ImGuiFullscreen::FloatingButton(ICON_FA_WINDOW_CLOSE, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f,
|
||||
true, g_large_font) ||
|
||||
true, UIStyle.LargeFont) ||
|
||||
ImGuiFullscreen::WantsToCloseMenu());
|
||||
|
||||
const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize));
|
||||
const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.LargeFont->FontSize));
|
||||
text.assign(s_state.game_title);
|
||||
|
||||
if (s_state.hardcore_mode)
|
||||
text.append(TRANSLATE_SV("Achievements", " (Hardcore Mode)"));
|
||||
|
||||
top += g_large_font->FontSize + spacing;
|
||||
top += UIStyle.LargeFont->FontSize + spacing;
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::PushFont(UIStyle.LargeFont);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f),
|
||||
&title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + g_medium_font->FontSize));
|
||||
const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.MediumFont->FontSize));
|
||||
if (s_state.game_summary.num_core_achievements > 0)
|
||||
{
|
||||
if (s_state.game_summary.num_unlocked_achievements == s_state.game_summary.num_core_achievements)
|
||||
|
@ -2489,31 +2490,31 @@ void Achievements::DrawAchievementsWindow()
|
|||
text.assign(TRANSLATE_SV("Achievements", "This game has no achievements."));
|
||||
}
|
||||
|
||||
top += g_medium_font->FontSize + spacing;
|
||||
top += UIStyle.MediumFont->FontSize + spacing;
|
||||
|
||||
ImGui::PushFont(g_medium_font);
|
||||
ImGui::PushFont(UIStyle.MediumFont);
|
||||
ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, text.c_str(), text.end_ptr(), nullptr,
|
||||
ImVec2(0.0f, 0.0f), &summary_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (s_state.game_summary.num_core_achievements > 0)
|
||||
{
|
||||
const float progress_height = ImGuiFullscreen::LayoutScale(20.0f);
|
||||
const float progress_height = LayoutScale(20.0f);
|
||||
const ImRect progress_bb(ImVec2(left, top), ImVec2(right, top + progress_height));
|
||||
const float fraction = static_cast<float>(s_state.game_summary.num_unlocked_achievements) /
|
||||
static_cast<float>(s_state.game_summary.num_core_achievements);
|
||||
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryDarkColor));
|
||||
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(UIStyle.PrimaryDarkColor));
|
||||
dl->AddRectFilled(progress_bb.Min,
|
||||
ImVec2(progress_bb.Min.x + fraction * progress_bb.GetWidth(), progress_bb.Max.y),
|
||||
ImGui::GetColorU32(ImGuiFullscreen::UISecondaryColor));
|
||||
ImGui::GetColorU32(UIStyle.SecondaryColor));
|
||||
|
||||
text.format("{}%", static_cast<int>(std::round(fraction * 100.0f)));
|
||||
text_size = ImGui::CalcTextSize(text.c_str(), text.end_ptr());
|
||||
const ImVec2 text_pos(
|
||||
progress_bb.Min.x + ((progress_bb.Max.x - progress_bb.Min.x) / 2.0f) - (text_size.x / 2.0f),
|
||||
progress_bb.Min.y + ((progress_bb.Max.y - progress_bb.Min.y) / 2.0f) - (text_size.y / 2.0f));
|
||||
dl->AddText(g_medium_font, g_medium_font->FontSize, text_pos,
|
||||
ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryTextColor), text.c_str(), text.end_ptr());
|
||||
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos,
|
||||
ImGui::GetColorU32(UIStyle.PrimaryTextColor), text.c_str(), text.end_ptr());
|
||||
top += progress_height + spacing;
|
||||
}
|
||||
}
|
||||
|
@ -2587,10 +2588,9 @@ void Achievements::DrawAchievementsWindow()
|
|||
|
||||
void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
|
||||
{
|
||||
using ImGuiFullscreen::g_large_font;
|
||||
using ImGuiFullscreen::g_medium_font;
|
||||
using ImGuiFullscreen::LayoutScale;
|
||||
using ImGuiFullscreen::LayoutUnscale;
|
||||
using ImGuiFullscreen::UIStyle;
|
||||
|
||||
static constexpr float alpha = 0.8f;
|
||||
static constexpr float progress_height_unscaled = 20.0f;
|
||||
|
@ -2602,18 +2602,19 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
|
|||
const std::string_view measured_progress(cheevo->measured_progress);
|
||||
const bool is_measured = !is_unlocked && !measured_progress.empty();
|
||||
const float unlock_size = is_unlocked ? (spacing + ImGuiFullscreen::LAYOUT_MEDIUM_FONT_SIZE) : 0.0f;
|
||||
const ImVec2 points_template_size(
|
||||
g_medium_font->CalcTextSizeA(g_medium_font->FontSize, FLT_MAX, 0.0f, TRANSLATE("Achievements", "XXX points")));
|
||||
const ImVec2 points_template_size(UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f,
|
||||
TRANSLATE("Achievements", "XXX points")));
|
||||
|
||||
const size_t summary_length = std::strlen(cheevo->description);
|
||||
const float summary_wrap_width =
|
||||
(ImGui::GetCurrentWindow()->WorkRect.GetWidth() - (ImGui::GetStyle().FramePadding.x * 2.0f) -
|
||||
LayoutScale(ImGuiFullscreen::LAYOUT_MENU_BUTTON_HEIGHT + 30.0f) - points_template_size.x);
|
||||
const ImVec2 summary_text_size(g_medium_font->CalcTextSizeA(
|
||||
g_medium_font->FontSize, FLT_MAX, summary_wrap_width, cheevo->description, cheevo->description + summary_length));
|
||||
const ImVec2 summary_text_size(UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX,
|
||||
summary_wrap_width, cheevo->description,
|
||||
cheevo->description + summary_length));
|
||||
|
||||
// Messy, but need to undo LayoutScale in MenuButtonFrame()...
|
||||
const float extra_summary_height = LayoutUnscale(std::max(summary_text_size.y - g_medium_font->FontSize, 0.0f));
|
||||
const float extra_summary_height = LayoutUnscale(std::max(summary_text_size.y - UIStyle.MediumFont->FontSize, 0.0f));
|
||||
|
||||
ImRect bb;
|
||||
bool visible, hovered;
|
||||
|
@ -2653,10 +2654,10 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
|
|||
|
||||
SmallString text;
|
||||
|
||||
const float midpoint = bb.Min.y + g_large_font->FontSize + spacing;
|
||||
const float midpoint = bb.Min.y + UIStyle.LargeFont->FontSize + spacing;
|
||||
text = TRANSLATE_PLURAL_SSTR("Achievements", "%n points", "Achievement points", cheevo->points);
|
||||
const ImVec2 points_size(
|
||||
g_medium_font->CalcTextSizeA(g_medium_font->FontSize, FLT_MAX, 0.0f, text.c_str(), text.end_ptr()));
|
||||
UIStyle.MediumFont->CalcTextSizeA(UIStyle.MediumFont->FontSize, FLT_MAX, 0.0f, text.c_str(), text.end_ptr()));
|
||||
const float points_template_start = bb.Max.x - points_template_size.x;
|
||||
const float points_start = points_template_start + ((points_template_size.x - points_size.x) * 0.5f);
|
||||
|
||||
|
@ -2682,23 +2683,24 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
|
|||
break;
|
||||
}
|
||||
|
||||
const ImVec2 right_icon_size(g_large_font->CalcTextSizeA(g_large_font->FontSize, FLT_MAX, 0.0f, right_icon_text));
|
||||
const ImVec2 right_icon_size(
|
||||
UIStyle.LargeFont->CalcTextSizeA(UIStyle.LargeFont->FontSize, FLT_MAX, 0.0f, right_icon_text));
|
||||
|
||||
const float text_start_x = bb.Min.x + image_size.x + LayoutScale(15.0f);
|
||||
const ImRect title_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(points_start, midpoint));
|
||||
const ImRect summary_bb(ImVec2(text_start_x, midpoint),
|
||||
ImVec2(points_start, midpoint + g_medium_font->FontSize + extra_summary_height));
|
||||
ImVec2(points_start, midpoint + UIStyle.MediumFont->FontSize + extra_summary_height));
|
||||
const ImRect points_bb(ImVec2(points_start, midpoint), bb.Max);
|
||||
const ImRect lock_bb(ImVec2(points_template_start + ((points_template_size.x - right_icon_size.x) * 0.5f), bb.Min.y),
|
||||
ImVec2(bb.Max.x, midpoint));
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::PushFont(UIStyle.LargeFont);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, cheevo->title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGui::RenderTextClipped(lock_bb.Min, lock_bb.Max, right_icon_text, nullptr, &right_icon_size, ImVec2(0.0f, 0.0f),
|
||||
&lock_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
ImGui::PushFont(g_medium_font);
|
||||
ImGui::PushFont(UIStyle.MediumFont);
|
||||
if (cheevo->description && summary_length > 0)
|
||||
{
|
||||
ImGui::RenderTextWrapped(summary_bb.Min, cheevo->description, cheevo->description + summary_length,
|
||||
|
@ -2722,19 +2724,19 @@ void Achievements::DrawAchievement(const rc_client_achievement_t* cheevo)
|
|||
ImDrawList* dl = ImGui::GetWindowDrawList();
|
||||
const float progress_height = LayoutScale(progress_height_unscaled);
|
||||
const float progress_spacing = LayoutScale(progress_spacing_unscaled);
|
||||
const float top = midpoint + g_medium_font->FontSize + progress_spacing;
|
||||
const float top = midpoint + UIStyle.MediumFont->FontSize + progress_spacing;
|
||||
const ImRect progress_bb(ImVec2(text_start_x, top), ImVec2(bb.Max.x, top + progress_height));
|
||||
const float fraction = cheevo->measured_percent * 0.01f;
|
||||
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryDarkColor));
|
||||
dl->AddRectFilled(progress_bb.Min, progress_bb.Max, ImGui::GetColorU32(ImGuiFullscreen::UIStyle.PrimaryDarkColor));
|
||||
dl->AddRectFilled(progress_bb.Min, ImVec2(progress_bb.Min.x + fraction * progress_bb.GetWidth(), progress_bb.Max.y),
|
||||
ImGui::GetColorU32(ImGuiFullscreen::UISecondaryColor));
|
||||
ImGui::GetColorU32(ImGuiFullscreen::UIStyle.SecondaryColor));
|
||||
|
||||
const ImVec2 text_size =
|
||||
ImGui::CalcTextSize(measured_progress.data(), measured_progress.data() + measured_progress.size());
|
||||
const ImVec2 text_pos(progress_bb.Min.x + ((progress_bb.Max.x - progress_bb.Min.x) / 2.0f) - (text_size.x / 2.0f),
|
||||
progress_bb.Min.y + ((progress_bb.Max.y - progress_bb.Min.y) / 2.0f) - (text_size.y / 2.0f));
|
||||
dl->AddText(g_medium_font, g_medium_font->FontSize, text_pos,
|
||||
ImGui::GetColorU32(ImGuiFullscreen::UIPrimaryTextColor), measured_progress.data(),
|
||||
dl->AddText(UIStyle.MediumFont, UIStyle.MediumFont->FontSize, text_pos,
|
||||
ImGui::GetColorU32(ImGuiFullscreen::UIStyle.PrimaryTextColor), measured_progress.data(),
|
||||
measured_progress.data() + measured_progress.size());
|
||||
}
|
||||
|
||||
|
@ -2769,9 +2771,8 @@ bool Achievements::PrepareLeaderboardsWindow()
|
|||
|
||||
void Achievements::DrawLeaderboardsWindow()
|
||||
{
|
||||
using ImGuiFullscreen::g_large_font;
|
||||
using ImGuiFullscreen::g_medium_font;
|
||||
using ImGuiFullscreen::LayoutScale;
|
||||
using ImGuiFullscreen::UIStyle;
|
||||
|
||||
static constexpr float alpha = 0.8f;
|
||||
static constexpr float heading_alpha = 0.95f;
|
||||
|
@ -2785,8 +2786,8 @@ void Achievements::DrawLeaderboardsWindow()
|
|||
|
||||
ImRect bb;
|
||||
|
||||
const ImVec4 background = ImGuiFullscreen::ModAlpha(ImGuiFullscreen::UIBackgroundColor, alpha);
|
||||
const ImVec4 heading_background = ImGuiFullscreen::ModAlpha(ImGuiFullscreen::UIBackgroundColor, heading_alpha);
|
||||
const ImVec4 background = ImGuiFullscreen::ModAlpha(ImGuiFullscreen::UIStyle.BackgroundColor, alpha);
|
||||
const ImVec4 heading_background = ImGuiFullscreen::ModAlpha(ImGuiFullscreen::UIStyle.BackgroundColor, heading_alpha);
|
||||
const ImVec2 display_size = ImGui::GetIO().DisplaySize;
|
||||
const float padding = LayoutScale(10.0f);
|
||||
const float spacing = LayoutScale(10.0f);
|
||||
|
@ -2802,13 +2803,15 @@ void Achievements::DrawLeaderboardsWindow()
|
|||
}
|
||||
|
||||
const float rank_column_width =
|
||||
g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits<float>::max(), -1.0f, "99999").x;
|
||||
UIStyle.LargeFont->CalcTextSizeA(UIStyle.LargeFont->FontSize, std::numeric_limits<float>::max(), -1.0f, "99999").x;
|
||||
const float name_column_width =
|
||||
g_large_font
|
||||
->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits<float>::max(), -1.0f, "WWWWWWWWWWWWWWWWWWWWWW")
|
||||
UIStyle.LargeFont
|
||||
->CalcTextSizeA(UIStyle.LargeFont->FontSize, std::numeric_limits<float>::max(), -1.0f, "WWWWWWWWWWWWWWWWWWWWWW")
|
||||
.x;
|
||||
const float time_column_width =
|
||||
g_large_font->CalcTextSizeA(g_large_font->FontSize, std::numeric_limits<float>::max(), -1.0f, "WWWWWWWWWWW").x;
|
||||
UIStyle.LargeFont
|
||||
->CalcTextSizeA(UIStyle.LargeFont->FontSize, std::numeric_limits<float>::max(), -1.0f, "WWWWWWWWWWW")
|
||||
.x;
|
||||
const float column_spacing = spacing * 2.0f;
|
||||
|
||||
if (ImGuiFullscreen::BeginFullscreenWindow(
|
||||
|
@ -2845,7 +2848,7 @@ void Achievements::DrawLeaderboardsWindow()
|
|||
if (!is_leaderboard_open)
|
||||
{
|
||||
if (ImGuiFullscreen::FloatingButton(ICON_FA_WINDOW_CLOSE, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f, true,
|
||||
g_large_font) ||
|
||||
UIStyle.LargeFont) ||
|
||||
ImGuiFullscreen::WantsToCloseMenu())
|
||||
{
|
||||
FullscreenUI::ReturnToPreviousWindow();
|
||||
|
@ -2854,31 +2857,31 @@ void Achievements::DrawLeaderboardsWindow()
|
|||
else
|
||||
{
|
||||
if (ImGuiFullscreen::FloatingButton(ICON_FA_CARET_SQUARE_LEFT, 10.0f, 10.0f, -1.0f, -1.0f, 1.0f, 0.0f, true,
|
||||
g_large_font) ||
|
||||
UIStyle.LargeFont) ||
|
||||
ImGuiFullscreen::WantsToCloseMenu())
|
||||
{
|
||||
close_leaderboard_on_exit = true;
|
||||
}
|
||||
}
|
||||
|
||||
const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize));
|
||||
const ImRect title_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.LargeFont->FontSize));
|
||||
text.assign(Achievements::GetGameTitle());
|
||||
|
||||
top += g_large_font->FontSize + spacing;
|
||||
top += UIStyle.LargeFont->FontSize + spacing;
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::PushFont(UIStyle.LargeFont);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, text.c_str(), text.end_ptr(), nullptr, ImVec2(0.0f, 0.0f),
|
||||
&title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (is_leaderboard_open)
|
||||
{
|
||||
const ImRect subtitle_bb(ImVec2(left, top), ImVec2(right, top + g_large_font->FontSize));
|
||||
const ImRect subtitle_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.LargeFont->FontSize));
|
||||
text.assign(s_state.open_leaderboard->title);
|
||||
|
||||
top += g_large_font->FontSize + spacing_small;
|
||||
top += UIStyle.LargeFont->FontSize + spacing_small;
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::PushFont(UIStyle.LargeFont);
|
||||
ImGui::RenderTextClipped(subtitle_bb.Min, subtitle_bb.Max, text.c_str(), text.end_ptr(), nullptr,
|
||||
ImVec2(0.0f, 0.0f), &subtitle_bb);
|
||||
ImGui::PopFont();
|
||||
|
@ -2893,17 +2896,17 @@ void Achievements::DrawLeaderboardsWindow()
|
|||
text = TRANSLATE_PLURAL_SSTR("Achievements", "This game has %n leaderboards.", "Leaderboard count", count);
|
||||
}
|
||||
|
||||
const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + g_medium_font->FontSize));
|
||||
top += g_medium_font->FontSize + spacing_small;
|
||||
const ImRect summary_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.MediumFont->FontSize));
|
||||
top += UIStyle.MediumFont->FontSize + spacing_small;
|
||||
|
||||
ImGui::PushFont(g_medium_font);
|
||||
ImGui::PushFont(UIStyle.MediumFont);
|
||||
ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, text.c_str(), text.end_ptr(), nullptr,
|
||||
ImVec2(0.0f, 0.0f), &summary_bb);
|
||||
|
||||
if (!is_leaderboard_open && !Achievements::IsHardcoreModeActive())
|
||||
{
|
||||
const ImRect hardcore_warning_bb(ImVec2(left, top), ImVec2(right, top + g_medium_font->FontSize));
|
||||
top += g_medium_font->FontSize + spacing_small;
|
||||
const ImRect hardcore_warning_bb(ImVec2(left, top), ImVec2(right, top + UIStyle.MediumFont->FontSize));
|
||||
top += UIStyle.MediumFont->FontSize + spacing_small;
|
||||
|
||||
ImGui::RenderTextClipped(
|
||||
hardcore_warning_bb.Min, hardcore_warning_bb.Max,
|
||||
|
@ -2916,7 +2919,7 @@ void Achievements::DrawLeaderboardsWindow()
|
|||
|
||||
if (is_leaderboard_open)
|
||||
{
|
||||
const float tab_width = (ImGui::GetWindowWidth() / ImGuiFullscreen::g_layout_scale) * 0.5f;
|
||||
const float tab_width = (ImGui::GetWindowWidth() / ImGuiFullscreen::UIStyle.LayoutScale) * 0.5f;
|
||||
ImGui::SetCursorPos(ImVec2(0.0f, top + spacing_small));
|
||||
|
||||
if (ImGui::IsKeyPressed(ImGuiKey_GamepadDpadLeft, false) ||
|
||||
|
@ -2953,10 +2956,10 @@ void Achievements::DrawLeaderboardsWindow()
|
|||
&visible, &hovered, &bb.Min, &bb.Max, 0, alpha);
|
||||
UNREFERENCED_VARIABLE(pressed);
|
||||
|
||||
const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f);
|
||||
const float midpoint = bb.Min.y + UIStyle.LargeFont->FontSize + LayoutScale(4.0f);
|
||||
float text_start_x = bb.Min.x + LayoutScale(15.0f) + padding;
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::PushFont(UIStyle.LargeFont);
|
||||
|
||||
const ImRect rank_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
ImGui::RenderTextClipped(rank_bb.Min, rank_bb.Max, TRANSLATE("Achievements", "Rank"), nullptr, nullptr,
|
||||
|
@ -2991,7 +2994,7 @@ void Achievements::DrawLeaderboardsWindow()
|
|||
|
||||
const float line_thickness = LayoutScale(1.0f);
|
||||
const float line_padding = LayoutScale(5.0f);
|
||||
const ImVec2 line_start(bb.Min.x, bb.Min.y + g_large_font->FontSize + line_padding);
|
||||
const ImVec2 line_start(bb.Min.x, bb.Min.y + UIStyle.LargeFont->FontSize + line_padding);
|
||||
const ImVec2 line_end(bb.Max.x, line_start.y);
|
||||
ImGui::GetWindowDrawList()->AddLine(line_start, line_end, ImGui::GetColorU32(ImGuiCol_TextDisabled),
|
||||
line_thickness);
|
||||
|
@ -3063,7 +3066,7 @@ void Achievements::DrawLeaderboardsWindow()
|
|||
}
|
||||
else
|
||||
{
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::PushFont(UIStyle.LargeFont);
|
||||
|
||||
const ImVec2 pos_min(0.0f, heading_height);
|
||||
const ImVec2 pos_max(display_size.x, display_size.y);
|
||||
|
@ -3092,10 +3095,10 @@ void Achievements::DrawLeaderboardsWindow()
|
|||
&bb.Min, &bb.Max);
|
||||
if (visible)
|
||||
{
|
||||
const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f);
|
||||
const float midpoint = bb.Min.y + UIStyle.LargeFont->FontSize + LayoutScale(4.0f);
|
||||
const ImRect title_bb(bb.Min, ImVec2(bb.Max.x, midpoint));
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::PushFont(UIStyle.LargeFont);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, TRANSLATE("Achievements", "Loading..."), nullptr,
|
||||
nullptr, ImVec2(0, 0), &title_bb);
|
||||
ImGui::PopFont();
|
||||
|
@ -3130,8 +3133,8 @@ void Achievements::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent
|
|||
float rank_column_width, float name_column_width, float time_column_width,
|
||||
float column_spacing)
|
||||
{
|
||||
using ImGuiFullscreen::g_large_font;
|
||||
using ImGuiFullscreen::LayoutScale;
|
||||
using ImGuiFullscreen::UIStyle;
|
||||
|
||||
static constexpr float alpha = 0.8f;
|
||||
|
||||
|
@ -3143,13 +3146,13 @@ void Achievements::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent
|
|||
if (!visible)
|
||||
return;
|
||||
|
||||
const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f);
|
||||
const float midpoint = bb.Min.y + UIStyle.LargeFont->FontSize + LayoutScale(4.0f);
|
||||
float text_start_x = bb.Min.x + LayoutScale(15.0f);
|
||||
SmallString text;
|
||||
|
||||
text.format("{}", entry.rank);
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::PushFont(UIStyle.LargeFont);
|
||||
|
||||
if (is_self)
|
||||
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(255, 242, 0, 255));
|
||||
|
@ -3212,9 +3215,8 @@ void Achievements::DrawLeaderboardEntry(const rc_client_leaderboard_entry_t& ent
|
|||
}
|
||||
void Achievements::DrawLeaderboardListEntry(const rc_client_leaderboard_t* lboard)
|
||||
{
|
||||
using ImGuiFullscreen::g_large_font;
|
||||
using ImGuiFullscreen::g_medium_font;
|
||||
using ImGuiFullscreen::LayoutScale;
|
||||
using ImGuiFullscreen::UIStyle;
|
||||
|
||||
static constexpr float alpha = 0.8f;
|
||||
|
||||
|
@ -3228,18 +3230,18 @@ void Achievements::DrawLeaderboardListEntry(const rc_client_leaderboard_t* lboar
|
|||
if (!visible)
|
||||
return;
|
||||
|
||||
const float midpoint = bb.Min.y + g_large_font->FontSize + LayoutScale(4.0f);
|
||||
const float midpoint = bb.Min.y + UIStyle.LargeFont->FontSize + LayoutScale(4.0f);
|
||||
const float text_start_x = bb.Min.x + LayoutScale(15.0f);
|
||||
const ImRect title_bb(ImVec2(text_start_x, bb.Min.y), ImVec2(bb.Max.x, midpoint));
|
||||
const ImRect summary_bb(ImVec2(text_start_x, midpoint), bb.Max);
|
||||
|
||||
ImGui::PushFont(g_large_font);
|
||||
ImGui::PushFont(UIStyle.LargeFont);
|
||||
ImGui::RenderTextClipped(title_bb.Min, title_bb.Max, lboard->title, nullptr, nullptr, ImVec2(0.0f, 0.0f), &title_bb);
|
||||
ImGui::PopFont();
|
||||
|
||||
if (lboard->description && lboard->description[0] != '\0')
|
||||
{
|
||||
ImGui::PushFont(g_medium_font);
|
||||
ImGui::PushFont(UIStyle.MediumFont);
|
||||
ImGui::RenderTextClipped(summary_bb.Min, summary_bb.Max, lboard->description, nullptr, nullptr, ImVec2(0.0f, 0.0f),
|
||||
&summary_bb);
|
||||
ImGui::PopFont();
|
||||
|
|
|
@ -123,8 +123,8 @@ std::unique_ptr<CDROMSubQReplacement> CDROMSubQReplacement::LoadLSD(const std::s
|
|||
return ret;
|
||||
}
|
||||
|
||||
bool CDROMSubQReplacement::LoadForImage(std::unique_ptr<CDROMSubQReplacement>* ret, CDImage* image, std::string_view serial,
|
||||
std::string_view title, Error* error)
|
||||
bool CDROMSubQReplacement::LoadForImage(std::unique_ptr<CDROMSubQReplacement>* ret, CDImage* image,
|
||||
std::string_view serial, std::string_view title, Error* error)
|
||||
{
|
||||
struct FileLoader
|
||||
{
|
||||
|
@ -140,13 +140,19 @@ bool CDROMSubQReplacement::LoadForImage(std::unique_ptr<CDROMSubQReplacement>* r
|
|||
std::string path;
|
||||
|
||||
// Try sbi/lsd in the directory first.
|
||||
for (const FileLoader& loader : loaders)
|
||||
if (!CDImage::IsDeviceName(image_path.c_str()))
|
||||
{
|
||||
path = Path::ReplaceExtension(image_path, loader.extension);
|
||||
if (FileSystem::FileExists(path.c_str()))
|
||||
for (const FileLoader& loader : loaders)
|
||||
{
|
||||
*ret = loader.func(path, error);
|
||||
return static_cast<bool>(*ret);
|
||||
path = Path::ReplaceExtension(image_path, loader.extension);
|
||||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
*ret = loader.func(path, error);
|
||||
if (!static_cast<bool>(*ret))
|
||||
Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));
|
||||
|
||||
return static_cast<bool>(*ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -161,6 +167,9 @@ bool CDROMSubQReplacement::LoadForImage(std::unique_ptr<CDROMSubQReplacement>* r
|
|||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
*ret = loader.func(path, error);
|
||||
if (!static_cast<bool>(*ret))
|
||||
Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));
|
||||
|
||||
return static_cast<bool>(*ret);
|
||||
}
|
||||
}
|
||||
|
@ -175,6 +184,9 @@ bool CDROMSubQReplacement::LoadForImage(std::unique_ptr<CDROMSubQReplacement>* r
|
|||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
*ret = loader.func(path, error);
|
||||
if (!static_cast<bool>(*ret))
|
||||
Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));
|
||||
|
||||
return static_cast<bool>(*ret);
|
||||
}
|
||||
}
|
||||
|
@ -188,6 +200,9 @@ bool CDROMSubQReplacement::LoadForImage(std::unique_ptr<CDROMSubQReplacement>* r
|
|||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
*ret = loader.func(path, error);
|
||||
if (!static_cast<bool>(*ret))
|
||||
Error::AddPrefixFmt(error, "Failed to load subchannel data from {}: ", Path::GetFileName(path));
|
||||
|
||||
return static_cast<bool>(*ret);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -107,11 +107,17 @@ public:
|
|||
|
||||
ALWAYS_INLINE bool IsOpen() const { return static_cast<bool>(m_zip); }
|
||||
|
||||
bool Open(const char* name)
|
||||
bool Open(bool cheats)
|
||||
{
|
||||
if (m_zip)
|
||||
return true;
|
||||
|
||||
#ifndef __ANDROID__
|
||||
const char* name = cheats ? "cheats.zip" : "patches.zip";
|
||||
#else
|
||||
const char* name = cheats ? "patchcodes.zip" : "patches.zip";
|
||||
#endif
|
||||
|
||||
Error error;
|
||||
std::optional<DynamicHeapArray<u8>> data = Host::ReadResourceFile(name, false, &error);
|
||||
if (!data.has_value())
|
||||
|
@ -344,15 +350,16 @@ std::vector<std::string> Cheats::FindChtFilesOnDisk(const std::string_view seria
|
|||
std::vector<std::string> ret;
|
||||
FileSystem::FindResultsArray files;
|
||||
FileSystem::FindFiles(cheats ? EmuFolders::Cheats.c_str() : EmuFolders::Patches.c_str(),
|
||||
GetChtTemplate(serial, hash, true).c_str(),
|
||||
GetChtTemplate(serial, std::nullopt, true).c_str(),
|
||||
FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_HIDDEN_FILES, &files);
|
||||
ret.reserve(files.size());
|
||||
|
||||
for (FILESYSTEM_FIND_DATA& fd : files)
|
||||
{
|
||||
// Skip mismatched hashes.
|
||||
if (hash.has_value())
|
||||
{
|
||||
if (const std::string_view filename = Path::GetFileTitle(fd.FileName); filename.length() >= serial.length() + 18)
|
||||
if (const std::string_view filename = Path::GetFileTitle(fd.FileName); filename.length() >= serial.length() + 17)
|
||||
{
|
||||
const std::string_view filename_hash = filename.substr(serial.length() + 1, 16);
|
||||
const std::optional filename_parsed_hash = StringUtil::FromChars<GameHash>(filename_hash, 16);
|
||||
|
@ -398,10 +405,7 @@ void Cheats::EnumerateChtFiles(const std::string_view serial, std::optional<Game
|
|||
const std::unique_lock lock(s_zip_mutex);
|
||||
CheatArchive& archive = cheats ? s_cheats_zip : s_patches_zip;
|
||||
if (!archive.IsOpen())
|
||||
{
|
||||
const char* archive_name = cheats ? "cheats.zip" : "patches.zip";
|
||||
archive.Open(archive_name);
|
||||
}
|
||||
archive.Open(cheats);
|
||||
|
||||
if (archive.IsOpen())
|
||||
{
|
||||
|
@ -428,12 +432,12 @@ void Cheats::EnumerateChtFiles(const std::string_view serial, std::optional<Game
|
|||
std::vector<std::string> disk_patch_files;
|
||||
if (for_ui || !Achievements::IsHardcoreModeActive())
|
||||
{
|
||||
disk_patch_files = FindChtFilesOnDisk(serial, for_ui ? hash : std::nullopt, cheats);
|
||||
disk_patch_files = FindChtFilesOnDisk(serial, for_ui ? std::nullopt : hash, cheats);
|
||||
if (cheats && disk_patch_files.empty())
|
||||
{
|
||||
// Check if there's an old-format titled file.
|
||||
if (ImportOldChtFile(serial))
|
||||
disk_patch_files = FindChtFilesOnDisk(serial, for_ui ? hash : std::nullopt, cheats);
|
||||
disk_patch_files = FindChtFilesOnDisk(serial, for_ui ? std::nullopt : hash, cheats);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -701,6 +705,36 @@ bool Cheats::SaveCodesToFile(const char* path, const CodeInfoList& codes, Error*
|
|||
return true;
|
||||
}
|
||||
|
||||
void Cheats::RemoveAllCodes(const std::string_view serial, const std::string_view title, std::optional<GameHash> hash)
|
||||
{
|
||||
Error error;
|
||||
std::string path = GetChtFilename(serial, hash, true);
|
||||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
if (!FileSystem::DeleteFile(path.c_str(), &error))
|
||||
ERROR_LOG("Failed to remove cht file '{}': {}", Path::GetFileName(path), error.GetDescription());
|
||||
}
|
||||
|
||||
// check for a non-hashed path and remove that too
|
||||
path = GetChtFilename(serial, std::nullopt, true);
|
||||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
if (!FileSystem::DeleteFile(path.c_str(), &error))
|
||||
ERROR_LOG("Failed to remove cht file '{}': {}", Path::GetFileName(path), error.GetDescription());
|
||||
}
|
||||
|
||||
// and a legacy cht file with the game title
|
||||
if (!title.empty())
|
||||
{
|
||||
path = fmt::format("{}" FS_OSPATH_SEPARATOR_STR "{}.cht", EmuFolders::Cheats, Path::SanitizeFileName(title));
|
||||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
if (!FileSystem::DeleteFile(path.c_str(), &error))
|
||||
ERROR_LOG("Failed to remove cht file '{}': {}", Path::GetFileName(path), error.GetDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string Cheats::GetChtFilename(const std::string_view serial, std::optional<GameHash> hash, bool cheats)
|
||||
{
|
||||
return Path::Combine(cheats ? EmuFolders::Cheats : EmuFolders::Patches, GetChtTemplate(serial, hash, false));
|
||||
|
|
|
@ -117,6 +117,9 @@ extern bool UpdateCodeInFile(const char* path, const std::string_view name, cons
|
|||
/// Updates or adds multiple codes to the file, rewriting it.
|
||||
extern bool SaveCodesToFile(const char* path, const CodeInfoList& codes, Error* error);
|
||||
|
||||
/// Removes any .cht files for the specified game.
|
||||
extern void RemoveAllCodes(const std::string_view serial, const std::string_view title, std::optional<GameHash> hash);
|
||||
|
||||
/// Returns the path to a new cheat/patch cht for the specified serial and hash.
|
||||
extern std::string GetChtFilename(const std::string_view serial, std::optional<GameHash> hash, bool cheats);
|
||||
|
||||
|
|
|
@ -130,10 +130,11 @@ static constexpr u32 RECOMPILER_CODE_CACHE_SIZE = 48 * 1024 * 1024;
|
|||
static constexpr u32 RECOMPILER_FAR_CODE_CACHE_SIZE = 16 * 1024 * 1024;
|
||||
#endif
|
||||
|
||||
// On Linux ARM32/ARM64, we use a dedicated section in the ELF for storing code.
|
||||
// This is because without ASLR, or on certain ASLR offsets, the sbrk() heap ends up immediately following the text/data
|
||||
// sections, which means there isn't a large enough gap to fit within range on ARM32.
|
||||
#if defined(__linux__) && (defined(CPU_ARCH_ARM32) || defined(CPU_ARCH_ARM64))
|
||||
// On Linux ARM32/ARM64, we use a dedicated section in the ELF for storing code. This is because without
|
||||
// ASLR, or on certain ASLR offsets, the sbrk() heap ends up immediately following the text/data sections,
|
||||
// which means there isn't a large enough gap to fit within range on ARM32. Also enable it for Android,
|
||||
// because MAP_FIXED_NOREPLACE may not exist on older kernels.
|
||||
#if (defined(__linux__) && (defined(CPU_ARCH_ARM32) || defined(CPU_ARCH_ARM64))) || defined(__ANDROID__)
|
||||
#define USE_CODE_BUFFER_SECTION 1
|
||||
#ifdef __clang__
|
||||
#pragma clang section bss = ".jitstorage"
|
||||
|
|
|
@ -92,12 +92,12 @@ void armEmitMov(vixl::aarch32::Assembler* armAsm, const vixl::aarch32::Register&
|
|||
{
|
||||
if (vixl::IsUintN(16, imm))
|
||||
{
|
||||
armAsm->mov(al, rd, imm & 0xffff);
|
||||
armAsm->mov(vixl::aarch32::al, rd, imm & 0xffff);
|
||||
return;
|
||||
}
|
||||
|
||||
armAsm->mov(al, rd, imm & 0xffff);
|
||||
armAsm->movt(al, rd, imm >> 16);
|
||||
armAsm->mov(vixl::aarch32::al, rd, imm & 0xffff);
|
||||
armAsm->movt(vixl::aarch32::al, rd, imm >> 16);
|
||||
}
|
||||
|
||||
void armMoveAddressToReg(vixl::aarch32::Assembler* armAsm, const vixl::aarch32::Register& reg, const void* addr)
|
||||
|
@ -126,7 +126,7 @@ void armEmitJmp(vixl::aarch32::Assembler* armAsm, const void* ptr, bool force_in
|
|||
}
|
||||
else
|
||||
{
|
||||
Label label(displacement + armAsm->GetCursorOffset());
|
||||
vixl::aarch32::Label label(displacement + armAsm->GetCursorOffset());
|
||||
armAsm->b(&label);
|
||||
}
|
||||
}
|
||||
|
@ -152,7 +152,7 @@ void armEmitCall(vixl::aarch32::Assembler* armAsm, const void* ptr, bool force_i
|
|||
}
|
||||
else
|
||||
{
|
||||
Label label(displacement + armAsm->GetCursorOffset());
|
||||
vixl::aarch32::Label label(displacement + armAsm->GetCursorOffset());
|
||||
armAsm->bl(&label);
|
||||
}
|
||||
}
|
||||
|
@ -167,7 +167,7 @@ void armEmitCondBranch(vixl::aarch32::Assembler* armAsm, vixl::aarch32::Conditio
|
|||
}
|
||||
else
|
||||
{
|
||||
Label label(displacement + armAsm->GetCursorOffset());
|
||||
vixl::aarch32::Label label(displacement + armAsm->GetCursorOffset());
|
||||
armAsm->b(cond, &label);
|
||||
}
|
||||
}
|
||||
|
@ -175,14 +175,14 @@ void armEmitCondBranch(vixl::aarch32::Assembler* armAsm, vixl::aarch32::Conditio
|
|||
void armEmitFarLoad(vixl::aarch32::Assembler* armAsm, const vixl::aarch32::Register& reg, const void* addr)
|
||||
{
|
||||
armMoveAddressToReg(armAsm, reg, addr);
|
||||
armAsm->ldr(reg, MemOperand(reg));
|
||||
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)
|
||||
{
|
||||
armMoveAddressToReg(armAsm, tempreg, addr);
|
||||
armAsm->str(reg, MemOperand(tempreg));
|
||||
armAsm->str(reg, vixl::aarch32::MemOperand(tempreg));
|
||||
}
|
||||
|
||||
void CPU::CodeCache::DisassembleAndLogHostCode(const void* start, u32 size)
|
||||
|
@ -204,7 +204,6 @@ u32 CPU::CodeCache::GetHostInstructionCount(const void* start, u32 size)
|
|||
u32 CPU::CodeCache::EmitJump(void* code, const void* dst, bool flush_icache)
|
||||
{
|
||||
using namespace vixl::aarch32;
|
||||
using namespace CPU::Recompiler;
|
||||
|
||||
const s32 disp = armGetPCDisplacement(code, dst);
|
||||
DebugAssert(armIsPCDisplacementInImmediateRange(disp));
|
||||
|
@ -222,7 +221,7 @@ u32 CPU::CodeCache::EmitJump(void* code, const void* dst, bool flush_icache)
|
|||
return kA32InstructionSizeInBytes;
|
||||
}
|
||||
|
||||
u8* CPU::Recompiler::armGetJumpTrampoline(const void* target)
|
||||
u8* armGetJumpTrampoline(const void* target)
|
||||
{
|
||||
auto it = s_trampoline_targets.find(target);
|
||||
if (it != s_trampoline_targets.end())
|
||||
|
@ -239,7 +238,7 @@ u8* CPU::Recompiler::armGetJumpTrampoline(const void* target)
|
|||
}
|
||||
|
||||
u8* start = s_trampoline_start_ptr + offset;
|
||||
Assembler armAsm(start, TRAMPOLINE_AREA_SIZE - offset);
|
||||
vixl::aarch32::Assembler armAsm(start, TRAMPOLINE_AREA_SIZE - offset);
|
||||
armMoveAddressToReg(&armAsm, RSCRATCH, target);
|
||||
armAsm.bx(RSCRATCH);
|
||||
|
||||
|
@ -255,7 +254,6 @@ u8* CPU::Recompiler::armGetJumpTrampoline(const void* target)
|
|||
u32 CPU::CodeCache::EmitASMFunctions(void* code, u32 code_size)
|
||||
{
|
||||
using namespace vixl::aarch32;
|
||||
using namespace CPU::Recompiler;
|
||||
|
||||
Assembler actual_asm(static_cast<u8*>(code), code_size);
|
||||
Assembler* armAsm = &actual_asm;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -776,7 +776,7 @@ void GameList::Refresh(bool invalidate_cache, bool only_cache, ProgressCallback*
|
|||
if (!cache_file)
|
||||
ERROR_LOG("Failed to open game list cache: {}", error.GetDescription());
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
// Lock cache file for multi-instance on Linux. Implicitly done on Windows.
|
||||
std::optional<FileSystem::POSIXLock> cache_file_lock;
|
||||
if (cache_file)
|
||||
|
@ -1122,7 +1122,7 @@ GameList::PlayedTimeMap GameList::LoadPlayedTimeMap(const std::string& path)
|
|||
return ret;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
FileSystem::POSIXLock flock(fp.get());
|
||||
#endif
|
||||
|
||||
|
@ -1159,7 +1159,7 @@ GameList::PlayedTimeEntry GameList::UpdatePlayedTimeFile(const std::string& path
|
|||
return new_entry;
|
||||
}
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
FileSystem::POSIXLock flock(fp.get());
|
||||
#endif
|
||||
|
||||
|
@ -1726,7 +1726,7 @@ void GameList::ReloadMemcardTimestampCache()
|
|||
if (!fp)
|
||||
return;
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
FileSystem::POSIXLock lock(fp.get());
|
||||
#endif
|
||||
|
||||
|
@ -1856,7 +1856,7 @@ bool GameList::UpdateMemcardTimestampCache(const MemcardTimestampCacheEntry& ent
|
|||
if (!fp)
|
||||
return false;
|
||||
|
||||
#ifndef _WIN32
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
FileSystem::POSIXLock lock(fp.get());
|
||||
#endif
|
||||
|
||||
|
|
|
@ -1997,15 +1997,11 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
|
|||
|
||||
// Now we can apply the post chain.
|
||||
GPUTexture* post_output_texture = PostProcessing::InternalChain.GetOutputTexture();
|
||||
if (const GPUDevice::PresentResult pres = PostProcessing::InternalChain.Apply(
|
||||
display_texture, m_display_depth_buffer, post_output_texture,
|
||||
GSVector4i(0, 0, display_texture_view_width, display_texture_view_height), display_texture_view_width,
|
||||
display_texture_view_height, m_crtc_state.display_width, m_crtc_state.display_height);
|
||||
pres != GPUDevice::PresentResult::OK)
|
||||
{
|
||||
return pres;
|
||||
}
|
||||
else
|
||||
if (PostProcessing::InternalChain.Apply(display_texture, m_display_depth_buffer, post_output_texture,
|
||||
GSVector4i(0, 0, display_texture_view_width, display_texture_view_height),
|
||||
display_texture_view_width, display_texture_view_height,
|
||||
m_crtc_state.display_width,
|
||||
m_crtc_state.display_height) == GPUDevice::PresentResult::OK)
|
||||
{
|
||||
display_texture_view_x = 0;
|
||||
display_texture_view_y = 0;
|
||||
|
@ -2020,8 +2016,13 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
|
|||
const bool really_postfx = (postfx && PostProcessing::DisplayChain.IsActive() && g_gpu_device->HasMainSwapChain() &&
|
||||
hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 &&
|
||||
PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height));
|
||||
const GSVector4i real_draw_rect =
|
||||
g_gpu_device->UsesLowerLeftOrigin() ? GPUDevice::FlipToLowerLeft(draw_rect, target_height) : draw_rect;
|
||||
GSVector4i real_draw_rect = target ? draw_rect : g_gpu_device->GetMainSwapChain()->PreRotateClipRect(draw_rect);
|
||||
if (g_gpu_device->UsesLowerLeftOrigin())
|
||||
{
|
||||
real_draw_rect = GPUDevice::FlipToLowerLeft(
|
||||
real_draw_rect,
|
||||
(target || really_postfx) ? target_height : g_gpu_device->GetMainSwapChain()->GetPostRotatedHeight());
|
||||
}
|
||||
if (really_postfx)
|
||||
{
|
||||
g_gpu_device->ClearRenderTarget(PostProcessing::DisplayChain.GetInputTexture(), GPUDevice::DEFAULT_CLEAR_COLOR);
|
||||
|
@ -2106,16 +2107,22 @@ GPUDevice::PresentResult GPU::RenderDisplay(GPUTexture* target, const GSVector4i
|
|||
uniforms.src_size[2] = rcp_width;
|
||||
uniforms.src_size[3] = rcp_height;
|
||||
|
||||
if (g_settings.display_rotation != DisplayRotation::Normal)
|
||||
const WindowInfo::PreRotation surface_prerotation = (target || really_postfx) ?
|
||||
WindowInfo::PreRotation::Identity :
|
||||
g_gpu_device->GetMainSwapChain()->GetPreRotation();
|
||||
if (g_settings.display_rotation != DisplayRotation::Normal ||
|
||||
surface_prerotation != WindowInfo::PreRotation::Identity)
|
||||
{
|
||||
static constexpr const std::array<float, static_cast<size_t>(DisplayRotation::Count) - 1> rotation_radians = {{
|
||||
static constexpr const std::array<float, static_cast<size_t>(DisplayRotation::Count)> rotation_radians = {{
|
||||
0.0f, // Disabled
|
||||
static_cast<float>(std::numbers::pi * 1.5f), // Rotate90
|
||||
static_cast<float>(std::numbers::pi), // Rotate180
|
||||
static_cast<float>(std::numbers::pi / 2.0), // Rotate270
|
||||
}};
|
||||
|
||||
GSMatrix2x2::Rotation(rotation_radians[static_cast<size_t>(g_settings.display_rotation) - 1])
|
||||
.store(uniforms.rotation_matrix);
|
||||
const u32 rotation_idx = (static_cast<u32>(g_settings.display_rotation) + static_cast<u32>(surface_prerotation)) %
|
||||
static_cast<u32>(rotation_radians.size());
|
||||
GSMatrix2x2::Rotation(rotation_radians[rotation_idx]).store(uniforms.rotation_matrix);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -2340,8 +2347,6 @@ bool GPU::DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y,
|
|||
g_gpu_device->PushUniformBuffer(uniforms, sizeof(uniforms));
|
||||
g_gpu_device->SetViewportAndScissor(0, 0, width, height);
|
||||
g_gpu_device->Draw(3, 0);
|
||||
|
||||
GL_POP();
|
||||
}
|
||||
|
||||
dst->MakeReadyForSampling();
|
||||
|
|
|
@ -4080,6 +4080,7 @@ void GPU_HW::DownsampleFramebufferAdaptive(GPUTexture* source, u32 left, u32 top
|
|||
if (!m_downsample_texture || !level_texture || !weight_texture)
|
||||
{
|
||||
ERROR_LOG("Failed to create {}x{} RTs for adaptive downsampling", width, height);
|
||||
GL_POP();
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -781,6 +781,8 @@ void GPUTextureCache::SetHashCacheTextureFormat()
|
|||
// Prefer 16-bit texture formats where possible.
|
||||
if (g_gpu_device->SupportsTextureFormat(GPUTexture::Format::RGB5A1))
|
||||
s_state.hash_cache_texture_format = GPUTexture::Format::RGB5A1;
|
||||
else if (g_gpu_device->SupportsTextureFormat(GPUTexture::Format::A1BGR5))
|
||||
s_state.hash_cache_texture_format = GPUTexture::Format::A1BGR5;
|
||||
else
|
||||
s_state.hash_cache_texture_format = GPUTexture::Format::RGBA8;
|
||||
|
||||
|
@ -1080,70 +1082,6 @@ ALWAYS_INLINE_RELEASE static const u16* VRAMPalettePointer(GPUTexturePaletteReg
|
|||
return &g_vram[VRAM_WIDTH * palette.GetYBase() + palette.GetXBase()];
|
||||
}
|
||||
|
||||
template<GPUTexture::Format format>
|
||||
ALWAYS_INLINE static void WriteDecodedTexel(u8*& dest, u16 c16)
|
||||
{
|
||||
if constexpr (format == GPUTexture::Format::RGBA8)
|
||||
{
|
||||
const u32 c32 = VRAMRGBA5551ToRGBA8888(c16);
|
||||
std::memcpy(std::assume_aligned<sizeof(c32)>(dest), &c32, sizeof(c32));
|
||||
dest += sizeof(c32);
|
||||
}
|
||||
else if constexpr (format == GPUTexture::Format::RGB5A1)
|
||||
{
|
||||
const u16 repacked = (c16 & 0x83E0) | ((c16 >> 10) & 0x1F) | ((c16 & 0x1F) << 10);
|
||||
std::memcpy(std::assume_aligned<sizeof(repacked)>(dest), &repacked, sizeof(repacked));
|
||||
dest += sizeof(repacked);
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef CPU_ARCH_SIMD
|
||||
|
||||
ALWAYS_INLINE static GSVector4i VRAM5BitTo8Bit(GSVector4i val)
|
||||
{
|
||||
return val.mul32l(GSVector4i::cxpr(527)).add32(GSVector4i::cxpr(23)).srl32<6>();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static GSVector4i VRAMRGB5A1ToRGBA8888(GSVector4i val)
|
||||
{
|
||||
static constexpr GSVector4i cmask = GSVector4i::cxpr(0x1F);
|
||||
|
||||
const GSVector4i r = VRAM5BitTo8Bit(val & cmask);
|
||||
const GSVector4i g = VRAM5BitTo8Bit((val.srl32<5>() & cmask));
|
||||
const GSVector4i b = VRAM5BitTo8Bit((val.srl32<10>() & cmask));
|
||||
const GSVector4i a = val.srl32<15>().sll32<31>().sra32<7>();
|
||||
|
||||
return r | g.sll32<8>() | b.sll32<16>() | b.sll32<24>() | a;
|
||||
}
|
||||
|
||||
template<GPUTexture::Format format>
|
||||
ALWAYS_INLINE static void WriteDecodedTexels(u8*& dest, GSVector4i c16)
|
||||
{
|
||||
if constexpr (format == GPUTexture::Format::RGBA8)
|
||||
{
|
||||
const GSVector4i low = VRAMRGB5A1ToRGBA8888(c16.upl16());
|
||||
const GSVector4i high = VRAMRGB5A1ToRGBA8888(c16.uph16());
|
||||
|
||||
GSVector4i::store<false>(dest, low);
|
||||
dest += sizeof(GSVector4i);
|
||||
|
||||
GSVector4i::store<false>(dest, high);
|
||||
dest += sizeof(GSVector4i);
|
||||
}
|
||||
else if constexpr (format == GPUTexture::Format::RGB5A1)
|
||||
{
|
||||
static constexpr GSVector4i cmask = GSVector4i::cxpr16(0x1F);
|
||||
|
||||
const GSVector4i repacked =
|
||||
(c16 & GSVector4i::cxpr16(static_cast<s16>(0x83E0))) | (c16.srl16<10>() & cmask) | (c16 & cmask).sll16<10>();
|
||||
|
||||
GSVector4i::store<false>(dest, repacked);
|
||||
dest += sizeof(GSVector4i);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template<GPUTexture::Format format>
|
||||
void GPUTextureCache::DecodeTexture4(const u16* page, const u16* palette, u32 width, u32 height, u8* dest,
|
||||
u32 dest_stride)
|
||||
|
@ -1175,17 +1113,17 @@ void GPUTextureCache::DecodeTexture4(const u16* page, const u16* palette, u32 wi
|
|||
c16[5] = palette[(pp >> 4) & 0x0F];
|
||||
c16[6] = palette[(pp >> 8) & 0x0F];
|
||||
c16[7] = palette[pp >> 12];
|
||||
WriteDecodedTexels<format>(dest_ptr, GSVector4i::load<true>(c16));
|
||||
ConvertVRAMPixels<format>(dest_ptr, GSVector4i::load<true>(c16));
|
||||
}
|
||||
#endif
|
||||
|
||||
for (; x < vram_width; x++)
|
||||
{
|
||||
const u32 pp = *(page_ptr++);
|
||||
WriteDecodedTexel<format>(dest_ptr, palette[pp & 0x0F]);
|
||||
WriteDecodedTexel<format>(dest_ptr, palette[(pp >> 4) & 0x0F]);
|
||||
WriteDecodedTexel<format>(dest_ptr, palette[(pp >> 8) & 0x0F]);
|
||||
WriteDecodedTexel<format>(dest_ptr, palette[pp >> 12]);
|
||||
ConvertVRAMPixel<format>(dest_ptr, palette[pp & 0x0F]);
|
||||
ConvertVRAMPixel<format>(dest_ptr, palette[(pp >> 4) & 0x0F]);
|
||||
ConvertVRAMPixel<format>(dest_ptr, palette[(pp >> 8) & 0x0F]);
|
||||
ConvertVRAMPixel<format>(dest_ptr, palette[pp >> 12]);
|
||||
}
|
||||
|
||||
page += VRAM_WIDTH;
|
||||
|
@ -1206,7 +1144,7 @@ void GPUTextureCache::DecodeTexture4(const u16* page, const u16* palette, u32 wi
|
|||
if (offs == 0)
|
||||
texel = *(page_ptr++);
|
||||
|
||||
WriteDecodedTexel<format>(dest_ptr, palette[texel & 0x0F]);
|
||||
ConvertVRAMPixel<format>(dest_ptr, palette[texel & 0x0F]);
|
||||
texel >>= 4;
|
||||
|
||||
offs = (offs + 1) % 4;
|
||||
|
@ -1251,15 +1189,15 @@ void GPUTextureCache::DecodeTexture8(const u16* page, const u16* palette, u32 wi
|
|||
pp = *(page_ptr++);
|
||||
c16[6] = palette[pp & 0xFF];
|
||||
c16[7] = palette[(pp >> 8) & 0xFF];
|
||||
WriteDecodedTexels<format>(dest_ptr, GSVector4i::load<true>(c16));
|
||||
ConvertVRAMPixels<format>(dest_ptr, GSVector4i::load<true>(c16));
|
||||
}
|
||||
#endif
|
||||
|
||||
for (; x < vram_width; x++)
|
||||
{
|
||||
const u32 pp = *(page_ptr++);
|
||||
WriteDecodedTexel<format>(dest_ptr, palette[pp & 0xFF]);
|
||||
WriteDecodedTexel<format>(dest_ptr, palette[pp >> 8]);
|
||||
ConvertVRAMPixel<format>(dest_ptr, palette[pp & 0xFF]);
|
||||
ConvertVRAMPixel<format>(dest_ptr, palette[pp >> 8]);
|
||||
}
|
||||
|
||||
page += VRAM_WIDTH;
|
||||
|
@ -1280,7 +1218,7 @@ void GPUTextureCache::DecodeTexture8(const u16* page, const u16* palette, u32 wi
|
|||
if (offs == 0)
|
||||
texel = *(page_ptr++);
|
||||
|
||||
WriteDecodedTexel<format>(dest_ptr, palette[texel & 0xFF]);
|
||||
ConvertVRAMPixel<format>(dest_ptr, palette[texel & 0xFF]);
|
||||
texel >>= 8;
|
||||
|
||||
offs ^= 1;
|
||||
|
@ -1307,13 +1245,13 @@ void GPUTextureCache::DecodeTexture16(const u16* page, u32 width, u32 height, u8
|
|||
#ifdef CPU_ARCH_SIMD
|
||||
for (; x < aligned_width; x += pixels_per_vec)
|
||||
{
|
||||
WriteDecodedTexels<format>(dest_ptr, GSVector4i::load<false>(page_ptr));
|
||||
ConvertVRAMPixels<format>(dest_ptr, GSVector4i::load<false>(page_ptr));
|
||||
page_ptr += pixels_per_vec;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (; x < width; x++)
|
||||
WriteDecodedTexel<format>(dest_ptr, *(page_ptr++));
|
||||
ConvertVRAMPixel<format>(dest_ptr, *(page_ptr++));
|
||||
|
||||
page += VRAM_WIDTH;
|
||||
dest += dest_stride;
|
||||
|
@ -1359,6 +1297,24 @@ void GPUTextureCache::DecodeTexture(GPUTextureMode mode, const u16* page_ptr, co
|
|||
DefaultCaseIsUnreachable()
|
||||
}
|
||||
}
|
||||
else if (dest_format == GPUTexture::Format::A1BGR5)
|
||||
{
|
||||
switch (mode)
|
||||
{
|
||||
case GPUTextureMode::Palette4Bit:
|
||||
DecodeTexture4<GPUTexture::Format::A1BGR5>(page_ptr, palette, width, height, dest, dest_stride);
|
||||
break;
|
||||
case GPUTextureMode::Palette8Bit:
|
||||
DecodeTexture8<GPUTexture::Format::A1BGR5>(page_ptr, palette, width, height, dest, dest_stride);
|
||||
break;
|
||||
case GPUTextureMode::Direct16Bit:
|
||||
case GPUTextureMode::Reserved_Direct16Bit:
|
||||
DecodeTexture16<GPUTexture::Format::A1BGR5>(page_ptr, width, height, dest, dest_stride);
|
||||
break;
|
||||
|
||||
DefaultCaseIsUnreachable()
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
Panic("Unsupported texture format.");
|
||||
|
|
|
@ -41,10 +41,8 @@ bool GPU_SW::Initialize(Error* error)
|
|||
if (!GPU::Initialize(error) || !m_backend.Initialize(g_settings.gpu_use_thread))
|
||||
return false;
|
||||
|
||||
static constexpr const std::array formats_for_16bit = {GPUTexture::Format::RGB565, GPUTexture::Format::RGB5A1,
|
||||
GPUTexture::Format::RGBA8, GPUTexture::Format::BGRA8};
|
||||
static constexpr const std::array formats_for_24bit = {GPUTexture::Format::RGBA8, GPUTexture::Format::BGRA8,
|
||||
GPUTexture::Format::RGB565, GPUTexture::Format::RGB5A1};
|
||||
static constexpr const std::array formats_for_16bit = {GPUTexture::Format::RGB5A1, GPUTexture::Format::A1BGR5,
|
||||
GPUTexture::Format::RGB565, GPUTexture::Format::RGBA8};
|
||||
for (const GPUTexture::Format format : formats_for_16bit)
|
||||
{
|
||||
if (g_gpu_device->SupportsTextureFormat(format))
|
||||
|
@ -53,15 +51,10 @@ bool GPU_SW::Initialize(Error* error)
|
|||
break;
|
||||
}
|
||||
}
|
||||
for (const GPUTexture::Format format : formats_for_24bit)
|
||||
{
|
||||
if (g_gpu_device->SupportsTextureFormat(format))
|
||||
{
|
||||
m_24bit_display_format = format;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// RGBA8 will always be supported, hence we'll find one.
|
||||
INFO_LOG("Using {} format for 16-bit display", GPUTexture::GetFormatName(m_16bit_display_format));
|
||||
Assert(m_16bit_display_format != GPUTexture::Format::Unknown);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -108,129 +101,43 @@ GPUTexture* GPU_SW::GetDisplayTexture(u32 width, u32 height, GPUTexture::Format
|
|||
return m_upload_texture.get();
|
||||
}
|
||||
|
||||
template<GPUTexture::Format out_format, typename out_type>
|
||||
static void CopyOutRow16(const u16* src_ptr, out_type* dst_ptr, u32 width);
|
||||
|
||||
template<GPUTexture::Format out_format, typename out_type>
|
||||
static out_type VRAM16ToOutput(u16 value);
|
||||
|
||||
template<>
|
||||
ALWAYS_INLINE u16 VRAM16ToOutput<GPUTexture::Format::RGB5A1, u16>(u16 value)
|
||||
{
|
||||
return (value & 0x3E0) | ((value >> 10) & 0x1F) | ((value & 0x1F) << 10);
|
||||
}
|
||||
|
||||
template<>
|
||||
ALWAYS_INLINE u16 VRAM16ToOutput<GPUTexture::Format::RGB565, u16>(u16 value)
|
||||
{
|
||||
return ((value & 0x3E0) << 1) | ((value & 0x20) << 1) | ((value >> 10) & 0x1F) | ((value & 0x1F) << 11);
|
||||
}
|
||||
|
||||
template<>
|
||||
ALWAYS_INLINE u32 VRAM16ToOutput<GPUTexture::Format::RGBA8, u32>(u16 value)
|
||||
{
|
||||
const u32 value32 = ZeroExtend32(value);
|
||||
const u32 r = (value32 & 31u) << 3;
|
||||
const u32 g = ((value32 >> 5) & 31u) << 3;
|
||||
const u32 b = ((value32 >> 10) & 31u) << 3;
|
||||
const u32 a = ((value >> 15) != 0) ? 255 : 0;
|
||||
return ZeroExtend32(r) | (ZeroExtend32(g) << 8) | (ZeroExtend32(b) << 16) | (ZeroExtend32(a) << 24);
|
||||
}
|
||||
|
||||
template<>
|
||||
ALWAYS_INLINE u32 VRAM16ToOutput<GPUTexture::Format::BGRA8, u32>(u16 value)
|
||||
{
|
||||
const u32 value32 = ZeroExtend32(value);
|
||||
const u32 r = (value32 & 31u) << 3;
|
||||
const u32 g = ((value32 >> 5) & 31u) << 3;
|
||||
const u32 b = ((value32 >> 10) & 31u) << 3;
|
||||
return ZeroExtend32(b) | (ZeroExtend32(g) << 8) | (ZeroExtend32(r) << 16) | (0xFF000000u);
|
||||
}
|
||||
|
||||
template<>
|
||||
ALWAYS_INLINE void CopyOutRow16<GPUTexture::Format::RGB5A1, u16>(const u16* src_ptr, u16* dst_ptr, u32 width)
|
||||
{
|
||||
u32 col = 0;
|
||||
|
||||
const u32 aligned_width = Common::AlignDownPow2(width, 8);
|
||||
for (; col < aligned_width; col += 8)
|
||||
{
|
||||
constexpr GSVector4i single_mask = GSVector4i::cxpr16(0x1F);
|
||||
GSVector4i value = GSVector4i::load<false>(src_ptr);
|
||||
src_ptr += 8;
|
||||
GSVector4i a = value & GSVector4i::cxpr16(0x3E0);
|
||||
GSVector4i b = value.srl16<10>() & single_mask;
|
||||
GSVector4i c = (value & single_mask).sll16<10>();
|
||||
value = (a | b) | c;
|
||||
GSVector4i::store<false>(dst_ptr, value);
|
||||
dst_ptr += 8;
|
||||
}
|
||||
|
||||
for (; col < width; col++)
|
||||
*(dst_ptr++) = VRAM16ToOutput<GPUTexture::Format::RGB5A1, u16>(*(src_ptr++));
|
||||
}
|
||||
|
||||
template<>
|
||||
ALWAYS_INLINE void CopyOutRow16<GPUTexture::Format::RGB565, u16>(const u16* src_ptr, u16* dst_ptr, u32 width)
|
||||
{
|
||||
u32 col = 0;
|
||||
|
||||
const u32 aligned_width = Common::AlignDownPow2(width, 8);
|
||||
for (; col < aligned_width; col += 8)
|
||||
{
|
||||
constexpr GSVector4i single_mask = GSVector4i::cxpr16(0x1F);
|
||||
GSVector4i value = GSVector4i::load<false>(src_ptr);
|
||||
src_ptr += 8;
|
||||
GSVector4i a = (value & GSVector4i::cxpr16(0x3E0)).sll16<1>(); // (value & 0x3E0) << 1
|
||||
GSVector4i b = (value & GSVector4i::cxpr16(0x20)).sll16<1>(); // (value & 0x20) << 1
|
||||
GSVector4i c = (value.srl16<10>() & single_mask); // ((value >> 10) & 0x1F)
|
||||
GSVector4i d = (value & single_mask).sll16<11>(); // ((value & 0x1F) << 11)
|
||||
value = (((a | b) | c) | d);
|
||||
GSVector4i::store<false>(dst_ptr, value);
|
||||
dst_ptr += 8;
|
||||
}
|
||||
|
||||
for (; col < width; col++)
|
||||
*(dst_ptr++) = VRAM16ToOutput<GPUTexture::Format::RGB565, u16>(*(src_ptr++));
|
||||
}
|
||||
|
||||
template<>
|
||||
ALWAYS_INLINE void CopyOutRow16<GPUTexture::Format::RGBA8, u32>(const u16* src_ptr, u32* dst_ptr, u32 width)
|
||||
{
|
||||
for (u32 col = 0; col < width; col++)
|
||||
*(dst_ptr++) = VRAM16ToOutput<GPUTexture::Format::RGBA8, u32>(*(src_ptr++));
|
||||
}
|
||||
|
||||
template<>
|
||||
ALWAYS_INLINE void CopyOutRow16<GPUTexture::Format::BGRA8, u32>(const u16* src_ptr, u32* dst_ptr, u32 width)
|
||||
{
|
||||
for (u32 col = 0; col < width; col++)
|
||||
*(dst_ptr++) = VRAM16ToOutput<GPUTexture::Format::BGRA8, u32>(*(src_ptr++));
|
||||
}
|
||||
|
||||
template<GPUTexture::Format display_format>
|
||||
ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 line_skip)
|
||||
{
|
||||
using OutputPixelType =
|
||||
std::conditional_t<display_format == GPUTexture::Format::RGBA8 || display_format == GPUTexture::Format::BGRA8, u32,
|
||||
u16>;
|
||||
|
||||
GPUTexture* texture = GetDisplayTexture(width, height, display_format);
|
||||
if (!texture) [[unlikely]]
|
||||
return false;
|
||||
|
||||
u32 dst_stride = width * sizeof(OutputPixelType);
|
||||
u32 dst_stride = Common::AlignUpPow2(width * texture->GetPixelSize(), 4);
|
||||
u8* dst_ptr = m_upload_buffer.data();
|
||||
const bool mapped = texture->Map(reinterpret_cast<void**>(&dst_ptr), &dst_stride, 0, 0, width, height);
|
||||
|
||||
// Fast path when not wrapping around.
|
||||
if ((src_x + width) <= VRAM_WIDTH && (src_y + height) <= VRAM_HEIGHT)
|
||||
{
|
||||
[[maybe_unused]] constexpr u32 pixels_per_vec = 8;
|
||||
[[maybe_unused]] const u32 aligned_width = Common::AlignDownPow2(width, pixels_per_vec);
|
||||
|
||||
const u16* src_ptr = &g_vram[src_y * VRAM_WIDTH + src_x];
|
||||
const u32 src_step = VRAM_WIDTH << line_skip;
|
||||
|
||||
for (u32 row = 0; row < height; row++)
|
||||
{
|
||||
CopyOutRow16<display_format>(src_ptr, reinterpret_cast<OutputPixelType*>(dst_ptr), width);
|
||||
const u16* src_row_ptr = src_ptr;
|
||||
u8* dst_row_ptr = dst_ptr;
|
||||
u32 x = 0;
|
||||
|
||||
#ifdef CPU_ARCH_SIMD
|
||||
for (; x < aligned_width; x += pixels_per_vec)
|
||||
{
|
||||
ConvertVRAMPixels<display_format>(dst_row_ptr, GSVector4i::load<false>(src_row_ptr));
|
||||
src_row_ptr += pixels_per_vec;
|
||||
}
|
||||
#endif
|
||||
|
||||
for (; x < width; x++)
|
||||
ConvertVRAMPixel<display_format>(dst_row_ptr, *(src_row_ptr++));
|
||||
|
||||
src_ptr += src_step;
|
||||
dst_ptr += dst_stride;
|
||||
}
|
||||
|
@ -242,10 +149,10 @@ ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut15Bit(u32 src_x, u32 src_y, u32 width,
|
|||
for (u32 row = 0; row < height; row++)
|
||||
{
|
||||
const u16* src_row_ptr = &g_vram[(src_y % VRAM_HEIGHT) * VRAM_WIDTH];
|
||||
OutputPixelType* dst_row_ptr = reinterpret_cast<OutputPixelType*>(dst_ptr);
|
||||
u8* dst_row_ptr = dst_ptr;
|
||||
|
||||
for (u32 col = src_x; col < end_x; col++)
|
||||
*(dst_row_ptr++) = VRAM16ToOutput<display_format, OutputPixelType>(src_row_ptr[col % VRAM_WIDTH]);
|
||||
ConvertVRAMPixel<display_format>(dst_row_ptr, src_row_ptr[col % VRAM_WIDTH]);
|
||||
|
||||
src_y += y_step;
|
||||
dst_ptr += dst_stride;
|
||||
|
@ -260,18 +167,13 @@ ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut15Bit(u32 src_x, u32 src_y, u32 width,
|
|||
return true;
|
||||
}
|
||||
|
||||
template<GPUTexture::Format display_format>
|
||||
ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip)
|
||||
{
|
||||
using OutputPixelType =
|
||||
std::conditional_t<display_format == GPUTexture::Format::RGBA8 || display_format == GPUTexture::Format::BGRA8, u32,
|
||||
u16>;
|
||||
|
||||
GPUTexture* texture = GetDisplayTexture(width, height, display_format);
|
||||
GPUTexture* texture = GetDisplayTexture(width, height, FORMAT_FOR_24BIT);
|
||||
if (!texture) [[unlikely]]
|
||||
return false;
|
||||
|
||||
u32 dst_stride = Common::AlignUpPow2<u32>(width * sizeof(OutputPixelType), 4);
|
||||
u32 dst_stride = width * sizeof(u32);
|
||||
u8* dst_ptr = m_upload_buffer.data();
|
||||
const bool mapped = texture->Map(reinterpret_cast<void**>(&dst_ptr), &dst_stride, 0, 0, width, height);
|
||||
|
||||
|
@ -281,52 +183,14 @@ ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x
|
|||
const u32 src_stride = (VRAM_WIDTH << line_skip) * sizeof(u16);
|
||||
for (u32 row = 0; row < height; row++)
|
||||
{
|
||||
if constexpr (display_format == GPUTexture::Format::RGBA8)
|
||||
const u8* src_row_ptr = src_ptr;
|
||||
u8* dst_row_ptr = reinterpret_cast<u8*>(dst_ptr);
|
||||
for (u32 col = 0; col < width; col++)
|
||||
{
|
||||
const u8* src_row_ptr = src_ptr;
|
||||
u8* dst_row_ptr = reinterpret_cast<u8*>(dst_ptr);
|
||||
for (u32 col = 0; col < width; col++)
|
||||
{
|
||||
*(dst_row_ptr++) = *(src_row_ptr++);
|
||||
*(dst_row_ptr++) = *(src_row_ptr++);
|
||||
*(dst_row_ptr++) = *(src_row_ptr++);
|
||||
*(dst_row_ptr++) = 0xFF;
|
||||
}
|
||||
}
|
||||
else if constexpr (display_format == GPUTexture::Format::BGRA8)
|
||||
{
|
||||
const u8* src_row_ptr = src_ptr;
|
||||
u8* dst_row_ptr = reinterpret_cast<u8*>(dst_ptr);
|
||||
for (u32 col = 0; col < width; col++)
|
||||
{
|
||||
*(dst_row_ptr++) = src_row_ptr[2];
|
||||
*(dst_row_ptr++) = src_row_ptr[1];
|
||||
*(dst_row_ptr++) = src_row_ptr[0];
|
||||
*(dst_row_ptr++) = 0xFF;
|
||||
src_row_ptr += 3;
|
||||
}
|
||||
}
|
||||
else if constexpr (display_format == GPUTexture::Format::RGB565)
|
||||
{
|
||||
const u8* src_row_ptr = src_ptr;
|
||||
u16* dst_row_ptr = reinterpret_cast<u16*>(dst_ptr);
|
||||
for (u32 col = 0; col < width; col++)
|
||||
{
|
||||
*(dst_row_ptr++) = ((static_cast<u16>(src_row_ptr[0]) >> 3) << 11) |
|
||||
((static_cast<u16>(src_row_ptr[1]) >> 2) << 5) | (static_cast<u16>(src_row_ptr[2]) >> 3);
|
||||
src_row_ptr += 3;
|
||||
}
|
||||
}
|
||||
else if constexpr (display_format == GPUTexture::Format::RGB5A1)
|
||||
{
|
||||
const u8* src_row_ptr = src_ptr;
|
||||
u16* dst_row_ptr = reinterpret_cast<u16*>(dst_ptr);
|
||||
for (u32 col = 0; col < width; col++)
|
||||
{
|
||||
*(dst_row_ptr++) = ((static_cast<u16>(src_row_ptr[0]) >> 3) << 10) |
|
||||
((static_cast<u16>(src_row_ptr[1]) >> 3) << 5) | (static_cast<u16>(src_row_ptr[2]) >> 3);
|
||||
src_row_ptr += 3;
|
||||
}
|
||||
*(dst_row_ptr++) = *(src_row_ptr++);
|
||||
*(dst_row_ptr++) = *(src_row_ptr++);
|
||||
*(dst_row_ptr++) = *(src_row_ptr++);
|
||||
*(dst_row_ptr++) = 0xFF;
|
||||
}
|
||||
|
||||
src_ptr += src_stride;
|
||||
|
@ -340,7 +204,7 @@ ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x
|
|||
for (u32 row = 0; row < height; row++)
|
||||
{
|
||||
const u16* src_row_ptr = &g_vram[(src_y % VRAM_HEIGHT) * VRAM_WIDTH];
|
||||
OutputPixelType* dst_row_ptr = reinterpret_cast<OutputPixelType*>(dst_ptr);
|
||||
u32* dst_row_ptr = reinterpret_cast<u32*>(dst_ptr);
|
||||
|
||||
for (u32 col = 0; col < width; col++)
|
||||
{
|
||||
|
@ -350,22 +214,7 @@ ALWAYS_INLINE_RELEASE bool GPU_SW::CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x
|
|||
const u8 shift = static_cast<u8>(col & 1u) * 8;
|
||||
const u32 rgb = (((ZeroExtend32(s1) << 16) | ZeroExtend32(s0)) >> shift);
|
||||
|
||||
if constexpr (display_format == GPUTexture::Format::RGBA8)
|
||||
{
|
||||
*(dst_row_ptr++) = rgb | 0xFF000000u;
|
||||
}
|
||||
else if constexpr (display_format == GPUTexture::Format::BGRA8)
|
||||
{
|
||||
*(dst_row_ptr++) = (rgb & 0x00FF00) | ((rgb & 0xFF) << 16) | ((rgb >> 16) & 0xFF) | 0xFF000000u;
|
||||
}
|
||||
else if constexpr (display_format == GPUTexture::Format::RGB565)
|
||||
{
|
||||
*(dst_row_ptr++) = ((rgb >> 3) & 0x1F) | (((rgb >> 10) << 5) & 0x7E0) | (((rgb >> 19) << 11) & 0x3E0000);
|
||||
}
|
||||
else if constexpr (display_format == GPUTexture::Format::RGB5A1)
|
||||
{
|
||||
*(dst_row_ptr++) = ((rgb >> 3) & 0x1F) | (((rgb >> 11) << 5) & 0x3E0) | (((rgb >> 19) << 10) & 0x1F0000);
|
||||
}
|
||||
*(dst_row_ptr++) = rgb | 0xFF000000u;
|
||||
}
|
||||
|
||||
src_y += y_step;
|
||||
|
@ -392,6 +241,9 @@ bool GPU_SW::CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u3
|
|||
case GPUTexture::Format::RGB5A1:
|
||||
return CopyOut15Bit<GPUTexture::Format::RGB5A1>(src_x, src_y, width, height, line_skip);
|
||||
|
||||
case GPUTexture::Format::A1BGR5:
|
||||
return CopyOut15Bit<GPUTexture::Format::A1BGR5>(src_x, src_y, width, height, line_skip);
|
||||
|
||||
case GPUTexture::Format::RGB565:
|
||||
return CopyOut15Bit<GPUTexture::Format::RGB565>(src_x, src_y, width, height, line_skip);
|
||||
|
||||
|
@ -407,23 +259,7 @@ bool GPU_SW::CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u3
|
|||
}
|
||||
else
|
||||
{
|
||||
switch (m_24bit_display_format)
|
||||
{
|
||||
case GPUTexture::Format::RGB5A1:
|
||||
return CopyOut24Bit<GPUTexture::Format::RGB5A1>(src_x, src_y, skip_x, width, height, line_skip);
|
||||
|
||||
case GPUTexture::Format::RGB565:
|
||||
return CopyOut24Bit<GPUTexture::Format::RGB565>(src_x, src_y, skip_x, width, height, line_skip);
|
||||
|
||||
case GPUTexture::Format::RGBA8:
|
||||
return CopyOut24Bit<GPUTexture::Format::RGBA8>(src_x, src_y, skip_x, width, height, line_skip);
|
||||
|
||||
case GPUTexture::Format::BGRA8:
|
||||
return CopyOut24Bit<GPUTexture::Format::BGRA8>(src_x, src_y, skip_x, width, height, line_skip);
|
||||
|
||||
default:
|
||||
UnreachableCode();
|
||||
}
|
||||
return CopyOut24Bit(src_x, src_y, skip_x, width, height, line_skip);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -45,7 +45,6 @@ protected:
|
|||
template<GPUTexture::Format display_format>
|
||||
bool CopyOut15Bit(u32 src_x, u32 src_y, u32 width, u32 height, u32 line_skip);
|
||||
|
||||
template<GPUTexture::Format display_format>
|
||||
bool CopyOut24Bit(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip);
|
||||
|
||||
bool CopyOut(u32 src_x, u32 src_y, u32 skip_x, u32 width, u32 height, u32 line_skip, bool is_24bit);
|
||||
|
@ -57,11 +56,13 @@ protected:
|
|||
void FillBackendCommandParameters(GPUBackendCommand* cmd) const;
|
||||
void FillDrawCommand(GPUBackendDrawCommand* cmd, GPURenderCommand rc) const;
|
||||
|
||||
private:
|
||||
static constexpr GPUTexture::Format FORMAT_FOR_24BIT = GPUTexture::Format::RGBA8; // RGBA8 always supported.
|
||||
|
||||
GPUTexture* GetDisplayTexture(u32 width, u32 height, GPUTexture::Format format);
|
||||
|
||||
FixedHeapArray<u8, GPU_MAX_DISPLAY_WIDTH * GPU_MAX_DISPLAY_HEIGHT * sizeof(u32)> m_upload_buffer;
|
||||
GPUTexture::Format m_16bit_display_format = GPUTexture::Format::RGB565;
|
||||
GPUTexture::Format m_24bit_display_format = GPUTexture::Format::RGBA8;
|
||||
GPUTexture::Format m_16bit_display_format = GPUTexture::Format::Unknown;
|
||||
std::unique_ptr<GPUTexture> m_upload_texture;
|
||||
|
||||
GPU_SW_Backend m_backend;
|
||||
|
|
|
@ -3,8 +3,10 @@
|
|||
|
||||
#ifdef __INTELLISENSE__
|
||||
|
||||
#include "common/gsvector.h"
|
||||
#include "gpu.h"
|
||||
|
||||
#include "common/gsvector.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#define USE_VECTOR 1
|
||||
|
|
|
@ -5,6 +5,8 @@
|
|||
|
||||
#include "types.h"
|
||||
|
||||
#include "util/gpu_texture.h"
|
||||
|
||||
#include "common/bitfield.h"
|
||||
#include "common/bitutils.h"
|
||||
#include "common/gsvector.h"
|
||||
|
@ -249,6 +251,101 @@ ALWAYS_INLINE static constexpr u16 VRAMRGBA8888ToRGBA5551(u32 color)
|
|||
return Truncate16(r | (g << 5) | (b << 10) | (a << 15));
|
||||
}
|
||||
|
||||
#ifdef CPU_ARCH_SIMD
|
||||
|
||||
ALWAYS_INLINE static GSVector4i VRAM5BitTo8Bit(GSVector4i val)
|
||||
{
|
||||
return val.mul32l(GSVector4i::cxpr(527)).add32(GSVector4i::cxpr(23)).srl32<6>();
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static GSVector4i VRAMRGB5A1ToRGBA8888(GSVector4i val)
|
||||
{
|
||||
static constexpr GSVector4i cmask = GSVector4i::cxpr(0x1F);
|
||||
|
||||
const GSVector4i r = VRAM5BitTo8Bit(val & cmask);
|
||||
const GSVector4i g = VRAM5BitTo8Bit((val.srl32<5>() & cmask));
|
||||
const GSVector4i b = VRAM5BitTo8Bit((val.srl32<10>() & cmask));
|
||||
const GSVector4i a = val.srl32<15>().sll32<31>().sra32<7>();
|
||||
|
||||
return r | g.sll32<8>() | b.sll32<16>() | a;
|
||||
}
|
||||
|
||||
template<GPUTexture::Format format>
|
||||
ALWAYS_INLINE static void ConvertVRAMPixels(u8*& dest, GSVector4i c16)
|
||||
{
|
||||
if constexpr (format == GPUTexture::Format::RGBA8)
|
||||
{
|
||||
const GSVector4i low = VRAMRGB5A1ToRGBA8888(c16.upl16());
|
||||
const GSVector4i high = VRAMRGB5A1ToRGBA8888(c16.uph16());
|
||||
|
||||
GSVector4i::store<false>(dest, low);
|
||||
dest += sizeof(GSVector4i);
|
||||
|
||||
GSVector4i::store<false>(dest, high);
|
||||
dest += sizeof(GSVector4i);
|
||||
}
|
||||
else if constexpr (format == GPUTexture::Format::RGB5A1)
|
||||
{
|
||||
static constexpr GSVector4i cmask = GSVector4i::cxpr16(0x1F);
|
||||
|
||||
const GSVector4i repacked =
|
||||
(c16 & GSVector4i::cxpr16(static_cast<s16>(0x83E0))) | (c16.srl16<10>() & cmask) | (c16 & cmask).sll16<10>();
|
||||
|
||||
GSVector4i::store<false>(dest, repacked);
|
||||
dest += sizeof(GSVector4i);
|
||||
}
|
||||
else if constexpr (format == GPUTexture::Format::A1BGR5)
|
||||
{
|
||||
const GSVector4i repacked = (c16 & GSVector4i::cxpr16(static_cast<s16>(0x3E0))).sll16<1>() |
|
||||
(c16.srl16<9>() & GSVector4i::cxpr16(0x3E)) |
|
||||
(c16 & GSVector4i::cxpr16(0x1F)).sll16<11>() | c16.srl16<15>();
|
||||
|
||||
GSVector4i::store<false>(dest, repacked);
|
||||
dest += sizeof(GSVector4i);
|
||||
}
|
||||
else if constexpr (format == GPUTexture::Format::RGB565)
|
||||
{
|
||||
constexpr GSVector4i single_mask = GSVector4i::cxpr16(0x1F);
|
||||
const GSVector4i a = (c16 & GSVector4i::cxpr16(0x3E0)).sll16<1>(); // (value & 0x3E0) << 1
|
||||
const GSVector4i b = (c16 & GSVector4i::cxpr16(0x20)).sll16<1>(); // (value & 0x20) << 1
|
||||
const GSVector4i c = (c16.srl16<10>() & single_mask); // ((value >> 10) & 0x1F)
|
||||
const GSVector4i d = (c16 & single_mask).sll16<11>(); // ((value & 0x1F) << 11)
|
||||
GSVector4i::store<false>(dest, (((a | b) | c) | d));
|
||||
dest += sizeof(GSVector4i);
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
template<GPUTexture::Format format>
|
||||
ALWAYS_INLINE static void ConvertVRAMPixel(u8*& dest, u16 c16)
|
||||
{
|
||||
if constexpr (format == GPUTexture::Format::RGBA8)
|
||||
{
|
||||
const u32 c32 = VRAMRGBA5551ToRGBA8888(c16);
|
||||
std::memcpy(std::assume_aligned<sizeof(c32)>(dest), &c32, sizeof(c32));
|
||||
dest += sizeof(c32);
|
||||
}
|
||||
else if constexpr (format == GPUTexture::Format::RGB5A1)
|
||||
{
|
||||
const u16 repacked = (c16 & 0x83E0) | ((c16 >> 10) & 0x1F) | ((c16 & 0x1F) << 10);
|
||||
std::memcpy(std::assume_aligned<sizeof(repacked)>(dest), &repacked, sizeof(repacked));
|
||||
dest += sizeof(repacked);
|
||||
}
|
||||
else if constexpr (format == GPUTexture::Format::A1BGR5)
|
||||
{
|
||||
const u16 repacked = ((c16 & 0x3E0) << 1) | ((c16 >> 9) & 0x3E) | ((c16 & 0x1F) << 11) | (c16 >> 15);
|
||||
std::memcpy(std::assume_aligned<sizeof(repacked)>(dest), &repacked, sizeof(repacked));
|
||||
dest += sizeof(repacked);
|
||||
}
|
||||
else if constexpr (format == GPUTexture::Format::RGB565)
|
||||
{
|
||||
const u16 repacked = ((c16 & 0x3E0) << 1) | ((c16 & 0x20) << 1) | ((c16 >> 10) & 0x1F) | ((c16 & 0x1F) << 11);
|
||||
std::memcpy(std::assume_aligned<sizeof(repacked)>(dest), &repacked, sizeof(repacked));
|
||||
dest += sizeof(repacked);
|
||||
}
|
||||
}
|
||||
|
||||
union GPUVertexPosition
|
||||
{
|
||||
u32 bits;
|
||||
|
|
|
@ -374,6 +374,10 @@ bool Host::CreateGPUDevice(RenderAPI api, bool fullscreen, Error* error)
|
|||
disabled_features |= GPUDevice::FEATURE_MASK_MEMORY_IMPORT;
|
||||
if (g_settings.gpu_disable_raster_order_views)
|
||||
disabled_features |= GPUDevice::FEATURE_MASK_RASTER_ORDER_VIEWS;
|
||||
if (g_settings.gpu_disable_compute_shaders)
|
||||
disabled_features |= GPUDevice::FEATURE_MASK_COMPUTE_SHADERS;
|
||||
if (g_settings.gpu_disable_compressed_textures)
|
||||
disabled_features |= GPUDevice::FEATURE_MASK_COMPRESSED_TEXTURES;
|
||||
|
||||
// Don't dump shaders on debug builds for Android, users will complain about storage...
|
||||
#if !defined(__ANDROID__) || defined(_DEBUG)
|
||||
|
|
|
@ -111,7 +111,7 @@ static void HotkeySaveStateSlot(bool global, s32 slot)
|
|||
std::string path(global ? System::GetGlobalSaveStateFileName(slot) :
|
||||
System::GetGameSaveStateFileName(System::GetGameSerial(), slot));
|
||||
Error error;
|
||||
if (!System::SaveState(path.c_str(), &error, g_settings.create_save_state_backups))
|
||||
if (!System::SaveState(std::move(path), &error, g_settings.create_save_state_backups, false))
|
||||
{
|
||||
Host::AddIconOSDMessage(
|
||||
"SaveState", ICON_FA_EXCLAMATION_TRIANGLE,
|
||||
|
|
|
@ -389,7 +389,7 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
|
|||
if (g_settings.display_show_resolution)
|
||||
{
|
||||
const u32 resolution_scale = g_gpu->GetResolutionScale();
|
||||
const auto [display_width, display_height] = g_gpu->GetFullDisplayResolution();// wrong
|
||||
const auto [display_width, display_height] = g_gpu->GetFullDisplayResolution(); // wrong
|
||||
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled();
|
||||
const bool pal = g_gpu->IsInPALMode();
|
||||
text.format("{}x{} {} {} [{}x]", display_width * resolution_scale, display_height * resolution_scale,
|
||||
|
@ -827,66 +827,76 @@ static std::string GetCurrentSlotPath();
|
|||
static constexpr const char* DATE_TIME_FORMAT =
|
||||
TRANSLATE_NOOP("SaveStateSelectorUI", "Saved at {0:%H:%M} on {0:%a} {0:%Y/%m/%d}.");
|
||||
|
||||
static std::shared_ptr<GPUTexture> s_placeholder_texture;
|
||||
namespace {
|
||||
|
||||
static std::string s_load_legend;
|
||||
static std::string s_save_legend;
|
||||
static std::string s_prev_legend;
|
||||
static std::string s_next_legend;
|
||||
struct ALIGN_TO_CACHE_LINE State
|
||||
{
|
||||
std::shared_ptr<GPUTexture> placeholder_texture;
|
||||
|
||||
static llvm::SmallVector<ListEntry, System::PER_GAME_SAVE_STATE_SLOTS + System::GLOBAL_SAVE_STATE_SLOTS> s_slots;
|
||||
static s32 s_current_slot = 0;
|
||||
static bool s_current_slot_global = false;
|
||||
std::string load_legend;
|
||||
std::string save_legend;
|
||||
std::string prev_legend;
|
||||
std::string next_legend;
|
||||
|
||||
static float s_open_time = 0.0f;
|
||||
static float s_close_time = 0.0f;
|
||||
llvm::SmallVector<ListEntry, System::PER_GAME_SAVE_STATE_SLOTS + System::GLOBAL_SAVE_STATE_SLOTS> slots;
|
||||
s32 current_slot = 0;
|
||||
bool current_slot_global = false;
|
||||
|
||||
static ImAnimatedFloat s_scroll_animated;
|
||||
static ImAnimatedFloat s_background_animated;
|
||||
float open_time = 0.0f;
|
||||
float close_time = 0.0f;
|
||||
|
||||
ImAnimatedFloat scroll_animated;
|
||||
ImAnimatedFloat background_animated;
|
||||
|
||||
bool is_open = false;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
static State s_state;
|
||||
|
||||
static bool s_open = false;
|
||||
} // namespace SaveStateSelectorUI
|
||||
|
||||
bool SaveStateSelectorUI::IsOpen()
|
||||
{
|
||||
return s_open;
|
||||
return s_state.is_open;
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::Open(float open_time /* = DEFAULT_OPEN_TIME */)
|
||||
{
|
||||
const std::string& serial = System::GetGameSerial();
|
||||
|
||||
s_open_time = 0.0f;
|
||||
s_close_time = open_time;
|
||||
s_state.open_time = 0.0f;
|
||||
s_state.close_time = open_time;
|
||||
|
||||
if (s_open)
|
||||
if (s_state.is_open)
|
||||
return;
|
||||
|
||||
if (!s_placeholder_texture)
|
||||
s_placeholder_texture = ImGuiFullscreen::LoadTexture("no-save.png");
|
||||
if (!s_state.placeholder_texture)
|
||||
s_state.placeholder_texture = ImGuiFullscreen::LoadTexture("no-save.png");
|
||||
|
||||
s_open = true;
|
||||
s_state.is_open = true;
|
||||
RefreshList(serial);
|
||||
RefreshHotkeyLegend();
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::Close()
|
||||
{
|
||||
s_open = false;
|
||||
s_load_legend = {};
|
||||
s_save_legend = {};
|
||||
s_prev_legend = {};
|
||||
s_next_legend = {};
|
||||
s_state.is_open = false;
|
||||
s_state.load_legend = {};
|
||||
s_state.save_legend = {};
|
||||
s_state.prev_legend = {};
|
||||
s_state.next_legend = {};
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::RefreshList(const std::string& serial)
|
||||
{
|
||||
for (ListEntry& entry : s_slots)
|
||||
for (ListEntry& entry : s_state.slots)
|
||||
{
|
||||
if (entry.preview_texture)
|
||||
g_gpu_device->RecycleTexture(std::move(entry.preview_texture));
|
||||
}
|
||||
s_slots.clear();
|
||||
s_state.slots.clear();
|
||||
|
||||
if (System::IsShutdown())
|
||||
return;
|
||||
|
@ -904,16 +914,16 @@ void SaveStateSelectorUI::RefreshList(const std::string& serial)
|
|||
else
|
||||
InitializePlaceholderListEntry(&li, std::move(path), i, false);
|
||||
|
||||
s_slots.push_back(std::move(li));
|
||||
s_state.slots.push_back(std::move(li));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// reset slot if it's not global
|
||||
if (!s_current_slot_global)
|
||||
if (!s_state.current_slot_global)
|
||||
{
|
||||
s_current_slot = 0;
|
||||
s_current_slot_global = true;
|
||||
s_state.current_slot = 0;
|
||||
s_state.current_slot_global = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -928,7 +938,7 @@ void SaveStateSelectorUI::RefreshList(const std::string& serial)
|
|||
else
|
||||
InitializePlaceholderListEntry(&li, std::move(path), i, true);
|
||||
|
||||
s_slots.push_back(std::move(li));
|
||||
s_state.slots.push_back(std::move(li));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -938,33 +948,33 @@ void SaveStateSelectorUI::Clear()
|
|||
// big picture UI, in which case we have to delete them here...
|
||||
ClearList();
|
||||
|
||||
s_current_slot = 0;
|
||||
s_current_slot_global = false;
|
||||
s_scroll_animated.Reset(0.0f);
|
||||
s_background_animated.Reset(0.0f);
|
||||
s_state.current_slot = 0;
|
||||
s_state.current_slot_global = false;
|
||||
s_state.scroll_animated.Reset(0.0f);
|
||||
s_state.background_animated.Reset(0.0f);
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::ClearList()
|
||||
{
|
||||
for (ListEntry& li : s_slots)
|
||||
for (ListEntry& li : s_state.slots)
|
||||
{
|
||||
if (li.preview_texture)
|
||||
g_gpu_device->RecycleTexture(std::move(li.preview_texture));
|
||||
}
|
||||
s_slots.clear();
|
||||
s_state.slots.clear();
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::DestroyTextures()
|
||||
{
|
||||
Close();
|
||||
|
||||
for (ListEntry& entry : s_slots)
|
||||
for (ListEntry& entry : s_state.slots)
|
||||
{
|
||||
if (entry.preview_texture)
|
||||
g_gpu_device->RecycleTexture(std::move(entry.preview_texture));
|
||||
}
|
||||
|
||||
s_placeholder_texture.reset();
|
||||
s_state.placeholder_texture.reset();
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::RefreshHotkeyLegend()
|
||||
|
@ -974,33 +984,34 @@ void SaveStateSelectorUI::RefreshHotkeyLegend()
|
|||
return fmt::format("{} - {}", binding, caption);
|
||||
};
|
||||
|
||||
s_load_legend = format_legend_entry(Host::GetSmallStringSettingValue("Hotkeys", "LoadSelectedSaveState"),
|
||||
TRANSLATE_SV("SaveStateSelectorUI", "Load"));
|
||||
s_save_legend = format_legend_entry(Host::GetSmallStringSettingValue("Hotkeys", "SaveSelectedSaveState"),
|
||||
TRANSLATE_SV("SaveStateSelectorUI", "Save"));
|
||||
s_prev_legend = format_legend_entry(Host::GetSmallStringSettingValue("Hotkeys", "SelectPreviousSaveStateSlot"),
|
||||
TRANSLATE_SV("SaveStateSelectorUI", "Select Previous"));
|
||||
s_next_legend = format_legend_entry(Host::GetSmallStringSettingValue("Hotkeys", "SelectNextSaveStateSlot"),
|
||||
TRANSLATE_SV("SaveStateSelectorUI", "Select Next"));
|
||||
s_state.load_legend = format_legend_entry(Host::GetSmallStringSettingValue("Hotkeys", "LoadSelectedSaveState"),
|
||||
TRANSLATE_SV("SaveStateSelectorUI", "Load"));
|
||||
s_state.save_legend = format_legend_entry(Host::GetSmallStringSettingValue("Hotkeys", "SaveSelectedSaveState"),
|
||||
TRANSLATE_SV("SaveStateSelectorUI", "Save"));
|
||||
s_state.prev_legend = format_legend_entry(Host::GetSmallStringSettingValue("Hotkeys", "SelectPreviousSaveStateSlot"),
|
||||
TRANSLATE_SV("SaveStateSelectorUI", "Select Previous"));
|
||||
s_state.next_legend = format_legend_entry(Host::GetSmallStringSettingValue("Hotkeys", "SelectNextSaveStateSlot"),
|
||||
TRANSLATE_SV("SaveStateSelectorUI", "Select Next"));
|
||||
}
|
||||
|
||||
void SaveStateSelectorUI::SelectNextSlot(bool open_selector)
|
||||
{
|
||||
const s32 total_slots = s_current_slot_global ? System::GLOBAL_SAVE_STATE_SLOTS : System::PER_GAME_SAVE_STATE_SLOTS;
|
||||
s_current_slot++;
|
||||
if (s_current_slot >= total_slots)
|
||||
const s32 total_slots =
|
||||
s_state.current_slot_global ? System::GLOBAL_SAVE_STATE_SLOTS : System::PER_GAME_SAVE_STATE_SLOTS;
|
||||
s_state.current_slot++;
|
||||
if (s_state.current_slot >= total_slots)
|
||||
{
|
||||
if (!System::GetGameSerial().empty())
|
||||
s_current_slot_global ^= true;
|
||||
s_current_slot -= total_slots;
|
||||
s_state.current_slot_global ^= true;
|
||||
s_state.current_slot -= total_slots;
|
||||
}
|
||||
|
||||
if (open_selector)
|
||||
{
|
||||
if (!s_open)
|
||||
if (!s_state.is_open)
|
||||
Open();
|
||||
|
||||
s_open_time = 0.0f;
|
||||
s_state.open_time = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1010,20 +1021,21 @@ void SaveStateSelectorUI::SelectNextSlot(bool open_selector)
|
|||
|
||||
void SaveStateSelectorUI::SelectPreviousSlot(bool open_selector)
|
||||
{
|
||||
s_current_slot--;
|
||||
if (s_current_slot < 0)
|
||||
s_state.current_slot--;
|
||||
if (s_state.current_slot < 0)
|
||||
{
|
||||
if (!System::GetGameSerial().empty())
|
||||
s_current_slot_global ^= true;
|
||||
s_current_slot += s_current_slot_global ? System::GLOBAL_SAVE_STATE_SLOTS : System::PER_GAME_SAVE_STATE_SLOTS;
|
||||
s_state.current_slot_global ^= true;
|
||||
s_state.current_slot +=
|
||||
s_state.current_slot_global ? System::GLOBAL_SAVE_STATE_SLOTS : System::PER_GAME_SAVE_STATE_SLOTS;
|
||||
}
|
||||
|
||||
if (open_selector)
|
||||
{
|
||||
if (!s_open)
|
||||
if (!s_state.is_open)
|
||||
Open();
|
||||
|
||||
s_open_time = 0.0f;
|
||||
s_state.open_time = 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1103,9 +1115,9 @@ void SaveStateSelectorUI::Draw()
|
|||
const float item_height = std::floor(image_size.y + padding * 2.0f);
|
||||
const float text_indent = image_size.x + padding + padding;
|
||||
|
||||
for (size_t i = 0; i < s_slots.size(); i++)
|
||||
for (size_t i = 0; i < s_state.slots.size(); i++)
|
||||
{
|
||||
const ListEntry& entry = s_slots[i];
|
||||
const ListEntry& entry = s_state.slots[i];
|
||||
const float y_start = item_height * static_cast<float>(i);
|
||||
|
||||
if (entry.slot == current_slot && entry.global == current_slot_global)
|
||||
|
@ -1124,19 +1136,20 @@ void SaveStateSelectorUI::Draw()
|
|||
else if (item_rect.Max.y > window_rect.Max.y)
|
||||
scroll_target = (ImGui::GetScrollY() + (item_rect.Max.y - window_rect.Max.y));
|
||||
|
||||
if (scroll_target != s_scroll_animated.GetEndValue())
|
||||
s_scroll_animated.Start(ImGui::GetScrollY(), scroll_target, SCROLL_ANIMATION_TIME);
|
||||
if (scroll_target != s_state.scroll_animated.GetEndValue())
|
||||
s_state.scroll_animated.Start(ImGui::GetScrollY(), scroll_target, SCROLL_ANIMATION_TIME);
|
||||
}
|
||||
|
||||
if (s_scroll_animated.IsActive())
|
||||
ImGui::SetScrollY(s_scroll_animated.UpdateAndGetValue());
|
||||
if (s_state.scroll_animated.IsActive())
|
||||
ImGui::SetScrollY(s_state.scroll_animated.UpdateAndGetValue());
|
||||
|
||||
if (s_background_animated.GetEndValue() != p_start.y)
|
||||
s_background_animated.Start(s_background_animated.UpdateAndGetValue(), p_start.y, BG_ANIMATION_TIME);
|
||||
if (s_state.background_animated.GetEndValue() != p_start.y)
|
||||
s_state.background_animated.Start(s_state.background_animated.UpdateAndGetValue(), p_start.y,
|
||||
BG_ANIMATION_TIME);
|
||||
|
||||
ImVec2 highlight_pos;
|
||||
if (s_background_animated.IsActive())
|
||||
highlight_pos = ImVec2(p_start.x, s_background_animated.UpdateAndGetValue());
|
||||
if (s_state.background_animated.IsActive())
|
||||
highlight_pos = ImVec2(p_start.x, s_state.background_animated.UpdateAndGetValue());
|
||||
else
|
||||
highlight_pos = p_start;
|
||||
|
||||
|
@ -1146,7 +1159,7 @@ void SaveStateSelectorUI::Draw()
|
|||
}
|
||||
|
||||
if (GPUTexture* preview_texture =
|
||||
entry.preview_texture ? entry.preview_texture.get() : s_placeholder_texture.get())
|
||||
entry.preview_texture ? entry.preview_texture.get() : s_state.placeholder_texture.get())
|
||||
{
|
||||
ImGui::SetCursorPosY(y_start + padding);
|
||||
ImGui::SetCursorPosX(padding);
|
||||
|
@ -1184,13 +1197,13 @@ void SaveStateSelectorUI::Draw()
|
|||
if (ImGui::BeginTable("table", 2))
|
||||
{
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(s_load_legend.c_str());
|
||||
ImGui::TextUnformatted(s_state.load_legend.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(s_prev_legend.c_str());
|
||||
ImGui::TextUnformatted(s_state.prev_legend.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(s_save_legend.c_str());
|
||||
ImGui::TextUnformatted(s_state.save_legend.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(s_next_legend.c_str());
|
||||
ImGui::TextUnformatted(s_state.next_legend.c_str());
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
@ -1204,8 +1217,8 @@ void SaveStateSelectorUI::Draw()
|
|||
ImGui::PopStyleColor();
|
||||
|
||||
// auto-close
|
||||
s_open_time += io.DeltaTime;
|
||||
if (s_open_time >= s_close_time)
|
||||
s_state.open_time += io.DeltaTime;
|
||||
if (s_state.open_time >= s_state.close_time)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
|
@ -1219,25 +1232,25 @@ void SaveStateSelectorUI::Draw()
|
|||
|
||||
s32 SaveStateSelectorUI::GetCurrentSlot()
|
||||
{
|
||||
return s_current_slot + 1;
|
||||
return s_state.current_slot + 1;
|
||||
}
|
||||
|
||||
bool SaveStateSelectorUI::IsCurrentSlotGlobal()
|
||||
{
|
||||
return s_current_slot_global;
|
||||
return s_state.current_slot_global;
|
||||
}
|
||||
|
||||
std::string SaveStateSelectorUI::GetCurrentSlotPath()
|
||||
{
|
||||
std::string filename;
|
||||
if (!s_current_slot_global)
|
||||
if (!s_state.current_slot_global)
|
||||
{
|
||||
if (const std::string& serial = System::GetGameSerial(); !serial.empty())
|
||||
filename = System::GetGameSaveStateFileName(serial, s_current_slot + 1);
|
||||
filename = System::GetGameSaveStateFileName(serial, s_state.current_slot + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
filename = System::GetGlobalSaveStateFileName(s_current_slot + 1);
|
||||
filename = System::GetGlobalSaveStateFileName(s_state.current_slot + 1);
|
||||
}
|
||||
|
||||
return filename;
|
||||
|
@ -1277,7 +1290,7 @@ void SaveStateSelectorUI::SaveCurrentSlot()
|
|||
if (std::string path = GetCurrentSlotPath(); !path.empty())
|
||||
{
|
||||
Error error;
|
||||
if (!System::SaveState(path.c_str(), &error, g_settings.create_save_state_backups))
|
||||
if (!System::SaveState(std::move(path), &error, g_settings.create_save_state_backups, false))
|
||||
{
|
||||
Host::AddIconOSDMessage("SaveState", ICON_EMOJI_WARNING,
|
||||
fmt::format(TRANSLATE_FS("OSDMessage", "Failed to save state to slot {0}:\n{1}"),
|
||||
|
@ -1312,7 +1325,7 @@ void ImGuiManager::RenderOverlayWindows()
|
|||
const System::State state = System::GetState();
|
||||
if (state != System::State::Shutdown)
|
||||
{
|
||||
if (SaveStateSelectorUI::s_open)
|
||||
if (SaveStateSelectorUI::s_state.is_open)
|
||||
SaveStateSelectorUI::Draw();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -171,8 +171,8 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
|
|||
enable_discord_presence = si.GetBoolValue("Main", "EnableDiscordPresence", false);
|
||||
rewind_enable = si.GetBoolValue("Main", "RewindEnable", false);
|
||||
rewind_save_frequency = si.GetFloatValue("Main", "RewindFrequency", 10.0f);
|
||||
rewind_save_slots = static_cast<u8>(si.GetUIntValue("Main", "RewindSaveSlots", 10u));
|
||||
runahead_frames = static_cast<u8>(si.GetUIntValue("Main", "RunaheadFrameCount", 0u));
|
||||
rewind_save_slots = static_cast<u16>(std::min(si.GetUIntValue("Main", "RewindSaveSlots", 10u), 65535u));
|
||||
runahead_frames = static_cast<u8>(std::min(si.GetUIntValue("Main", "RunaheadFrameCount", 0u), 255u));
|
||||
|
||||
cpu_execution_mode =
|
||||
ParseCPUExecutionMode(
|
||||
|
@ -202,6 +202,8 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
|
|||
gpu_disable_texture_copy_to_self = si.GetBoolValue("GPU", "DisableTextureCopyToSelf", false);
|
||||
gpu_disable_memory_import = si.GetBoolValue("GPU", "DisableMemoryImport", false);
|
||||
gpu_disable_raster_order_views = si.GetBoolValue("GPU", "DisableRasterOrderViews", false);
|
||||
gpu_disable_compute_shaders = si.GetBoolValue("GPU", "DisableComputeShaders", false);
|
||||
gpu_disable_compressed_textures = si.GetBoolValue("GPU", "DisableCompressedTextures", false);
|
||||
gpu_per_sample_shading = si.GetBoolValue("GPU", "PerSampleShading", false);
|
||||
gpu_use_thread = si.GetBoolValue("GPU", "UseThread", true);
|
||||
gpu_use_software_renderer_for_readbacks = si.GetBoolValue("GPU", "UseSoftwareRendererForReadbacks", false);
|
||||
|
@ -539,6 +541,8 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
|
|||
si.SetBoolValue("GPU", "DisableTextureCopyToSelf", gpu_disable_texture_copy_to_self);
|
||||
si.SetBoolValue("GPU", "DisableMemoryImport", gpu_disable_memory_import);
|
||||
si.SetBoolValue("GPU", "DisableRasterOrderViews", gpu_disable_raster_order_views);
|
||||
si.SetBoolValue("GPU", "DisableComputeShaders", gpu_disable_compute_shaders);
|
||||
si.SetBoolValue("GPU", "DisableCompressedTextures", gpu_disable_compressed_textures);
|
||||
}
|
||||
|
||||
si.SetBoolValue("GPU", "PerSampleShading", gpu_per_sample_shading);
|
||||
|
@ -964,6 +968,8 @@ void Settings::FixIncompatibleSettings(bool display_osd_messages)
|
|||
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;
|
||||
}
|
||||
|
||||
// fast forward boot requires fast boot
|
||||
|
|
|
@ -91,7 +91,7 @@ struct Settings
|
|||
|
||||
bool rewind_enable : 1 = false;
|
||||
float rewind_save_frequency = 10.0f;
|
||||
u8 rewind_save_slots = 10;
|
||||
u16 rewind_save_slots = 10;
|
||||
u8 runahead_frames = 0;
|
||||
|
||||
GPURenderer gpu_renderer = DEFAULT_GPU_RENDERER;
|
||||
|
@ -108,6 +108,8 @@ struct Settings
|
|||
bool gpu_disable_texture_copy_to_self : 1 = false;
|
||||
bool gpu_disable_memory_import : 1 = false;
|
||||
bool gpu_disable_raster_order_views : 1 = false;
|
||||
bool gpu_disable_compute_shaders : 1 = false;
|
||||
bool gpu_disable_compressed_textures : 1 = false;
|
||||
bool gpu_per_sample_shading : 1 = false;
|
||||
bool gpu_true_color : 1 = true;
|
||||
bool gpu_scaled_dithering : 1 = true;
|
||||
|
@ -168,7 +170,7 @@ struct Settings
|
|||
bool display_stretch_vertically : 1 = false;
|
||||
bool display_auto_resize_window : 1 = false;
|
||||
float display_pre_frame_sleep_buffer = DEFAULT_DISPLAY_PRE_FRAME_SLEEP_BUFFER;
|
||||
float display_osd_scale = 100.0f;
|
||||
float display_osd_scale = DEFAULT_OSD_SCALE;
|
||||
float display_osd_margin = 0.0f;
|
||||
float gpu_pgxp_tolerance = -1.0f;
|
||||
float gpu_pgxp_depth_clear_threshold = DEFAULT_GPU_PGXP_DEPTH_THRESHOLD / GPU_PGXP_DEPTH_THRESHOLD_SCALE;
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
|
||||
#include "common/types.h"
|
||||
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 22;
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 23;
|
||||
|
|
|
@ -1663,8 +1663,8 @@ bool System::SaveResumeState(Error* error)
|
|||
return false;
|
||||
}
|
||||
|
||||
const std::string path(GetGameSaveStateFileName(s_state.running_game_serial, -1));
|
||||
return SaveState(path.c_str(), error, false);
|
||||
std::string path(GetGameSaveStateFileName(s_state.running_game_serial, -1));
|
||||
return SaveState(std::move(path), error, false, true);
|
||||
}
|
||||
|
||||
bool System::BootSystem(SystemBootParameters parameters, Error* error)
|
||||
|
@ -2481,7 +2481,10 @@ bool System::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
|
|||
u32 state_taints = s_state.taints;
|
||||
sw.DoEx(&state_taints, 75, static_cast<u32>(0));
|
||||
if (state_taints != s_state.taints) [[unlikely]]
|
||||
{
|
||||
WarnAboutStateTaints(state_taints);
|
||||
s_state.taints |= state_taints;
|
||||
}
|
||||
|
||||
sw.Do(&s_state.frame_number);
|
||||
sw.Do(&s_state.internal_frame_number);
|
||||
|
@ -3010,14 +3013,14 @@ bool System::ReadAndDecompressStateData(std::FILE* fp, std::span<u8> dst, u32 fi
|
|||
}
|
||||
}
|
||||
|
||||
bool System::SaveState(const char* path, Error* error, bool backup_existing_save)
|
||||
bool System::SaveState(std::string path, Error* error, bool backup_existing_save, bool ignore_memcard_busy)
|
||||
{
|
||||
if (!IsValid() || IsReplayingGPUDump())
|
||||
{
|
||||
Error::SetStringView(error, TRANSLATE_SV("System", "System is not in correct state."));
|
||||
return false;
|
||||
}
|
||||
else if (IsSavingMemoryCards())
|
||||
else if (!ignore_memcard_busy && IsSavingMemoryCards())
|
||||
{
|
||||
Error::SetStringView(error, TRANSLATE_SV("System", "Cannot save state while memory card is being saved."));
|
||||
return false;
|
||||
|
@ -3029,40 +3032,62 @@ bool System::SaveState(const char* path, Error* error, bool backup_existing_save
|
|||
if (!SaveStateToBuffer(&buffer, error, 256))
|
||||
return false;
|
||||
|
||||
// TODO: Do this on a thread pool
|
||||
VERBOSE_LOG("Preparing state save took {:.2f} msec", save_timer.GetTimeMilliseconds());
|
||||
|
||||
if (backup_existing_save && FileSystem::FileExists(path))
|
||||
{
|
||||
Error backup_error;
|
||||
const std::string backup_filename = Path::ReplaceExtension(path, "bak");
|
||||
if (!FileSystem::RenamePath(path, backup_filename.c_str(), &backup_error))
|
||||
std::string osd_key = fmt::format("save_state_{}", path);
|
||||
Host::AddIconOSDMessage(osd_key, ICON_EMOJI_FLOPPY_DISK,
|
||||
fmt::format(TRANSLATE_FS("System", "Saving state to '{}'."), Path::GetFileName(path)), 60.0f);
|
||||
|
||||
QueueTaskOnThread([path = std::move(path), buffer = std::move(buffer), osd_key = std::move(osd_key),
|
||||
backup_existing_save, compression = g_settings.save_state_compression]() {
|
||||
INFO_LOG("Saving state to '{}'...", path);
|
||||
|
||||
Error lerror;
|
||||
Timer lsave_timer;
|
||||
|
||||
if (backup_existing_save && FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
ERROR_LOG("Failed to rename save state backup '{}': {}", Path::GetFileName(backup_filename),
|
||||
backup_error.GetDescription());
|
||||
const std::string backup_filename = Path::ReplaceExtension(path, "bak");
|
||||
if (!FileSystem::RenamePath(path.c_str(), backup_filename.c_str(), &lerror))
|
||||
{
|
||||
ERROR_LOG("Failed to rename save state backup '{}': {}", Path::GetFileName(backup_filename),
|
||||
lerror.GetDescription());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto fp = FileSystem::CreateAtomicRenamedFile(path, error);
|
||||
if (!fp)
|
||||
{
|
||||
Error::AddPrefixFmt(error, "Cannot open '{}': ", Path::GetFileName(path));
|
||||
return false;
|
||||
}
|
||||
auto fp = FileSystem::CreateAtomicRenamedFile(path, &lerror);
|
||||
bool result = false;
|
||||
if (fp)
|
||||
{
|
||||
if (SaveStateBufferToFile(buffer, fp.get(), &lerror, compression))
|
||||
result = FileSystem::CommitAtomicRenamedFile(fp, &lerror);
|
||||
else
|
||||
FileSystem::DiscardAtomicRenamedFile(fp);
|
||||
}
|
||||
else
|
||||
{
|
||||
lerror.AddPrefixFmt("Cannot open '{}': ", Path::GetFileName(path));
|
||||
}
|
||||
|
||||
INFO_LOG("Saving state to '{}'...", path);
|
||||
VERBOSE_LOG("Saving state took {:.2f} msec", lsave_timer.GetTimeMilliseconds());
|
||||
if (result)
|
||||
{
|
||||
Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_FLOPPY_DISK,
|
||||
fmt::format(TRANSLATE_FS("System", "State saved to '{}'."), Path::GetFileName(path)),
|
||||
Host::OSD_QUICK_DURATION);
|
||||
}
|
||||
else
|
||||
{
|
||||
Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_WARNING,
|
||||
fmt::format(TRANSLATE_FS("System", "Failed to save state to '{0}':\n{1}"),
|
||||
Path::GetFileName(path), lerror.GetDescription()),
|
||||
Host::OSD_ERROR_DURATION);
|
||||
}
|
||||
|
||||
if (!SaveStateBufferToFile(buffer, fp.get(), error, g_settings.save_state_compression))
|
||||
{
|
||||
FileSystem::DiscardAtomicRenamedFile(fp);
|
||||
return false;
|
||||
}
|
||||
System::RemoveSelfFromTaskThreads();
|
||||
});
|
||||
|
||||
Host::AddIconOSDMessage("save_state", ICON_EMOJI_FLOPPY_DISK,
|
||||
fmt::format(TRANSLATE_FS("OSDMessage", "State saved to '{}'."), Path::GetFileName(path)),
|
||||
5.0f);
|
||||
|
||||
VERBOSE_LOG("Saving state took {:.2f} msec", save_timer.GetTimeMilliseconds());
|
||||
return FileSystem::CommitAtomicRenamedFile(fp, error);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool System::SaveStateToBuffer(SaveStateBuffer* buffer, Error* error, u32 screenshot_size /* = 256 */)
|
||||
|
@ -3261,7 +3286,7 @@ u32 System::CompressAndWriteStateData(std::FILE* fp, std::span<const u8> src, Sa
|
|||
buffer.resize(buffer_size);
|
||||
|
||||
const int level =
|
||||
((method == SaveStateCompressionMode::ZstLow) ? 1 : ((method == SaveStateCompressionMode::ZstHigh) ? 19 : 0));
|
||||
((method == SaveStateCompressionMode::ZstLow) ? 1 : ((method == SaveStateCompressionMode::ZstHigh) ? 18 : 0));
|
||||
const size_t compressed_size = ZSTD_compress(buffer.data(), buffer_size, src.data(), src.size(), level);
|
||||
if (ZSTD_isError(compressed_size)) [[unlikely]]
|
||||
{
|
||||
|
@ -4244,6 +4269,8 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
|||
g_settings.gpu_disable_texture_copy_to_self != old_settings.gpu_disable_texture_copy_to_self ||
|
||||
g_settings.gpu_disable_memory_import != old_settings.gpu_disable_memory_import ||
|
||||
g_settings.gpu_disable_raster_order_views != old_settings.gpu_disable_raster_order_views ||
|
||||
g_settings.gpu_disable_compute_shaders != old_settings.gpu_disable_compute_shaders ||
|
||||
g_settings.gpu_disable_compressed_textures != old_settings.gpu_disable_compressed_textures ||
|
||||
g_settings.display_exclusive_fullscreen_control != old_settings.display_exclusive_fullscreen_control))
|
||||
{
|
||||
// if debug device/threaded presentation change, we need to recreate the whole display
|
||||
|
@ -4256,6 +4283,8 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
|
|||
g_settings.gpu_disable_texture_copy_to_self != old_settings.gpu_disable_texture_copy_to_self ||
|
||||
g_settings.gpu_disable_memory_import != old_settings.gpu_disable_memory_import ||
|
||||
g_settings.gpu_disable_raster_order_views != old_settings.gpu_disable_raster_order_views ||
|
||||
g_settings.gpu_disable_compute_shaders != old_settings.gpu_disable_compute_shaders ||
|
||||
g_settings.gpu_disable_compressed_textures != old_settings.gpu_disable_compressed_textures ||
|
||||
g_settings.display_exclusive_fullscreen_control != old_settings.display_exclusive_fullscreen_control);
|
||||
|
||||
Host::AddIconOSDMessage("RendererSwitch", ICON_FA_PAINT_ROLLER,
|
||||
|
@ -4698,6 +4727,10 @@ void System::WarnAboutUnsafeSettings()
|
|||
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Video timings set to default."));
|
||||
if (g_settings.gpu_widescreen_hack)
|
||||
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "Widescreen rendering disabled."));
|
||||
if (g_settings.gpu_pgxp_enable)
|
||||
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "PGXP disabled."));
|
||||
if (g_settings.gpu_texture_cache)
|
||||
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "GPU texture cache disabled."));
|
||||
if (g_settings.display_24bit_chroma_smoothing)
|
||||
APPEND_SUBMESSAGE(TRANSLATE_SV("System", "FMV chroma smoothing disabled."));
|
||||
if (g_settings.cdrom_read_speedup != 1)
|
||||
|
@ -5391,7 +5424,7 @@ std::string System::GetGlobalSaveStateFileName(s32 slot)
|
|||
return Path::Combine(EmuFolders::SaveStates, fmt::format("savestate_{}.sav", slot));
|
||||
}
|
||||
|
||||
std::vector<SaveStateInfo> System::GetAvailableSaveStates(const char* serial)
|
||||
std::vector<SaveStateInfo> System::GetAvailableSaveStates(std::string_view serial)
|
||||
{
|
||||
std::vector<SaveStateInfo> si;
|
||||
std::string path;
|
||||
|
@ -5404,7 +5437,7 @@ std::vector<SaveStateInfo> System::GetAvailableSaveStates(const char* serial)
|
|||
si.push_back(SaveStateInfo{std::move(path), sd.ModificationTime, static_cast<s32>(slot), global});
|
||||
};
|
||||
|
||||
if (serial && std::strlen(serial) > 0)
|
||||
if (!serial.empty())
|
||||
{
|
||||
add_path(GetGameSaveStateFileName(serial, -1), -1, false);
|
||||
for (s32 i = 1; i <= PER_GAME_SAVE_STATE_SLOTS; i++)
|
||||
|
@ -5417,9 +5450,9 @@ std::vector<SaveStateInfo> System::GetAvailableSaveStates(const char* serial)
|
|||
return si;
|
||||
}
|
||||
|
||||
std::optional<SaveStateInfo> System::GetSaveStateInfo(const char* serial, s32 slot)
|
||||
std::optional<SaveStateInfo> System::GetSaveStateInfo(std::string_view serial, s32 slot)
|
||||
{
|
||||
const bool global = (!serial || serial[0] == 0);
|
||||
const bool global = serial.empty();
|
||||
std::string path = global ? GetGlobalSaveStateFileName(slot) : GetGameSaveStateFileName(serial, slot);
|
||||
|
||||
FILESYSTEM_STAT_DATA sd;
|
||||
|
@ -5460,7 +5493,7 @@ std::optional<ExtendedSaveStateInfo> System::GetExtendedSaveStateInfo(const char
|
|||
return ssi;
|
||||
}
|
||||
|
||||
void System::DeleteSaveStates(const char* serial, bool resume)
|
||||
void System::DeleteSaveStates(std::string_view serial, bool resume)
|
||||
{
|
||||
const std::vector<SaveStateInfo> states(GetAvailableSaveStates(serial));
|
||||
for (const SaveStateInfo& si : states)
|
||||
|
|
|
@ -256,7 +256,7 @@ void ResetSystem();
|
|||
|
||||
/// Loads state from the specified path.
|
||||
bool LoadState(const char* path, Error* error, bool save_undo_state);
|
||||
bool SaveState(const char* path, Error* error, bool backup_existing_save);
|
||||
bool SaveState(std::string path, Error* error, bool backup_existing_save, bool ignore_memcard_busy);
|
||||
bool SaveResumeState(Error* error);
|
||||
|
||||
/// Runs the VM until the CPU execution is canceled.
|
||||
|
@ -358,16 +358,16 @@ std::optional<ExtendedSaveStateInfo> GetUndoSaveStateInfo();
|
|||
bool UndoLoadState();
|
||||
|
||||
/// Returns a list of save states for the specified game code.
|
||||
std::vector<SaveStateInfo> GetAvailableSaveStates(const char* serial);
|
||||
std::vector<SaveStateInfo> GetAvailableSaveStates(std::string_view serial);
|
||||
|
||||
/// Returns save state info if present. If serial is null or empty, assumes global state.
|
||||
std::optional<SaveStateInfo> GetSaveStateInfo(const char* serial, s32 slot);
|
||||
std::optional<SaveStateInfo> GetSaveStateInfo(std::string_view serial, s32 slot);
|
||||
|
||||
/// Returns save state info from opened save state stream.
|
||||
std::optional<ExtendedSaveStateInfo> GetExtendedSaveStateInfo(const char* path);
|
||||
|
||||
/// Deletes save states for the specified game code. If resume is set, the resume state is deleted too.
|
||||
void DeleteSaveStates(const char* serial, bool resume);
|
||||
void DeleteSaveStates(std::string_view serial, bool resume);
|
||||
|
||||
/// Returns the path to the memory card for the specified game, considering game settings.
|
||||
std::string GetGameMemoryCardPath(std::string_view serial, std::string_view path, u32 slot,
|
||||
|
|
|
@ -44,7 +44,6 @@ ConsoleSettingsWidget::ConsoleSettingsWidget(SettingsWindow* dialog, QWidget* pa
|
|||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.region, "Console", "Region", &Settings::ParseConsoleRegionName,
|
||||
&Settings::GetConsoleRegionName, Settings::DEFAULT_CONSOLE_REGION);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enable8MBRAM, "Console", "Enable8MBRAM", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enableCheats, "Console", "EnableCheats", false);
|
||||
SettingWidgetBinder::BindWidgetToEnumSetting(sif, m_ui.cpuExecutionMode, "CPU", "ExecutionMode",
|
||||
&Settings::ParseCPUExecutionMode, &Settings::GetCPUExecutionModeName,
|
||||
Settings::DEFAULT_CPU_EXECUTION_MODE);
|
||||
|
|
|
@ -48,13 +48,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="enableCheats">
|
||||
<property name="text">
|
||||
<string>Enable Cheats</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -626,34 +626,7 @@ void GameCheatSettingsWidget::onClearClicked()
|
|||
}
|
||||
|
||||
disableAllCheats();
|
||||
|
||||
Error error;
|
||||
std::string path = Cheats::GetChtFilename(m_dialog->getGameSerial(), m_dialog->getGameHash(), true);
|
||||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
if (!FileSystem::DeleteFile(path.c_str(), &error))
|
||||
ERROR_LOG("Failed to remove cht file '{}': {}", Path::GetFileName(path), error.GetDescription());
|
||||
}
|
||||
|
||||
// check for a non-hashed path and remove that too
|
||||
path = Cheats::GetChtFilename(m_dialog->getGameSerial(), std::nullopt, true);
|
||||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
if (!FileSystem::DeleteFile(path.c_str(), &error))
|
||||
ERROR_LOG("Failed to remove cht file '{}': {}", Path::GetFileName(path), error.GetDescription());
|
||||
}
|
||||
|
||||
// and a legacy cht file with the game title
|
||||
if (const std::string& title = m_dialog->getGameTitle(); !title.empty())
|
||||
{
|
||||
path = fmt::format("{}" FS_OSPATH_SEPARATOR_STR "{}.cht", EmuFolders::Cheats, Path::SanitizeFileName(title));
|
||||
if (FileSystem::FileExists(path.c_str()))
|
||||
{
|
||||
if (!FileSystem::DeleteFile(path.c_str(), &error))
|
||||
ERROR_LOG("Failed to remove cht file '{}': {}", Path::GetFileName(path), error.GetDescription());
|
||||
}
|
||||
}
|
||||
|
||||
Cheats::RemoveAllCodes(m_dialog->getGameSerial(), m_dialog->getGameTitle(), m_dialog->getGameHash());
|
||||
reloadList();
|
||||
}
|
||||
|
||||
|
|
|
@ -308,6 +308,12 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
|||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableTextureBuffers, "GPU", "DisableTextureBuffers", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableTextureCopyToSelf, "GPU", "DisableTextureCopyToSelf",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableMemoryImport, "GPU", "DisableMemoryImport", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableRasterOrderViews, "GPU", "DisableRasterOrderViews",
|
||||
false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableComputeShaders, "GPU", "DisableComputeShaders", false);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableCompressedTextures, "GPU", "DisableCompressedTextures",
|
||||
false);
|
||||
|
||||
// Init all dependent options.
|
||||
updateRendererDependentOptions();
|
||||
|
@ -632,6 +638,12 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsWindow* dialog, QWidget*
|
|||
dialog->registerWidgetHelp(m_ui.disableRasterOrderViews, tr("Disable Rasterizer Order Views"), tr("Unchecked"),
|
||||
tr("Disables the use of rasterizer order views. Useful for testing broken graphics "
|
||||
"drivers. <strong>Only for developer use.</strong>"));
|
||||
dialog->registerWidgetHelp(m_ui.disableComputeShaders, tr("Disable Compute Shaders"), tr("Unchecked"),
|
||||
tr("Disables the use of compute shaders. Useful for testing broken graphics drivers. "
|
||||
"<strong>Only for developer use.</strong>"));
|
||||
dialog->registerWidgetHelp(m_ui.disableCompressedTextures, tr("Disable Compressed Textures"), tr("Unchecked"),
|
||||
tr("Disables the use of compressed textures. Useful for testing broken graphics drivers. "
|
||||
"<strong>Only for developer use.</strong>"));
|
||||
}
|
||||
|
||||
GraphicsSettingsWidget::~GraphicsSettingsWidget() = default;
|
||||
|
|
|
@ -1331,13 +1331,6 @@
|
|||
<layout class="QFormLayout" name="formLayout_10">
|
||||
<item row="0" column="0" colspan="2">
|
||||
<layout class="QGridLayout" name="gridLayout_8">
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="disableTextureCopyToSelf">
|
||||
<property name="text">
|
||||
<string>Disable Texture Copy To Self</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="useDebugDevice">
|
||||
<property name="text">
|
||||
|
@ -1345,20 +1338,6 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="disableDualSource">
|
||||
<property name="text">
|
||||
<string>Disable Dual-Source Blending</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="disableFramebufferFetch">
|
||||
<property name="text">
|
||||
<string>Disable Framebuffer Fetch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QCheckBox" name="disableTextureBuffers">
|
||||
<property name="text">
|
||||
|
@ -1366,6 +1345,13 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QCheckBox" name="disableDualSource">
|
||||
<property name="text">
|
||||
<string>Disable Dual-Source Blending</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QCheckBox" name="disableShaderCache">
|
||||
<property name="text">
|
||||
|
@ -1373,6 +1359,20 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QCheckBox" name="disableFramebufferFetch">
|
||||
<property name="text">
|
||||
<string>Disable Framebuffer Fetch</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QCheckBox" name="disableTextureCopyToSelf">
|
||||
<property name="text">
|
||||
<string>Disable Texture Copy To Self</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QCheckBox" name="disableMemoryImport">
|
||||
<property name="text">
|
||||
|
@ -1387,6 +1387,20 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QCheckBox" name="disableComputeShaders">
|
||||
<property name="text">
|
||||
<string>Disable Compute Shaders</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QCheckBox" name="disableCompressedTextures">
|
||||
<property name="text">
|
||||
<string>Disable Compressed Textures</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
</layout>
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include "core/host.h"
|
||||
|
||||
#include "common/bitutils.h"
|
||||
#include "common/string_util.h"
|
||||
|
||||
#include <QtCore/QTimer>
|
||||
#include <QtGui/QKeyEvent>
|
||||
|
@ -61,26 +62,22 @@ void InputBindingWidget::initialize(SettingsInterface* sif, InputBindingInfo::Ty
|
|||
|
||||
void InputBindingWidget::updateText()
|
||||
{
|
||||
static constexpr const char* help_text =
|
||||
QT_TR_NOOP("Left-click to change binding.\nShift-click to set multiple bindings.");
|
||||
static constexpr const char* help_clear_text = QT_TR_NOOP("Right-click to remove binding.");
|
||||
|
||||
if (m_bindings.empty())
|
||||
{
|
||||
setText(QString());
|
||||
setToolTip(QStringLiteral("%1\n\n%2").arg(tr("No binding set.")).arg(tr(help_text)));
|
||||
}
|
||||
else if (m_bindings.size() > 1)
|
||||
{
|
||||
setText(tr("%n bindings", "", static_cast<int>(m_bindings.size())));
|
||||
|
||||
// keep the full thing for the tooltip
|
||||
std::stringstream ss;
|
||||
bool first = true;
|
||||
for (const std::string& binding : m_bindings)
|
||||
{
|
||||
if (first)
|
||||
first = false;
|
||||
else
|
||||
ss << "\n";
|
||||
ss << binding;
|
||||
}
|
||||
setToolTip(QString::fromStdString(ss.str()));
|
||||
const QString qss = QString::fromStdString(StringUtil::JoinString(m_bindings.begin(), m_bindings.end(), "\n"));
|
||||
setToolTip(QStringLiteral("%1\n\n%2\n%3").arg(qss).arg(tr(help_text)).arg(help_clear_text));
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -93,6 +90,7 @@ void InputBindingWidget::updateText()
|
|||
if (binding_text.length() > 35)
|
||||
binding_text = binding_text.left(35).append(QStringLiteral("..."));
|
||||
setText(binding_text);
|
||||
setToolTip(QStringLiteral("%1\n\n%2\n%3").arg(binding_text).arg(tr(help_text)).arg(tr(help_clear_text)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ ISOBrowserWindow::ISOBrowserWindow(QWidget* parent) : QWidget(parent)
|
|||
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
|
||||
|
||||
connect(m_ui.openFile, &QAbstractButton::clicked, this, &ISOBrowserWindow::onOpenFileClicked);
|
||||
connect(m_ui.extract, &QAbstractButton::clicked, this, &ISOBrowserWindow::onExtractClicked);
|
||||
connect(m_ui.directoryView, &QTreeWidget::itemClicked, this, &ISOBrowserWindow::onDirectoryItemClicked);
|
||||
connect(m_ui.fileView, &QTreeWidget::itemActivated, this, &ISOBrowserWindow::onFileItemActivated);
|
||||
connect(m_ui.fileView, &QTreeWidget::itemSelectionChanged, this, &ISOBrowserWindow::onFileItemSelectionChanged);
|
||||
|
@ -102,6 +103,16 @@ void ISOBrowserWindow::onOpenFileClicked()
|
|||
}
|
||||
}
|
||||
|
||||
void ISOBrowserWindow::onExtractClicked()
|
||||
{
|
||||
const QList<QTreeWidgetItem*> items = m_ui.fileView->selectedItems();
|
||||
if (items.isEmpty())
|
||||
return;
|
||||
|
||||
const QString path = items.front()->data(0, Qt::UserRole).toString();
|
||||
extractFile(path);
|
||||
}
|
||||
|
||||
void ISOBrowserWindow::onDirectoryItemClicked(QTreeWidgetItem* item, int column)
|
||||
{
|
||||
populateFiles(item->data(0, Qt::UserRole).toString());
|
||||
|
@ -295,8 +306,6 @@ void ISOBrowserWindow::populateFiles(const QString& path)
|
|||
}
|
||||
|
||||
const auto add_entry = [this](const std::string& full_path, const IsoReader::ISODirectoryEntry& entry) {
|
||||
const std::string_view filename = Path::GetFileName(full_path);
|
||||
|
||||
QTreeWidgetItem* item = new QTreeWidgetItem;
|
||||
item->setIcon(
|
||||
0, QIcon::fromTheme(entry.IsDirectory() ? QStringLiteral("folder-open-line") : QStringLiteral("file-line")));
|
||||
|
|
|
@ -25,6 +25,7 @@ protected:
|
|||
|
||||
private Q_SLOTS:
|
||||
void onOpenFileClicked();
|
||||
void onExtractClicked();
|
||||
void onDirectoryItemClicked(QTreeWidgetItem* item, int column);
|
||||
void onFileItemActivated(QTreeWidgetItem* item, int column);
|
||||
void onFileItemSelectionChanged();
|
||||
|
|
|
@ -811,7 +811,7 @@ void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidg
|
|||
|
||||
if (!entry->serial.empty())
|
||||
{
|
||||
std::vector<SaveStateInfo> available_states(System::GetAvailableSaveStates(entry->serial.c_str()));
|
||||
std::vector<SaveStateInfo> available_states(System::GetAvailableSaveStates(entry->serial));
|
||||
const QString timestamp_format = QLocale::system().dateTimeFormat(QLocale::ShortFormat);
|
||||
const bool challenge_mode = Achievements::IsHardcoreModeActive();
|
||||
for (SaveStateInfo& ssi : available_states)
|
||||
|
@ -869,7 +869,7 @@ void MainWindow::populateGameListContextMenu(const GameList::Entry* entry, QWidg
|
|||
return;
|
||||
}
|
||||
|
||||
System::DeleteSaveStates(entry->serial.c_str(), true);
|
||||
System::DeleteSaveStates(entry->serial, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -881,10 +881,11 @@ static QString FormatTimestampForSaveStateMenu(u64 timestamp)
|
|||
return qtime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
|
||||
}
|
||||
|
||||
void MainWindow::populateLoadStateMenu(const char* game_serial, QMenu* menu)
|
||||
void MainWindow::populateLoadStateMenu(std::string_view game_serial, QMenu* menu)
|
||||
{
|
||||
auto add_slot = [this, game_serial, menu](const QString& title, const QString& empty_title, bool global, s32 slot) {
|
||||
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(global ? nullptr : game_serial, slot);
|
||||
auto add_slot = [this, menu](const QString& title, const QString& empty_title, const std::string_view& serial,
|
||||
s32 slot) {
|
||||
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot);
|
||||
|
||||
const QString menu_title =
|
||||
ssi.has_value() ? title.arg(slot).arg(FormatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot);
|
||||
|
@ -913,28 +914,30 @@ void MainWindow::populateLoadStateMenu(const char* game_serial, QMenu* menu)
|
|||
connect(load_from_state, &QAction::triggered, g_emu_thread, &EmuThread::undoLoadState);
|
||||
menu->addSeparator();
|
||||
|
||||
if (game_serial && std::strlen(game_serial) > 0)
|
||||
if (!game_serial.empty())
|
||||
{
|
||||
for (u32 slot = 1; slot <= System::PER_GAME_SAVE_STATE_SLOTS; slot++)
|
||||
add_slot(tr("Game Save %1 (%2)"), tr("Game Save %1 (Empty)"), false, static_cast<s32>(slot));
|
||||
add_slot(tr("Game Save %1 (%2)"), tr("Game Save %1 (Empty)"), game_serial, static_cast<s32>(slot));
|
||||
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
std::string_view empty_serial;
|
||||
for (u32 slot = 1; slot <= System::GLOBAL_SAVE_STATE_SLOTS; slot++)
|
||||
add_slot(tr("Global Save %1 (%2)"), tr("Global Save %1 (Empty)"), true, static_cast<s32>(slot));
|
||||
add_slot(tr("Global Save %1 (%2)"), tr("Global Save %1 (Empty)"), empty_serial, static_cast<s32>(slot));
|
||||
}
|
||||
|
||||
void MainWindow::populateSaveStateMenu(const char* game_serial, QMenu* menu)
|
||||
void MainWindow::populateSaveStateMenu(std::string_view game_serial, QMenu* menu)
|
||||
{
|
||||
auto add_slot = [game_serial, menu](const QString& title, const QString& empty_title, bool global, s32 slot) {
|
||||
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(global ? nullptr : game_serial, slot);
|
||||
auto add_slot = [menu](const QString& title, const QString& empty_title, const std::string_view& serial, s32 slot) {
|
||||
std::optional<SaveStateInfo> ssi = System::GetSaveStateInfo(serial, slot);
|
||||
|
||||
const QString menu_title =
|
||||
ssi.has_value() ? title.arg(slot).arg(FormatTimestampForSaveStateMenu(ssi->timestamp)) : empty_title.arg(slot);
|
||||
|
||||
QAction* save_action = menu->addAction(menu_title);
|
||||
connect(save_action, &QAction::triggered, [global, slot]() { g_emu_thread->saveState(global, slot); });
|
||||
connect(save_action, &QAction::triggered,
|
||||
[global = serial.empty(), slot]() { g_emu_thread->saveState(global, slot); });
|
||||
};
|
||||
|
||||
menu->clear();
|
||||
|
@ -952,16 +955,17 @@ void MainWindow::populateSaveStateMenu(const char* game_serial, QMenu* menu)
|
|||
});
|
||||
menu->addSeparator();
|
||||
|
||||
if (game_serial && std::strlen(game_serial) > 0)
|
||||
if (!game_serial.empty())
|
||||
{
|
||||
for (u32 slot = 1; slot <= System::PER_GAME_SAVE_STATE_SLOTS; slot++)
|
||||
add_slot(tr("Game Save %1 (%2)"), tr("Game Save %1 (Empty)"), false, static_cast<s32>(slot));
|
||||
add_slot(tr("Game Save %1 (%2)"), tr("Game Save %1 (Empty)"), game_serial, static_cast<s32>(slot));
|
||||
|
||||
menu->addSeparator();
|
||||
}
|
||||
|
||||
std::string_view empty_serial;
|
||||
for (u32 slot = 1; slot <= System::GLOBAL_SAVE_STATE_SLOTS; slot++)
|
||||
add_slot(tr("Global Save %1 (%2)"), tr("Global Save %1 (Empty)"), true, static_cast<s32>(slot));
|
||||
add_slot(tr("Global Save %1 (%2)"), tr("Global Save %1 (Empty)"), empty_serial, static_cast<s32>(slot));
|
||||
}
|
||||
|
||||
void MainWindow::populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group)
|
||||
|
@ -1238,12 +1242,12 @@ void MainWindow::onChangeDiscMenuAboutToHide()
|
|||
|
||||
void MainWindow::onLoadStateMenuAboutToShow()
|
||||
{
|
||||
populateLoadStateMenu(s_current_game_serial.toUtf8().constData(), m_ui.menuLoadState);
|
||||
populateLoadStateMenu(s_current_game_serial.toStdString(), m_ui.menuLoadState);
|
||||
}
|
||||
|
||||
void MainWindow::onSaveStateMenuAboutToShow()
|
||||
{
|
||||
populateSaveStateMenu(s_current_game_serial.toUtf8().constData(), m_ui.menuSaveState);
|
||||
populateSaveStateMenu(s_current_game_serial.toStdString(), m_ui.menuSaveState);
|
||||
}
|
||||
|
||||
void MainWindow::onStartFullscreenUITriggered()
|
||||
|
|
|
@ -268,8 +268,8 @@ private:
|
|||
/// Fills menu with save state info and handlers.
|
||||
void populateGameListContextMenu(const GameList::Entry* entry, QWidget* parent_window, QMenu* menu);
|
||||
|
||||
void populateLoadStateMenu(const char* game_serial, QMenu* menu);
|
||||
void populateSaveStateMenu(const char* game_serial, QMenu* menu);
|
||||
void populateLoadStateMenu(std::string_view game_serial, QMenu* menu);
|
||||
void populateSaveStateMenu(std::string_view game_serial, QMenu* menu);
|
||||
|
||||
/// Fills menu with the current playlist entries. The disc index is marked as checked.
|
||||
void populateChangeDiscSubImageMenu(QMenu* menu, QActionGroup* action_group);
|
||||
|
|
|
@ -223,10 +223,16 @@ bool QtHost::SaveGameSettings(SettingsInterface* sif, bool delete_if_empty)
|
|||
INISettingsInterface* ini = static_cast<INISettingsInterface*>(sif);
|
||||
Error error;
|
||||
|
||||
|
||||
// if there's no keys, just toss the whole thing out
|
||||
if (delete_if_empty && ini->IsEmpty())
|
||||
{
|
||||
INFO_LOG("Removing empty gamesettings ini {}", Path::GetFileName(ini->GetFileName()));
|
||||
|
||||
// grab the settings lock while we're writing the file, that way the CPU thread doesn't try
|
||||
// to read it at the same time.
|
||||
const auto lock = Host::GetSettingsLock();
|
||||
|
||||
if (FileSystem::FileExists(ini->GetFileName().c_str()) &&
|
||||
!FileSystem::DeleteFile(ini->GetFileName().c_str(), &error))
|
||||
{
|
||||
|
@ -243,6 +249,9 @@ bool QtHost::SaveGameSettings(SettingsInterface* sif, bool delete_if_empty)
|
|||
// clean unused sections, stops the file being bloated
|
||||
sif->RemoveEmptySections();
|
||||
|
||||
// see above
|
||||
const auto lock = Host::GetSettingsLock();
|
||||
|
||||
if (!sif->Save(&error))
|
||||
{
|
||||
Host::ReportErrorAsync(
|
||||
|
@ -1403,7 +1412,7 @@ void EmuThread::saveState(const QString& filename, bool block_until_done /* = fa
|
|||
return;
|
||||
|
||||
Error error;
|
||||
if (!System::SaveState(filename.toUtf8().data(), &error, g_settings.create_save_state_backups))
|
||||
if (!System::SaveState(filename.toStdString(), &error, g_settings.create_save_state_backups, false))
|
||||
emit errorReported(tr("Error"), tr("Failed to save state: %1").arg(QString::fromStdString(error.GetDescription())));
|
||||
}
|
||||
|
||||
|
@ -1423,7 +1432,7 @@ void EmuThread::saveState(bool global, qint32 slot, bool block_until_done /* = f
|
|||
if (!System::SaveState((global ? System::GetGlobalSaveStateFileName(slot) :
|
||||
System::GetGameSaveStateFileName(System::GetGameSerial(), slot))
|
||||
.c_str(),
|
||||
&error, g_settings.create_save_state_backups))
|
||||
&error, g_settings.create_save_state_backups, false))
|
||||
{
|
||||
emit errorReported(tr("Error"), tr("Failed to save state: %1").arg(QString::fromStdString(error.GetDescription())));
|
||||
}
|
||||
|
@ -1908,6 +1917,18 @@ bool Host::ConfirmMessage(std::string_view title, std::string_view message)
|
|||
QString::fromUtf8(message.data(), message.size()));
|
||||
}
|
||||
|
||||
void Host::ConfirmMessageAsync(std::string_view title, std::string_view message, ConfirmMessageAsyncCallback callback)
|
||||
{
|
||||
QtHost::RunOnUIThread([title = QtUtils::StringViewToQString(title), message = QtUtils::StringViewToQString(message),
|
||||
callback = std::move(callback)]() mutable {
|
||||
auto lock = g_main_window->pauseAndLockSystem();
|
||||
|
||||
const bool result = (QMessageBox::question(lock.getDialogParent(), title, message) != QMessageBox::No);
|
||||
|
||||
callback(result);
|
||||
});
|
||||
}
|
||||
|
||||
void Host::OpenURL(std::string_view url)
|
||||
{
|
||||
QtHost::RunOnUIThread([url = QtUtils::StringViewToQString(url)]() { QtUtils::OpenURL(g_main_window, QUrl(url)); });
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -150,6 +150,16 @@ bool Host::ConfirmMessage(std::string_view title, std::string_view message)
|
|||
return true;
|
||||
}
|
||||
|
||||
void Host::ConfirmMessageAsync(std::string_view title, std::string_view message, ConfirmMessageAsyncCallback callback)
|
||||
{
|
||||
if (!title.empty() && !message.empty())
|
||||
ERROR_LOG("ConfirmMessage: {}: {}", title, message);
|
||||
else if (!message.empty())
|
||||
ERROR_LOG("ConfirmMessage: {}", message);
|
||||
|
||||
callback(true);
|
||||
}
|
||||
|
||||
void Host::ReportDebuggerMessage(std::string_view message)
|
||||
{
|
||||
ERROR_LOG("ReportDebuggerMessage: {}", message);
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
@echo off
|
||||
|
||||
SET VERSIONFILE="scmversion.cpp"
|
||||
PUSHD %~dp0
|
||||
FOR /F "tokens=* USEBACKQ" %%g IN (`git rev-parse HEAD`) do (SET "HASH=%%g")
|
||||
FOR /F "tokens=* USEBACKQ" %%g IN (`git rev-parse --abbrev-ref HEAD`) do (SET "BRANCH=%%g")
|
||||
FOR /F "tokens=* USEBACKQ" %%g IN (`git describe --dirty`) do (SET "TAG=%%g")
|
||||
FOR /F "tokens=* USEBACKQ" %%g IN (`git log -1 --date=iso8601-strict "--format=%%cd"`) do (SET "CDATE=%%g")
|
||||
POPD
|
||||
|
||||
SET SIGNATURELINE=// %HASH% %BRANCH% %TAG% %CDATE%
|
||||
SET /P EXISTINGLINE=< %VERSIONFILE%
|
||||
|
|
|
@ -475,8 +475,9 @@ std::optional<DynamicHeapArray<u8>> D3DCommon::CompileShaderWithFXC(u32 shader_m
|
|||
return {};
|
||||
}
|
||||
|
||||
static constexpr UINT flags_non_debug = D3DCOMPILE_OPTIMIZATION_LEVEL3;
|
||||
static constexpr UINT flags_debug = D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_DEBUG;
|
||||
static constexpr UINT flags_non_debug = D3DCOMPILE_PACK_MATRIX_ROW_MAJOR | D3DCOMPILE_OPTIMIZATION_LEVEL3;
|
||||
static constexpr UINT flags_debug =
|
||||
D3DCOMPILE_PACK_MATRIX_ROW_MAJOR | D3DCOMPILE_SKIP_OPTIMIZATION | D3DCOMPILE_DEBUG;
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3DBlob> blob;
|
||||
Microsoft::WRL::ComPtr<ID3DBlob> error_blob;
|
||||
|
@ -556,12 +557,14 @@ std::optional<DynamicHeapArray<u8>> D3DCommon::CompileShaderWithDXC(u32 shader_m
|
|||
static constexpr const wchar_t* nondebug_arguments[] = {
|
||||
L"-Qstrip_reflect",
|
||||
L"-Qstrip_debug",
|
||||
DXC_ARG_PACK_MATRIX_ROW_MAJOR,
|
||||
DXC_ARG_OPTIMIZATION_LEVEL3,
|
||||
};
|
||||
static constexpr const wchar_t* debug_arguments[] = {
|
||||
L"-Qstrip_reflect",
|
||||
DXC_ARG_DEBUG,
|
||||
L"-Qembed_debug",
|
||||
DXC_ARG_PACK_MATRIX_ROW_MAJOR,
|
||||
DXC_ARG_SKIP_OPTIMIZATIONS,
|
||||
};
|
||||
const wchar_t* const* arguments = debug_device ? debug_arguments : nondebug_arguments;
|
||||
|
@ -630,6 +633,7 @@ static constexpr std::array<D3DCommon::DXGIFormatMapping, static_cast<int>(GPUTe
|
|||
{DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_B8G8R8A8_UNORM, DXGI_FORMAT_UNKNOWN }, // BGRA8
|
||||
{DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_B5G6R5_UNORM, DXGI_FORMAT_UNKNOWN }, // RGB565
|
||||
{DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_B5G5R5A1_UNORM, DXGI_FORMAT_UNKNOWN }, // RGB5A1
|
||||
{DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // A1BGR5
|
||||
{DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_R8_UNORM, DXGI_FORMAT_UNKNOWN }, // R8
|
||||
{DXGI_FORMAT_R16_TYPELESS, DXGI_FORMAT_R16_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_D16_UNORM }, // D16
|
||||
{DXGI_FORMAT_R24G8_TYPELESS, DXGI_FORMAT_R24_UNORM_X8_TYPELESS, DXGI_FORMAT_R24_UNORM_X8_TYPELESS, DXGI_FORMAT_D24_UNORM_S8_UINT }, // D24S8
|
||||
|
|
|
@ -233,6 +233,47 @@ GPUSwapChain::GPUSwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool a
|
|||
|
||||
GPUSwapChain::~GPUSwapChain() = default;
|
||||
|
||||
GSVector4i GPUSwapChain::PreRotateClipRect(const GSVector4i& v)
|
||||
{
|
||||
GSVector4i new_clip;
|
||||
switch (m_window_info.surface_prerotation)
|
||||
{
|
||||
case WindowInfo::PreRotation::Identity:
|
||||
new_clip = v;
|
||||
break;
|
||||
|
||||
case WindowInfo::PreRotation::Rotate90Clockwise:
|
||||
{
|
||||
const s32 height = (v.w - v.y);
|
||||
const s32 y = m_window_info.surface_height - v.y - height;
|
||||
new_clip = GSVector4i(y, v.x, y + height, v.z);
|
||||
}
|
||||
break;
|
||||
|
||||
case WindowInfo::PreRotation::Rotate180Clockwise:
|
||||
{
|
||||
const s32 width = (v.z - v.x);
|
||||
const s32 height = (v.w - v.y);
|
||||
const s32 x = m_window_info.surface_width - v.x - width;
|
||||
const s32 y = m_window_info.surface_height - v.y - height;
|
||||
new_clip = GSVector4i(x, y, x + width, y + height);
|
||||
}
|
||||
break;
|
||||
|
||||
case WindowInfo::PreRotation::Rotate270Clockwise:
|
||||
{
|
||||
const s32 width = (v.z - v.x);
|
||||
const s32 x = m_window_info.surface_width - v.x - width;
|
||||
new_clip = GSVector4i(v.y, x, v.w, x + width);
|
||||
}
|
||||
break;
|
||||
|
||||
DefaultCaseIsUnreachable()
|
||||
}
|
||||
|
||||
return new_clip;
|
||||
}
|
||||
|
||||
bool GPUSwapChain::ShouldSkipPresentingFrame()
|
||||
{
|
||||
// Only needed with FIFO. But since we're so fast, we allow it always.
|
||||
|
@ -674,20 +715,18 @@ void GPUDevice::RenderImGui(GPUSwapChain* swap_chain)
|
|||
if (draw_data->CmdListsCount == 0 || !swap_chain)
|
||||
return;
|
||||
|
||||
const s32 post_rotated_height = swap_chain->GetPostRotatedHeight();
|
||||
SetPipeline(m_imgui_pipeline.get());
|
||||
SetViewportAndScissor(0, 0, swap_chain->GetWidth(), swap_chain->GetHeight());
|
||||
SetViewport(0, 0, swap_chain->GetPostRotatedWidth(), post_rotated_height);
|
||||
|
||||
const float L = 0.0f;
|
||||
const float R = static_cast<float>(swap_chain->GetWidth());
|
||||
const float T = 0.0f;
|
||||
const float B = static_cast<float>(swap_chain->GetHeight());
|
||||
const float ortho_projection[4][4] = {
|
||||
{2.0f / (R - L), 0.0f, 0.0f, 0.0f},
|
||||
{0.0f, 2.0f / (T - B), 0.0f, 0.0f},
|
||||
{0.0f, 0.0f, 0.5f, 0.0f},
|
||||
{(R + L) / (L - R), (T + B) / (B - T), 0.5f, 1.0f},
|
||||
};
|
||||
PushUniformBuffer(ortho_projection, sizeof(ortho_projection));
|
||||
GSMatrix4x4 mproj = GSMatrix4x4::OffCenterOrthographicProjection(
|
||||
0.0f, 0.0f, static_cast<float>(swap_chain->GetWidth()), static_cast<float>(swap_chain->GetHeight()), 0.0f, 1.0f);
|
||||
if (swap_chain->GetPreRotation() != WindowInfo::PreRotation::Identity)
|
||||
{
|
||||
mproj =
|
||||
GSMatrix4x4::RotationZ(WindowInfo::GetZRotationForPreRotation(swap_chain->GetPreRotation())) * mproj;
|
||||
}
|
||||
PushUniformBuffer(&mproj, sizeof(mproj));
|
||||
|
||||
// Render command lists
|
||||
const bool flip = UsesLowerLeftOrigin();
|
||||
|
@ -708,20 +747,12 @@ void GPUDevice::RenderImGui(GPUSwapChain* swap_chain)
|
|||
if (pcmd->ElemCount == 0 || pcmd->ClipRect.z <= pcmd->ClipRect.x || pcmd->ClipRect.w <= pcmd->ClipRect.y)
|
||||
continue;
|
||||
|
||||
GSVector4i clip = GSVector4i(GSVector4::load<false>(&pcmd->ClipRect.x));
|
||||
clip = swap_chain->PreRotateClipRect(clip);
|
||||
if (flip)
|
||||
{
|
||||
const s32 height = static_cast<s32>(pcmd->ClipRect.w - pcmd->ClipRect.y);
|
||||
const s32 flipped_y = static_cast<s32>(swap_chain->GetHeight()) - static_cast<s32>(pcmd->ClipRect.y) - height;
|
||||
SetScissor(static_cast<s32>(pcmd->ClipRect.x), flipped_y, static_cast<s32>(pcmd->ClipRect.z - pcmd->ClipRect.x),
|
||||
height);
|
||||
}
|
||||
else
|
||||
{
|
||||
SetScissor(static_cast<s32>(pcmd->ClipRect.x), static_cast<s32>(pcmd->ClipRect.y),
|
||||
static_cast<s32>(pcmd->ClipRect.z - pcmd->ClipRect.x),
|
||||
static_cast<s32>(pcmd->ClipRect.w - pcmd->ClipRect.y));
|
||||
}
|
||||
clip = FlipToLowerLeft(clip, post_rotated_height);
|
||||
|
||||
SetScissor(clip);
|
||||
SetTextureSampler(0, reinterpret_cast<GPUTexture*>(pcmd->TextureId), m_linear_sampler.get());
|
||||
DrawIndexed(pcmd->ElemCount, base_index + pcmd->IdxOffset, base_vertex + pcmd->VtxOffset);
|
||||
}
|
||||
|
@ -1041,7 +1072,7 @@ std::unique_ptr<GPUTexture> GPUDevice::FetchTexture(u32 width, u32 height, u32 l
|
|||
return ret;
|
||||
}
|
||||
|
||||
std::unique_ptr<GPUTexture, GPUDevice::PooledTextureDeleter>
|
||||
GPUDevice::AutoRecycleTexture
|
||||
GPUDevice::FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type,
|
||||
GPUTexture::Format format, GPUTexture::Flags flags, const void* data /* = nullptr */,
|
||||
u32 data_stride /* = 0 */, Error* error /* = nullptr */)
|
||||
|
@ -1396,7 +1427,7 @@ bool dyn_libs::OpenSpirvCross(Error* error)
|
|||
if (s_spirv_cross_library.IsOpen())
|
||||
return true;
|
||||
|
||||
#ifdef _WIN32
|
||||
#if defined(_WIN32) || defined(__ANDROID__)
|
||||
// SPVC's build on Windows doesn't spit out a versioned DLL.
|
||||
const std::string libname = DynamicLibrary::GetVersionedFilename("spirv-cross-c-shared");
|
||||
#else
|
||||
|
|
|
@ -507,7 +507,10 @@ public:
|
|||
ALWAYS_INLINE const WindowInfo& GetWindowInfo() const { return m_window_info; }
|
||||
ALWAYS_INLINE u32 GetWidth() const { return m_window_info.surface_width; }
|
||||
ALWAYS_INLINE u32 GetHeight() const { return m_window_info.surface_height; }
|
||||
ALWAYS_INLINE u32 GetPostRotatedWidth() const { return m_window_info.GetPostRotatedWidth(); }
|
||||
ALWAYS_INLINE u32 GetPostRotatedHeight() const { return m_window_info.GetPostRotatedHeight(); }
|
||||
ALWAYS_INLINE float GetScale() const { return m_window_info.surface_scale; }
|
||||
ALWAYS_INLINE WindowInfo::PreRotation GetPreRotation() const { return m_window_info.surface_prerotation; }
|
||||
ALWAYS_INLINE GPUTexture::Format GetFormat() const { return m_window_info.surface_format; }
|
||||
|
||||
ALWAYS_INLINE GPUVSyncMode GetVSyncMode() const { return m_vsync_mode; }
|
||||
|
@ -517,6 +520,8 @@ public:
|
|||
virtual bool ResizeBuffers(u32 new_width, u32 new_height, float new_scale, Error* error) = 0;
|
||||
virtual bool SetVSyncMode(GPUVSyncMode mode, bool allow_present_throttle, Error* error) = 0;
|
||||
|
||||
GSVector4i PreRotateClipRect(const GSVector4i& v);
|
||||
|
||||
bool ShouldSkipPresentingFrame();
|
||||
void ThrottlePresentation();
|
||||
|
||||
|
@ -630,6 +635,7 @@ public:
|
|||
{
|
||||
void operator()(GPUTexture* const tex);
|
||||
};
|
||||
using AutoRecycleTexture = std::unique_ptr<GPUTexture, PooledTextureDeleter>;
|
||||
|
||||
static constexpr u32 MAX_TEXTURE_SAMPLERS = 8;
|
||||
static constexpr u32 MIN_TEXEL_BUFFER_ELEMENTS = 4 * 1024 * 512;
|
||||
|
@ -742,10 +748,9 @@ public:
|
|||
std::unique_ptr<GPUTexture> FetchTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags,
|
||||
const void* data = nullptr, u32 data_stride = 0, Error* error = nullptr);
|
||||
std::unique_ptr<GPUTexture, PooledTextureDeleter>
|
||||
FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type,
|
||||
GPUTexture::Format format, GPUTexture::Flags flags, const void* data = nullptr,
|
||||
u32 data_stride = 0, Error* error = nullptr);
|
||||
AutoRecycleTexture FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags,
|
||||
const void* data = nullptr, u32 data_stride = 0, Error* error = nullptr);
|
||||
std::unique_ptr<GPUTexture> FetchAndUploadTextureImage(const Image& image,
|
||||
GPUTexture::Flags flags = GPUTexture::Flags::None,
|
||||
Error* error = nullptr);
|
||||
|
|
|
@ -31,6 +31,7 @@ const char* GPUTexture::GetFormatName(Format format)
|
|||
"BGRA8", // BGRA8
|
||||
"RGB565", // RGB565
|
||||
"RGB5A1", // RGB5A1
|
||||
"A1BGR5", // A1BGR5
|
||||
"R8", // R8
|
||||
"D16", // D16
|
||||
"D24S8", // D24S8
|
||||
|
@ -152,6 +153,7 @@ GPUTexture::Format GPUTexture::GetTextureFormatForImageFormat(ImageFormat format
|
|||
Format::BGRA8, // BGRA8
|
||||
Format::RGB565, // RGB565
|
||||
Format::RGB5A1, // RGB5A1
|
||||
Format::A1BGR5, // A1BGR5
|
||||
Format::Unknown, // BGR8
|
||||
Format::BC1, // BC1
|
||||
Format::BC2, // BC2
|
||||
|
@ -171,6 +173,7 @@ ImageFormat GPUTexture::GetImageFormatForTextureFormat(Format format)
|
|||
ImageFormat::BGRA8, // BGRA8
|
||||
ImageFormat::RGB565, // RGB565
|
||||
ImageFormat::RGB5A1, // RGB5A1
|
||||
ImageFormat::A1BGR5, // A1BGR5
|
||||
ImageFormat::None, // R8
|
||||
ImageFormat::None, // D16
|
||||
ImageFormat::None, // D24S8
|
||||
|
@ -253,6 +256,7 @@ u32 GPUTexture::GetPixelSize(GPUTexture::Format format)
|
|||
4, // BGRA8
|
||||
2, // RGB565
|
||||
2, // RGB5A1
|
||||
2, // A1BGR5
|
||||
1, // R8
|
||||
2, // D16
|
||||
4, // D24S8
|
||||
|
|
|
@ -44,6 +44,7 @@ public:
|
|||
BGRA8,
|
||||
RGB565,
|
||||
RGB5A1,
|
||||
A1BGR5,
|
||||
R8,
|
||||
D16,
|
||||
D24S8,
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "common/types.h"
|
||||
|
||||
#include <ctime>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
@ -44,6 +45,11 @@ void ReportErrorAsync(std::string_view title, std::string_view message);
|
|||
/// Displays a synchronous confirmation on the UI thread, i.e. blocks the caller.
|
||||
bool ConfirmMessage(std::string_view title, std::string_view message);
|
||||
|
||||
/// Displays an asynchronous confirmation on the UI thread, but does not block the caller.
|
||||
/// The callback may be executed on a different thread. Use RunOnCPUThread() in the callback to ensure safety.
|
||||
using ConfirmMessageAsyncCallback = std::function<void(bool)>;
|
||||
void ConfirmMessageAsync(std::string_view title, std::string_view message, ConfirmMessageAsyncCallback callback);
|
||||
|
||||
/// Returns the user agent to use for HTTP requests.
|
||||
std::string GetHTTPUserAgent();
|
||||
|
||||
|
|
|
@ -160,8 +160,11 @@ void HTTPDownloader::LockedPollRequests(std::unique_lock<std::mutex>& lock)
|
|||
|
||||
// run callback with lock unheld
|
||||
lock.unlock();
|
||||
if (req->status_code != HTTP_STATUS_OK)
|
||||
if (req->status_code >= 0 && req->status_code != HTTP_STATUS_OK)
|
||||
req->error.SetStringFmt("Request failed with HTTP status code {}", req->status_code);
|
||||
else if (req->status_code < 0)
|
||||
DEV_LOG("Request failed with error {}", req->error.GetDescription());
|
||||
|
||||
req->callback(req->status_code, req->error, req->content_type, std::move(req->data));
|
||||
CloseRequest(req);
|
||||
lock.lock();
|
||||
|
|
|
@ -168,6 +168,7 @@ const char* Image::GetFormatName(ImageFormat format)
|
|||
"BGRA8", // BGRA8
|
||||
"RGB565", // RGB565
|
||||
"RGB5A1", // RGB5A1
|
||||
"A1BGR5", // A1BGR5
|
||||
"BGR8", // BGR8
|
||||
"BC1", // BC1
|
||||
"BC2", // BC2
|
||||
|
@ -187,6 +188,7 @@ u32 Image::GetPixelSize(ImageFormat format)
|
|||
4, // BGRA8
|
||||
2, // RGB565
|
||||
2, // RGB5A1
|
||||
2, // A1BGR5
|
||||
3, // BGR8
|
||||
8, // BC1 - 16 pixels in 64 bits
|
||||
16, // BC2 - 16 pixels in 128 bits
|
||||
|
|
|
@ -21,6 +21,7 @@ enum class ImageFormat : u8
|
|||
BGRA8,
|
||||
RGB565,
|
||||
RGB5A1,
|
||||
A1BGR5,
|
||||
BGR8,
|
||||
BC1,
|
||||
BC2,
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -43,57 +43,62 @@ static constexpr float LAYOUT_HORIZONTAL_MENU_HEIGHT = 320.0f;
|
|||
static constexpr float LAYOUT_HORIZONTAL_MENU_PADDING = 30.0f;
|
||||
static constexpr float LAYOUT_HORIZONTAL_MENU_ITEM_WIDTH = 250.0f;
|
||||
|
||||
extern ImFont* g_medium_font;
|
||||
extern ImFont* g_large_font;
|
||||
struct ALIGN_TO_CACHE_LINE UIStyles
|
||||
{
|
||||
ImVec4 BackgroundColor;
|
||||
ImVec4 BackgroundTextColor;
|
||||
ImVec4 BackgroundLineColor;
|
||||
ImVec4 BackgroundHighlight;
|
||||
ImVec4 PopupBackgroundColor;
|
||||
ImVec4 DisabledColor;
|
||||
ImVec4 PrimaryColor;
|
||||
ImVec4 PrimaryLightColor;
|
||||
ImVec4 PrimaryDarkColor;
|
||||
ImVec4 PrimaryTextColor;
|
||||
ImVec4 TextHighlightColor;
|
||||
ImVec4 PrimaryLineColor;
|
||||
ImVec4 SecondaryColor;
|
||||
ImVec4 SecondaryWeakColor; // Not currently used.
|
||||
ImVec4 SecondaryStrongColor;
|
||||
ImVec4 SecondaryTextColor;
|
||||
|
||||
extern float g_layout_scale;
|
||||
extern float g_rcp_layout_scale;
|
||||
extern float g_layout_padding_left;
|
||||
extern float g_layout_padding_top;
|
||||
ImFont* MediumFont;
|
||||
ImFont* LargeFont;
|
||||
|
||||
extern ImVec4 UIBackgroundColor;
|
||||
extern ImVec4 UIBackgroundTextColor;
|
||||
extern ImVec4 UIBackgroundLineColor;
|
||||
extern ImVec4 UIBackgroundHighlightColor;
|
||||
extern ImVec4 UIPopupBackgroundColor;
|
||||
extern ImVec4 UIDisabledColor;
|
||||
extern ImVec4 UIPrimaryColor;
|
||||
extern ImVec4 UIPrimaryLightColor;
|
||||
extern ImVec4 UIPrimaryDarkColor;
|
||||
extern ImVec4 UIPrimaryTextColor;
|
||||
extern ImVec4 UITextHighlightColor;
|
||||
extern ImVec4 UIPrimaryLineColor;
|
||||
extern ImVec4 UISecondaryColor;
|
||||
extern ImVec4 UISecondaryWeakColor; // Not currently used.
|
||||
extern ImVec4 UISecondaryStrongColor;
|
||||
extern ImVec4 UISecondaryTextColor;
|
||||
float LayoutScale;
|
||||
float RcpLayoutScale;
|
||||
float LayoutPaddingLeft;
|
||||
float LayoutPaddingTop;
|
||||
};
|
||||
|
||||
extern UIStyles UIStyle;
|
||||
|
||||
ALWAYS_INLINE static float LayoutScale(float v)
|
||||
{
|
||||
return ImCeil(g_layout_scale * v);
|
||||
return ImCeil(UIStyle.LayoutScale * v);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static ImVec2 LayoutScale(const ImVec2& v)
|
||||
{
|
||||
return ImVec2(ImCeil(v.x * g_layout_scale), ImCeil(v.y * g_layout_scale));
|
||||
return ImVec2(ImCeil(v.x * UIStyle.LayoutScale), ImCeil(v.y * UIStyle.LayoutScale));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static ImVec2 LayoutScale(float x, float y)
|
||||
{
|
||||
return ImVec2(ImCeil(x * g_layout_scale), ImCeil(y * g_layout_scale));
|
||||
return ImVec2(ImCeil(x * UIStyle.LayoutScale), ImCeil(y * UIStyle.LayoutScale));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static float LayoutUnscale(float v)
|
||||
{
|
||||
return ImCeil(g_rcp_layout_scale * v);
|
||||
return ImCeil(UIStyle.RcpLayoutScale * v);
|
||||
}
|
||||
ALWAYS_INLINE static ImVec2 LayoutUnscale(const ImVec2& v)
|
||||
{
|
||||
return ImVec2(ImCeil(v.x * g_rcp_layout_scale), ImCeil(v.y * g_rcp_layout_scale));
|
||||
return ImVec2(ImCeil(v.x * UIStyle.RcpLayoutScale), ImCeil(v.y * UIStyle.RcpLayoutScale));
|
||||
}
|
||||
ALWAYS_INLINE static ImVec2 LayoutUnscale(float x, float y)
|
||||
{
|
||||
return ImVec2(ImCeil(x * g_rcp_layout_scale), ImCeil(y * g_rcp_layout_scale));
|
||||
return ImVec2(ImCeil(x * UIStyle.RcpLayoutScale), ImCeil(y * UIStyle.RcpLayoutScale));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static ImVec4 ModAlpha(const ImVec4& v, float a)
|
||||
|
@ -170,7 +175,7 @@ bool BeginFullscreenColumns(const char* title = nullptr, float pos_y = 0.0f, boo
|
|||
void EndFullscreenColumns();
|
||||
|
||||
bool BeginFullscreenColumnWindow(float start, float end, const char* name,
|
||||
const ImVec4& background = UIBackgroundColor);
|
||||
const ImVec4& background = UIStyle.BackgroundColor);
|
||||
void EndFullscreenColumnWindow();
|
||||
|
||||
bool BeginFullscreenWindow(float left, float top, float width, float height, const char* name,
|
||||
|
@ -200,39 +205,41 @@ void ResetMenuButtonFrame();
|
|||
void MenuHeading(const char* title, bool draw_line = true);
|
||||
bool MenuHeadingButton(const char* title, const char* value = nullptr, bool enabled = true, bool draw_line = true);
|
||||
bool ActiveButton(const char* title, bool is_active, bool enabled = true,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = UIStyle.LargeFont);
|
||||
bool DefaultActiveButton(const char* title, bool is_active, bool enabled = true,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = UIStyle.LargeFont);
|
||||
bool ActiveButtonWithRightText(const char* title, const char* right_title, bool is_active, bool enabled = true,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = UIStyle.LargeFont);
|
||||
bool MenuButton(const char* title, const char* summary, bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
|
||||
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
|
||||
ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont);
|
||||
bool MenuButtonWithoutSummary(const char* title, bool enabled = true,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = UIStyle.LargeFont,
|
||||
const ImVec2& text_align = ImVec2(0.0f, 0.0f));
|
||||
bool MenuButtonWithValue(const char* title, const char* summary, const char* value, bool enabled = true,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
|
||||
ImFont* summary_font = g_medium_font);
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont,
|
||||
ImFont* summary_font = UIStyle.MediumFont);
|
||||
bool MenuImageButton(const char* title, const char* summary, ImTextureID user_texture_id, const ImVec2& image_size,
|
||||
bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
|
||||
const ImVec2& uv0 = ImVec2(0.0f, 0.0f), const ImVec2& uv1 = ImVec2(1.0f, 1.0f),
|
||||
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font);
|
||||
ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont);
|
||||
bool FloatingButton(const char* text, float x, float y, float width = -1.0f,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, float anchor_x = 0.0f, float anchor_y = 0.0f,
|
||||
bool enabled = true, ImFont* font = g_large_font, ImVec2* out_position = nullptr,
|
||||
bool enabled = true, ImFont* font = UIStyle.LargeFont, ImVec2* out_position = nullptr,
|
||||
bool repeat_button = false);
|
||||
bool ToggleButton(const char* title, const char* summary, bool* v, bool enabled = true,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
|
||||
ImFont* summary_font = g_medium_font);
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont,
|
||||
ImFont* summary_font = UIStyle.MediumFont);
|
||||
bool ThreeWayToggleButton(const char* title, const char* summary, std::optional<bool>* v, bool enabled = true,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = g_large_font,
|
||||
ImFont* summary_font = g_medium_font);
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT, ImFont* font = UIStyle.LargeFont,
|
||||
ImFont* summary_font = UIStyle.MediumFont);
|
||||
bool RangeButton(const char* title, const char* summary, s32* value, s32 min, s32 max, s32 increment,
|
||||
const char* format = "%d", bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
|
||||
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font, const char* ok_text = "OK");
|
||||
ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont,
|
||||
const char* ok_text = "OK");
|
||||
bool RangeButton(const char* title, const char* summary, float* value, float min, float max, float increment,
|
||||
const char* format = "%f", bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
|
||||
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font, const char* ok_text = "OK");
|
||||
ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont,
|
||||
const char* ok_text = "OK");
|
||||
bool EnumChoiceButtonImpl(const char* title, const char* summary, s32* value_pointer,
|
||||
const char* (*to_display_name_function)(s32 value, void* opaque), void* opaque, u32 count,
|
||||
bool enabled, float height, ImFont* font, ImFont* summary_font);
|
||||
|
@ -241,7 +248,7 @@ template<typename DataType, typename CountType>
|
|||
ALWAYS_INLINE static bool EnumChoiceButton(const char* title, const char* summary, DataType* value_pointer,
|
||||
const char* (*to_display_name_function)(DataType value), CountType count,
|
||||
bool enabled = true, float height = LAYOUT_MENU_BUTTON_HEIGHT,
|
||||
ImFont* font = g_large_font, ImFont* summary_font = g_medium_font)
|
||||
ImFont* font = UIStyle.LargeFont, ImFont* summary_font = UIStyle.MediumFont)
|
||||
{
|
||||
s32 value = static_cast<s32>(*value_pointer);
|
||||
auto to_display_name_wrapper = [](s32 value, void* opaque) -> const char* {
|
||||
|
@ -265,13 +272,13 @@ void DrawShadowedText(ImDrawList* dl, ImFont* font, const ImVec2& pos, u32 col,
|
|||
|
||||
void BeginNavBar(float x_padding = LAYOUT_MENU_BUTTON_X_PADDING, float y_padding = LAYOUT_MENU_BUTTON_Y_PADDING);
|
||||
void EndNavBar();
|
||||
void NavTitle(const char* title, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
|
||||
void NavTitle(const char* title, float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = UIStyle.LargeFont);
|
||||
void RightAlignNavButtons(u32 num_items = 0, float item_width = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY,
|
||||
float item_height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY);
|
||||
bool NavButton(const char* title, bool is_active, bool enabled = true, float width = -1.0f,
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = g_large_font);
|
||||
float height = LAYOUT_MENU_BUTTON_HEIGHT_NO_SUMMARY, ImFont* font = UIStyle.LargeFont);
|
||||
bool NavTab(const char* title, bool is_active, bool enabled, float width, float height, const ImVec4& background,
|
||||
ImFont* font = g_large_font);
|
||||
ImFont* font = UIStyle.LargeFont);
|
||||
|
||||
bool BeginHorizontalMenu(const char* name, const ImVec2& position, const ImVec2& size, u32 num_items);
|
||||
void EndHorizontalMenu();
|
||||
|
|
|
@ -54,7 +54,11 @@ namespace ImGuiManager {
|
|||
using WCharType = u32;
|
||||
|
||||
/// Default size for screen margins.
|
||||
#ifndef __ANDROID__
|
||||
static constexpr float DEFAULT_SCREEN_MARGIN = 10.0f;
|
||||
#else
|
||||
static constexpr float DEFAULT_SCREEN_MARGIN = 16.0f;
|
||||
#endif
|
||||
|
||||
/// Sets the path to the font to use. Empty string means to use the default.
|
||||
void SetFontPathAndRange(std::string path, std::vector<WCharType> range);
|
||||
|
|
|
@ -51,6 +51,7 @@ static constexpr std::array<MTLPixelFormat, static_cast<u32>(GPUTexture::Format:
|
|||
MTLPixelFormatBGRA8Unorm, // BGRA8
|
||||
MTLPixelFormatB5G6R5Unorm, // RGB565
|
||||
MTLPixelFormatBGR5A1Unorm, // RGB5A1
|
||||
MTLPixelFormatInvalid, // A1BGR5
|
||||
MTLPixelFormatR8Unorm, // R8
|
||||
MTLPixelFormatDepth16Unorm, // D16
|
||||
MTLPixelFormatDepth24Unorm_Stencil8, // D24S8
|
||||
|
|
|
@ -443,6 +443,9 @@ void OpenGLContextEGL::UpdateWindowInfoSize(WindowInfo& wi, EGLSurface surface)
|
|||
{
|
||||
wi.surface_width = static_cast<u16>(surface_width);
|
||||
wi.surface_height = static_cast<u16>(surface_height);
|
||||
|
||||
if (WindowInfo::ShouldSwapDimensionsForPreRotation(wi.surface_prerotation))
|
||||
std::swap(wi.surface_width, wi.surface_height);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
|
@ -17,12 +17,6 @@
|
|||
#include <string_view>
|
||||
#include <tuple>
|
||||
|
||||
// Unix doesn't prevent concurrent write access, need to explicitly lock the pipeline cache.
|
||||
// Don't worry about Android, it's not like you can run one more than one instance of the app there...
|
||||
#if !defined(_WIN32) && !defined(__ANDROID__)
|
||||
#define OPENGL_PIPELINE_CACHE_NEEDS_LOCK 1
|
||||
#endif
|
||||
|
||||
class OpenGLPipeline;
|
||||
class OpenGLStreamBuffer;
|
||||
class OpenGLTexture;
|
||||
|
@ -239,7 +233,7 @@ private:
|
|||
bool m_timestamp_query_started = false;
|
||||
|
||||
std::FILE* m_pipeline_disk_cache_file = nullptr;
|
||||
#ifdef OPENGL_PIPELINE_CACHE_NEEDS_LOCK
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
FileSystem::POSIXLock m_pipeline_disk_cache_file_lock;
|
||||
#endif
|
||||
u32 m_pipeline_disk_cache_data_end = 0;
|
||||
|
|
|
@ -765,9 +765,9 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error)
|
|||
if (!fp)
|
||||
return false;
|
||||
|
||||
#ifdef OPENGL_PIPELINE_CACHE_NEEDS_LOCK
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
// Unix doesn't prevent concurrent write access, need to explicitly lock it.
|
||||
FileSystem::POSIXLock fp_lock(fp.get(), true, error);
|
||||
FileSystem::POSIXLock fp_lock(fp.get(), false, error);
|
||||
if (!fp_lock.IsLocked())
|
||||
{
|
||||
Error::AddPrefix(error, "Failed to lock cache file: ");
|
||||
|
@ -847,7 +847,7 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error)
|
|||
|
||||
VERBOSE_LOG("Read {} programs from disk cache.", m_program_cache.size());
|
||||
m_pipeline_disk_cache_file = fp.release();
|
||||
#ifdef OPENGL_PIPELINE_CACHE_NEEDS_LOCK
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
m_pipeline_disk_cache_file_lock = std::move(fp_lock);
|
||||
#endif
|
||||
return true;
|
||||
|
@ -855,7 +855,7 @@ bool OpenGLDevice::OpenPipelineCache(const std::string& path, Error* error)
|
|||
|
||||
bool OpenGLDevice::CreatePipelineCache(const std::string& path, Error* error)
|
||||
{
|
||||
#ifndef OPENGL_PIPELINE_CACHE_NEEDS_LOCK
|
||||
#ifndef HAS_POSIX_FILE_LOCK
|
||||
m_pipeline_disk_cache_file = FileSystem::OpenCFile(path.c_str(), "w+b", error);
|
||||
if (!m_pipeline_disk_cache_file)
|
||||
return false;
|
||||
|
@ -865,7 +865,7 @@ bool OpenGLDevice::CreatePipelineCache(const std::string& path, Error* error)
|
|||
if (!m_pipeline_disk_cache_file || !FileSystem::FSeek64(m_pipeline_disk_cache_file, 0, SEEK_SET, error))
|
||||
return false;
|
||||
|
||||
m_pipeline_disk_cache_file_lock = FileSystem::POSIXLock(m_pipeline_disk_cache_file, true, error);
|
||||
m_pipeline_disk_cache_file_lock = FileSystem::POSIXLock(m_pipeline_disk_cache_file, false, error);
|
||||
if (!m_pipeline_disk_cache_file_lock.IsLocked())
|
||||
{
|
||||
Error::AddPrefix(error, "Failed to lock cache file: ");
|
||||
|
@ -1015,7 +1015,7 @@ bool OpenGLDevice::DiscardPipelineCache()
|
|||
if (!FileSystem::FTruncate64(m_pipeline_disk_cache_file, 0, &error))
|
||||
{
|
||||
ERROR_LOG("Failed to truncate pipeline cache: {}", error.GetDescription());
|
||||
#ifdef OPENGL_PIPELINE_CACHE_NEEDS_LOCK
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
m_pipeline_disk_cache_file_lock.Unlock();
|
||||
#endif
|
||||
std::fclose(m_pipeline_disk_cache_file);
|
||||
|
@ -1031,7 +1031,7 @@ bool OpenGLDevice::DiscardPipelineCache()
|
|||
bool OpenGLDevice::ClosePipelineCache(const std::string& filename, Error* error)
|
||||
{
|
||||
const auto close_cache = [this]() {
|
||||
#ifdef OPENGL_PIPELINE_CACHE_NEEDS_LOCK
|
||||
#ifdef HAS_POSIX_FILE_LOCK
|
||||
m_pipeline_disk_cache_file_lock.Unlock();
|
||||
#endif
|
||||
std::fclose(m_pipeline_disk_cache_file);
|
||||
|
|
|
@ -37,7 +37,8 @@ const std::tuple<GLenum, GLenum, GLenum>& OpenGLTexture::GetPixelFormatMapping(G
|
|||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
|
||||
{GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8
|
||||
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
{GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // RGB5A1
|
||||
{}, // RGB5A1
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // A1BGR5
|
||||
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8
|
||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_SHORT}, // D16
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT}, // D24S8
|
||||
|
@ -71,7 +72,8 @@ const std::tuple<GLenum, GLenum, GLenum>& OpenGLTexture::GetPixelFormatMapping(G
|
|||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
|
||||
{GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8
|
||||
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
{GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // RGB5A1
|
||||
{}, // RGB5A1
|
||||
{GL_RGB5_A1, GL_RGBA, GL_UNSIGNED_SHORT_5_5_5_1}, // A1BGR5
|
||||
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8
|
||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_SHORT}, // D16
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT}, // D24S8
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
#if defined(_WIN32)
|
||||
#include "common/windows_headers.h"
|
||||
#elif defined(__linux__)
|
||||
#elif defined(__linux__) && !defined(__ANDROID__)
|
||||
#include <signal.h>
|
||||
#include <ucontext.h>
|
||||
#include <unistd.h>
|
||||
|
@ -136,7 +136,7 @@ bool PageFaultHandler::Install(Error* error)
|
|||
return true;
|
||||
}
|
||||
|
||||
#else
|
||||
#elif !defined(__ANDROID__)
|
||||
|
||||
namespace PageFaultHandler {
|
||||
static void SignalHandler(int sig, siginfo_t* info, void* ctx);
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include "postprocessing_shader.h"
|
||||
#include "postprocessing_shader_fx.h"
|
||||
#include "postprocessing_shader_glsl.h"
|
||||
#include "shadergen.h"
|
||||
|
||||
// TODO: Remove me
|
||||
#include "core/host.h"
|
||||
|
@ -28,9 +29,6 @@
|
|||
|
||||
LOG_CHANNEL(PostProcessing);
|
||||
|
||||
// TODO: ProgressCallbacks for shader compiling, it can be a bit slow.
|
||||
// TODO: buffer width/height is wrong on resize, need to change it somehow.
|
||||
|
||||
namespace PostProcessing {
|
||||
template<typename T>
|
||||
static u32 ParseVector(std::string_view line, ShaderOption::ValueVector* values);
|
||||
|
@ -369,6 +367,11 @@ PostProcessing::Chain::Chain(const char* section) : m_section(section)
|
|||
|
||||
PostProcessing::Chain::~Chain() = default;
|
||||
|
||||
GPUTexture* PostProcessing::Chain::GetTextureUnusedAtEndOfChain() const
|
||||
{
|
||||
return (m_stages.size() % 2) ? m_output_texture.get() : m_input_texture.get();
|
||||
}
|
||||
|
||||
bool PostProcessing::Chain::IsActive() const
|
||||
{
|
||||
return m_enabled && !m_stages.empty();
|
||||
|
@ -561,16 +564,58 @@ bool PostProcessing::Chain::CheckTargets(GPUTexture::Format target_format, u32 t
|
|||
if (m_target_format == target_format && m_target_width == target_width && m_target_height == target_height)
|
||||
return true;
|
||||
|
||||
Error error;
|
||||
|
||||
if (!IsInternalChain() && (!m_rotated_copy_pipeline || m_target_format != target_format))
|
||||
{
|
||||
const RenderAPI rapi = g_gpu_device->GetRenderAPI();
|
||||
const ShaderGen shadergen(rapi, ShaderGen::GetShaderLanguageForAPI(rapi), false, false);
|
||||
const std::unique_ptr<GPUShader> vso = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(),
|
||||
shadergen.GenerateRotateVertexShader(), &error);
|
||||
const std::unique_ptr<GPUShader> fso = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
|
||||
shadergen.GenerateRotateFragmentShader(), &error);
|
||||
if (!vso || !fso)
|
||||
{
|
||||
ERROR_LOG("Failed to compile post-processing rotate shaders: {}", error.GetDescription());
|
||||
return false;
|
||||
}
|
||||
GL_OBJECT_NAME(vso, "Post-processing rotate blit VS");
|
||||
GL_OBJECT_NAME(vso, "Post-processing rotate blit FS");
|
||||
|
||||
const GPUPipeline::GraphicsConfig config = {.layout = GPUPipeline::Layout::SingleTextureAndPushConstants,
|
||||
.primitive = GPUPipeline::Primitive::Triangles,
|
||||
.input_layout = {},
|
||||
.rasterization = GPUPipeline::RasterizationState::GetNoCullState(),
|
||||
.depth = GPUPipeline::DepthState::GetNoTestsState(),
|
||||
.blend = GPUPipeline::BlendState::GetNoBlendingState(),
|
||||
.vertex_shader = vso.get(),
|
||||
.geometry_shader = nullptr,
|
||||
.fragment_shader = fso.get(),
|
||||
.color_formats = {target_format},
|
||||
.depth_format = GPUTexture::Format::Unknown,
|
||||
.samples = 1,
|
||||
.per_sample_shading = false,
|
||||
.render_pass_flags = GPUPipeline::NoRenderPassFlags};
|
||||
m_rotated_copy_pipeline = g_gpu_device->CreatePipeline(config, &error);
|
||||
if (!m_rotated_copy_pipeline)
|
||||
{
|
||||
ERROR_LOG("Failed to compile post-processing rotate pipeline: {}", error.GetDescription());
|
||||
return false;
|
||||
}
|
||||
GL_OBJECT_NAME(m_rotated_copy_pipeline, "Post-processing rotate pipeline");
|
||||
}
|
||||
|
||||
// In case any allocs fail.
|
||||
DestroyTextures();
|
||||
|
||||
if (!(m_input_texture =
|
||||
g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
|
||||
target_format, GPUTexture::Flags::None)) ||
|
||||
target_format, GPUTexture::Flags::None, nullptr, 0, &error)) ||
|
||||
!(m_output_texture =
|
||||
g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
|
||||
target_format, GPUTexture::Flags::None)))
|
||||
target_format, GPUTexture::Flags::None, nullptr, 0, &error)))
|
||||
{
|
||||
ERROR_LOG("Failed to create input/output textures: {}", error.GetDescription());
|
||||
DestroyTextures();
|
||||
return false;
|
||||
}
|
||||
|
@ -589,10 +634,10 @@ bool PostProcessing::Chain::CheckTargets(GPUTexture::Format target_format, u32 t
|
|||
|
||||
progress->FormatStatusText("Compiling {}...", shader->GetName());
|
||||
|
||||
if (!shader->CompilePipeline(target_format, target_width, target_height, progress) ||
|
||||
!shader->ResizeOutput(target_format, target_width, target_height))
|
||||
if (!shader->CompilePipeline(target_format, target_width, target_height, &error, progress) ||
|
||||
!shader->ResizeOutput(target_format, target_width, target_height, &error))
|
||||
{
|
||||
ERROR_LOG("Failed to compile one or more post-processing shaders, disabling.");
|
||||
ERROR_LOG("Failed to compile post-processing shader '{}':\n{}", shader->GetName(), error.GetDescription());
|
||||
Host::AddIconOSDMessage(
|
||||
"PostProcessLoadFail", ICON_FA_EXCLAMATION_TRIANGLE,
|
||||
fmt::format("Failed to compile post-processing shader '{}'. Disabling post-processing.", shader->GetName()));
|
||||
|
@ -621,6 +666,11 @@ void PostProcessing::Chain::DestroyTextures()
|
|||
g_gpu_device->RecycleTexture(std::move(m_input_texture));
|
||||
}
|
||||
|
||||
void PostProcessing::Chain::DestroyPipelines()
|
||||
{
|
||||
m_rotated_copy_pipeline.reset();
|
||||
}
|
||||
|
||||
GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* input_depth,
|
||||
GPUTexture* final_target, GSVector4i final_rect, s32 orig_width,
|
||||
s32 orig_height, s32 native_width, s32 native_height)
|
||||
|
@ -632,13 +682,24 @@ GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, G
|
|||
if (input_depth)
|
||||
input_depth->MakeReadyForSampling();
|
||||
|
||||
GPUTexture* draw_final_target = final_target;
|
||||
const WindowInfo::PreRotation prerotation =
|
||||
final_target ? WindowInfo::PreRotation::Identity : g_gpu_device->GetMainSwapChain()->GetPreRotation();
|
||||
if (prerotation != WindowInfo::PreRotation::Identity)
|
||||
{
|
||||
// We have prerotation and post processing. This is messy, since we need to run the shader on the "real" size,
|
||||
// then copy it across to the rotated image. We can use the input or output texture from the chain, whichever
|
||||
// was not the last that was drawn to.
|
||||
draw_final_target = GetTextureUnusedAtEndOfChain();
|
||||
}
|
||||
|
||||
for (const std::unique_ptr<Shader>& stage : m_stages)
|
||||
{
|
||||
const bool is_final = (stage.get() == m_stages.back().get());
|
||||
|
||||
if (const GPUDevice::PresentResult pres =
|
||||
stage->Apply(input_color, input_depth, is_final ? final_target : output, final_rect, orig_width, orig_height,
|
||||
native_width, native_height, m_target_width, m_target_height);
|
||||
stage->Apply(input_color, input_depth, is_final ? draw_final_target : output, final_rect, orig_width,
|
||||
orig_height, native_width, native_height, m_target_width, m_target_height);
|
||||
pres != GPUDevice::PresentResult::OK)
|
||||
{
|
||||
return pres;
|
||||
|
@ -652,6 +713,30 @@ GPUDevice::PresentResult PostProcessing::Chain::Apply(GPUTexture* input_color, G
|
|||
}
|
||||
}
|
||||
|
||||
if (prerotation != WindowInfo::PreRotation::Identity)
|
||||
{
|
||||
draw_final_target->MakeReadyForSampling();
|
||||
|
||||
// Rotate and blit to final swap chain.
|
||||
GPUSwapChain* const swap_chain = g_gpu_device->GetMainSwapChain();
|
||||
if (const GPUDevice::PresentResult pres = g_gpu_device->BeginPresent(swap_chain);
|
||||
pres != GPUDevice::PresentResult::OK)
|
||||
{
|
||||
return pres;
|
||||
}
|
||||
|
||||
GL_PUSH_FMT("Apply swap chain pre-rotation");
|
||||
|
||||
const GSMatrix2x2 rotmat = GSMatrix2x2::Rotation(WindowInfo::GetZRotationForPreRotation(prerotation));
|
||||
g_gpu_device->SetPipeline(m_rotated_copy_pipeline.get());
|
||||
g_gpu_device->PushUniformBuffer(&rotmat, sizeof(rotmat));
|
||||
g_gpu_device->SetTextureSampler(0, draw_final_target, g_gpu_device->GetNearestSampler());
|
||||
g_gpu_device->SetViewportAndScissor(0, 0, swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight());
|
||||
g_gpu_device->Draw(3, 0);
|
||||
|
||||
GL_POP();
|
||||
}
|
||||
|
||||
return GPUDevice::PresentResult::OK;
|
||||
}
|
||||
|
||||
|
@ -674,6 +759,7 @@ void PostProcessing::Shutdown()
|
|||
s_samplers.clear();
|
||||
ForAllChains([](Chain& chain) {
|
||||
chain.ClearStages();
|
||||
chain.DestroyPipelines();
|
||||
chain.DestroyTextures();
|
||||
});
|
||||
}
|
||||
|
@ -690,6 +776,7 @@ bool PostProcessing::ReloadShaders()
|
|||
|
||||
ForAllChains([](Chain& chain) {
|
||||
chain.ClearStages();
|
||||
chain.DestroyPipelines();
|
||||
chain.DestroyTextures();
|
||||
chain.LoadStages();
|
||||
});
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
|
||||
class Timer;
|
||||
|
||||
class GPUPipeline;
|
||||
class GPUSampler;
|
||||
class GPUTexture;
|
||||
|
||||
|
@ -117,6 +118,9 @@ public:
|
|||
ALWAYS_INLINE GPUTexture* GetInputTexture() const { return m_input_texture.get(); }
|
||||
ALWAYS_INLINE GPUTexture* GetOutputTexture() const { return m_output_texture.get(); }
|
||||
|
||||
/// Returns either the input or output texture, whichever isn't the destination after the final pass.
|
||||
GPUTexture* GetTextureUnusedAtEndOfChain() const;
|
||||
|
||||
bool IsActive() const;
|
||||
bool IsInternalChain() const;
|
||||
|
||||
|
@ -125,6 +129,7 @@ public:
|
|||
void LoadStages();
|
||||
void ClearStages();
|
||||
void DestroyTextures();
|
||||
void DestroyPipelines();
|
||||
|
||||
/// Temporarily toggles post-processing on/off.
|
||||
void Toggle();
|
||||
|
@ -151,6 +156,7 @@ private:
|
|||
std::vector<std::unique_ptr<PostProcessing::Shader>> m_stages;
|
||||
std::unique_ptr<GPUTexture> m_input_texture;
|
||||
std::unique_ptr<GPUTexture> m_output_texture;
|
||||
std::unique_ptr<GPUPipeline> m_rotated_copy_pipeline;
|
||||
};
|
||||
|
||||
// [display_name, filename]
|
||||
|
|
|
@ -44,9 +44,10 @@ public:
|
|||
const ShaderOption* GetOptionByName(std::string_view name) const;
|
||||
ShaderOption* GetOptionByName(std::string_view name);
|
||||
|
||||
virtual bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height) = 0;
|
||||
virtual bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height, Error* error) = 0;
|
||||
|
||||
virtual bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress) = 0;
|
||||
virtual bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, Error* error,
|
||||
ProgressCallback* progress) = 0;
|
||||
|
||||
virtual GPUDevice::PresentResult Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
|
||||
GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
|
||||
|
|
|
@ -1321,7 +1321,7 @@ GPUTexture* PostProcessing::ReShadeFXShader::GetTextureByID(TextureID id, GPUTex
|
|||
return m_textures[static_cast<size_t>(id)].texture.get();
|
||||
}
|
||||
|
||||
bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format, u32 width, u32 height,
|
||||
bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format, u32 width, u32 height, Error* error,
|
||||
ProgressCallback* progress)
|
||||
{
|
||||
m_valid = false;
|
||||
|
@ -1342,31 +1342,29 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format,
|
|||
|
||||
const auto& [cg, cg_language] = CreateRFXCodegen(false);
|
||||
|
||||
Error error;
|
||||
if (!CreateModule(width, height, cg.get(), cg_language, std::move(fxcode), &error))
|
||||
if (!CreateModule(width, height, cg.get(), cg_language, std::move(fxcode), error))
|
||||
{
|
||||
ERROR_LOG("Failed to create module for '{}': {}", m_name, error.GetDescription());
|
||||
Error::AddPrefix(error, "Failed to create module: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
const reshadefx::effect_module& mod = cg->module();
|
||||
DebugAssert(!mod.techniques.empty());
|
||||
|
||||
if (!CreatePasses(format, mod, &error))
|
||||
if (!CreatePasses(format, mod, error))
|
||||
{
|
||||
ERROR_LOG("Failed to create passes for '{}': {}", m_name, error.GetDescription());
|
||||
Error::AddPrefix(error, "Failed to create passes: ");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto get_shader = [cg_language, &cg](const std::string& name, const std::span<Sampler> samplers,
|
||||
GPUShaderStage stage) {
|
||||
auto get_shader = [cg_language, &cg, error](const std::string& name, const std::span<Sampler> samplers,
|
||||
GPUShaderStage stage) {
|
||||
const std::string real_code = cg->finalize_code_for_entry_point(name);
|
||||
const char* entry_point = (cg_language == GPUShaderLanguage::HLSL) ? name.c_str() : "main";
|
||||
|
||||
Error error;
|
||||
std::unique_ptr<GPUShader> sshader = g_gpu_device->CreateShader(stage, cg_language, real_code, &error, entry_point);
|
||||
std::unique_ptr<GPUShader> sshader = g_gpu_device->CreateShader(stage, cg_language, real_code, error, entry_point);
|
||||
if (!sshader)
|
||||
ERROR_LOG("Failed to compile function '{}': {}", name, error.GetDescription());
|
||||
Error::AddPrefixFmt(error, "Failed to compile function '{}': ", name);
|
||||
|
||||
return sshader;
|
||||
};
|
||||
|
@ -1426,10 +1424,10 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format,
|
|||
return false;
|
||||
}
|
||||
|
||||
pass.pipeline = g_gpu_device->CreatePipeline(plconfig, &error);
|
||||
pass.pipeline = g_gpu_device->CreatePipeline(plconfig, error);
|
||||
if (!pass.pipeline)
|
||||
{
|
||||
ERROR_LOG("Failed to create pipeline for pass '{}': {}", info.name, error.GetDescription());
|
||||
Error::AddPrefixFmt(error, "Failed to create pipeline for pass '{}': ", info.name);
|
||||
progress->PopState();
|
||||
return false;
|
||||
}
|
||||
|
@ -1444,7 +1442,7 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format,
|
|||
return true;
|
||||
}
|
||||
|
||||
bool PostProcessing::ReShadeFXShader::ResizeOutput(GPUTexture::Format format, u32 width, u32 height)
|
||||
bool PostProcessing::ReShadeFXShader::ResizeOutput(GPUTexture::Format format, u32 width, u32 height, Error* error)
|
||||
{
|
||||
m_valid = false;
|
||||
|
||||
|
@ -1458,7 +1456,7 @@ bool PostProcessing::ReShadeFXShader::ResizeOutput(GPUTexture::Format format, u3
|
|||
const u32 t_width = std::max(static_cast<u32>(static_cast<float>(width) * tex.rt_scale), 1u);
|
||||
const u32 t_height = std::max(static_cast<u32>(static_cast<float>(height) * tex.rt_scale), 1u);
|
||||
tex.texture = g_gpu_device->FetchTexture(t_width, t_height, 1, 1, 1, GPUTexture::Type::RenderTarget, tex.format,
|
||||
GPUTexture::Flags::None);
|
||||
GPUTexture::Flags::None, nullptr, 0, error);
|
||||
if (!tex.texture)
|
||||
return {};
|
||||
}
|
||||
|
|
|
@ -33,8 +33,9 @@ public:
|
|||
bool LoadFromFile(std::string name, std::string filename, bool only_config, Error* error);
|
||||
bool LoadFromString(std::string name, std::string filename, std::string code, bool only_config, Error* error);
|
||||
|
||||
bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height) override;
|
||||
bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress) override;
|
||||
bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height, Error* error) override;
|
||||
bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, Error* error,
|
||||
ProgressCallback* progress) override;
|
||||
GPUDevice::PresentResult Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
|
||||
GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
|
||||
s32 native_height, u32 target_width, u32 target_height) override;
|
||||
|
|
|
@ -121,7 +121,7 @@ void PostProcessing::GLSLShader::FillUniformBuffer(void* buffer, s32 viewport_x,
|
|||
}
|
||||
}
|
||||
|
||||
bool PostProcessing::GLSLShader::CompilePipeline(GPUTexture::Format format, u32 width, u32 height,
|
||||
bool PostProcessing::GLSLShader::CompilePipeline(GPUTexture::Format format, u32 width, u32 height, Error* error,
|
||||
ProgressCallback* progress)
|
||||
{
|
||||
if (m_pipeline)
|
||||
|
@ -130,10 +130,10 @@ bool PostProcessing::GLSLShader::CompilePipeline(GPUTexture::Format format, u32
|
|||
PostProcessingGLSLShaderGen shadergen(g_gpu_device->GetRenderAPI(), g_gpu_device->GetFeatures().dual_source_blend,
|
||||
g_gpu_device->GetFeatures().framebuffer_fetch);
|
||||
|
||||
std::unique_ptr<GPUShader> vs = g_gpu_device->CreateShader(GPUShaderStage::Vertex, shadergen.GetLanguage(),
|
||||
shadergen.GeneratePostProcessingVertexShader(*this));
|
||||
std::unique_ptr<GPUShader> fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
|
||||
shadergen.GeneratePostProcessingFragmentShader(*this));
|
||||
std::unique_ptr<GPUShader> vs = g_gpu_device->CreateShader(
|
||||
GPUShaderStage::Vertex, shadergen.GetLanguage(), shadergen.GeneratePostProcessingVertexShader(*this), error);
|
||||
std::unique_ptr<GPUShader> fs = g_gpu_device->CreateShader(
|
||||
GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GeneratePostProcessingFragmentShader(*this), error);
|
||||
if (!vs || !fs)
|
||||
return false;
|
||||
|
||||
|
@ -151,7 +151,7 @@ bool PostProcessing::GLSLShader::CompilePipeline(GPUTexture::Format format, u32
|
|||
plconfig.fragment_shader = fs.get();
|
||||
plconfig.geometry_shader = nullptr;
|
||||
|
||||
if (!(m_pipeline = g_gpu_device->CreatePipeline(plconfig)))
|
||||
if (!(m_pipeline = g_gpu_device->CreatePipeline(plconfig, error)))
|
||||
return false;
|
||||
|
||||
if (!m_sampler)
|
||||
|
@ -160,7 +160,7 @@ bool PostProcessing::GLSLShader::CompilePipeline(GPUTexture::Format format, u32
|
|||
config.address_u = GPUSampler::AddressMode::ClampToBorder;
|
||||
config.address_v = GPUSampler::AddressMode::ClampToBorder;
|
||||
config.border_color = 0xFF000000u;
|
||||
if (!(m_sampler = g_gpu_device->CreateSampler(config)))
|
||||
if (!(m_sampler = g_gpu_device->CreateSampler(config, error)))
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -201,7 +201,7 @@ GPUDevice::PresentResult PostProcessing::GLSLShader::Apply(GPUTexture* input_col
|
|||
return GPUDevice::PresentResult::OK;
|
||||
}
|
||||
|
||||
bool PostProcessing::GLSLShader::ResizeOutput(GPUTexture::Format format, u32 width, u32 height)
|
||||
bool PostProcessing::GLSLShader::ResizeOutput(GPUTexture::Format format, u32 width, u32 height, Error* error)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -22,8 +22,9 @@ public:
|
|||
bool LoadFromFile(std::string name, const char* filename, Error* error);
|
||||
bool LoadFromString(std::string name, std::string code, Error* error);
|
||||
|
||||
bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height) override;
|
||||
bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, ProgressCallback* progress) override;
|
||||
bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height, Error* error) override;
|
||||
bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height, Error* error,
|
||||
ProgressCallback* progress) override;
|
||||
GPUDevice::PresentResult Apply(GPUTexture* input_color, GPUTexture* input_depth, GPUTexture* final_target,
|
||||
GSVector4i final_rect, s32 orig_width, s32 orig_height, s32 native_width,
|
||||
s32 native_height, u32 target_width, u32 target_height) override;
|
||||
|
|
|
@ -357,20 +357,20 @@ void ShaderGen::WriteUniformBufferDeclaration(std::stringstream& ss, bool push_c
|
|||
{
|
||||
if (m_render_api == RenderAPI::Vulkan && push_constant_on_vulkan)
|
||||
{
|
||||
ss << "layout(push_constant) uniform PushConstants\n";
|
||||
ss << "layout(push_constant, row_major) uniform PushConstants\n";
|
||||
}
|
||||
else
|
||||
{
|
||||
ss << "layout(std140, set = 0, binding = 0) uniform UBOBlock\n";
|
||||
ss << "layout(std140, row_major, set = 0, binding = 0) uniform UBOBlock\n";
|
||||
m_has_uniform_buffer = true;
|
||||
}
|
||||
}
|
||||
else if (m_glsl)
|
||||
{
|
||||
if (m_use_glsl_binding_layout)
|
||||
ss << "layout(std140, binding = 0) uniform UBOBlock\n";
|
||||
ss << "layout(std140, row_major, binding = 0) uniform UBOBlock\n";
|
||||
else
|
||||
ss << "layout(std140) uniform UBOBlock\n";
|
||||
ss << "layout(std140, row_major) uniform UBOBlock\n";
|
||||
|
||||
m_has_uniform_buffer = true;
|
||||
}
|
||||
|
@ -791,6 +791,40 @@ void ShaderGen::DeclareFragmentEntryPoint(
|
|||
}
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateRotateVertexShader() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareUniformBuffer(ss, { "float2 u_rotation_matrix0", "float2 u_rotation_matrix1" }, true);
|
||||
DeclareVertexEntryPoint(ss, {}, 0, 1, {}, true);
|
||||
ss << "{\n";
|
||||
ss << " v_tex0 = float2(float((v_id << 1) & 2u), float(v_id & 2u));\n";
|
||||
ss << " v_pos = float4(v_tex0 * float2(2.0f, -2.0f) + float2(-1.0f, 1.0f), 0.0f, 1.0f);\n";
|
||||
ss << " v_pos.xy = float2(dot(u_rotation_matrix0, v_pos.xy), dot(u_rotation_matrix1, v_pos.xy));\n";
|
||||
ss << " #if API_OPENGL || API_OPENGL_ES || API_VULKAN\n";
|
||||
ss << " v_pos.y = -v_pos.y;\n";
|
||||
ss << " #endif\n";
|
||||
ss << "}\n";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateRotateFragmentShader() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareTexture(ss, "samp0", 0);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1);
|
||||
|
||||
ss << R"(
|
||||
{
|
||||
o_col0 = SAMPLE_TEXTURE(samp0, v_tex0);
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string ShaderGen::GenerateScreenQuadVertexShader(float z /* = 0.0f */) const
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
|
|
@ -25,6 +25,9 @@ public:
|
|||
|
||||
ALWAYS_INLINE GPUShaderLanguage GetLanguage() const { return m_shader_language; }
|
||||
|
||||
std::string GenerateRotateVertexShader() const;
|
||||
std::string GenerateRotateFragmentShader() const;
|
||||
|
||||
std::string GenerateScreenQuadVertexShader(float z = 0.0f) const;
|
||||
std::string GenerateUVQuadVertexShader() const;
|
||||
std::string GenerateFillFragmentShader() const;
|
||||
|
|
|
@ -76,6 +76,7 @@ const std::array<VkFormat, static_cast<u32>(GPUTexture::Format::MaxCount)> Vulka
|
|||
VK_FORMAT_B8G8R8A8_UNORM, // BGRA8
|
||||
VK_FORMAT_R5G6B5_UNORM_PACK16, // RGB565
|
||||
VK_FORMAT_A1R5G5B5_UNORM_PACK16, // RGB5A1
|
||||
VK_FORMAT_B5G5R5A1_UNORM_PACK16, // A1BGR5
|
||||
VK_FORMAT_R8_UNORM, // R8
|
||||
VK_FORMAT_D16_UNORM, // D16
|
||||
VK_FORMAT_D24_UNORM_S8_UINT, // D24S8
|
||||
|
@ -510,9 +511,12 @@ bool VulkanDevice::SelectDeviceExtensions(ExtensionList* extension_list, bool en
|
|||
{
|
||||
// VK_KHR_dynamic_rendering_local_read appears to be broken on RDNA3, like everything else...
|
||||
// Just causes GPU resets when you actually use a feedback loop. Assume Mesa is fine.
|
||||
// VK_EXT_fragment_shader_interlock is similar, random GPU hangs.
|
||||
#if defined(_WIN32) || defined(__ANDROID__)
|
||||
m_optional_extensions.vk_ext_fragment_shader_interlock = false;
|
||||
m_optional_extensions.vk_khr_dynamic_rendering_local_read = false;
|
||||
WARNING_LOG("Disabling VK_KHR_dynamic_rendering_local_read on broken AMD driver.");
|
||||
WARNING_LOG(
|
||||
"Disabling VK_EXT_fragment_shader_interlock and VK_KHR_dynamic_rendering_local_read on broken AMD driver.");
|
||||
#endif
|
||||
}
|
||||
|
||||
|
@ -3406,7 +3410,7 @@ void VulkanDevice::BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 cle
|
|||
const VkRenderingInfoKHR ri = {VK_STRUCTURE_TYPE_RENDERING_INFO_KHR,
|
||||
nullptr,
|
||||
0u,
|
||||
{{}, {swap_chain->GetWidth(), swap_chain->GetHeight()}},
|
||||
{{}, {swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight()}},
|
||||
1u,
|
||||
0u,
|
||||
1u,
|
||||
|
@ -3427,7 +3431,7 @@ void VulkanDevice::BeginSwapChainRenderPass(VulkanSwapChain* swap_chain, u32 cle
|
|||
nullptr,
|
||||
m_current_render_pass,
|
||||
swap_chain->GetCurrentFramebuffer(),
|
||||
{{0, 0}, {swap_chain->GetWidth(), swap_chain->GetHeight()}},
|
||||
{{0, 0}, {swap_chain->GetPostRotatedWidth(), swap_chain->GetPostRotatedHeight()}},
|
||||
1u,
|
||||
&clear_value};
|
||||
vkCmdBeginRenderPass(GetCurrentCommandBuffer(), &rp, VK_SUBPASS_CONTENTS_INLINE);
|
||||
|
|
|
@ -387,9 +387,50 @@ bool VulkanSwapChain::CreateSwapChain(VulkanDevice& dev, Error* error)
|
|||
surface_caps.surfaceCapabilities.maxImageExtent.height);
|
||||
|
||||
// Prefer identity transform if possible
|
||||
VkExtent2D window_size = size;
|
||||
WindowInfo::PreRotation window_prerotation = WindowInfo::PreRotation::Identity;
|
||||
VkSurfaceTransformFlagBitsKHR transform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
|
||||
if (!(surface_caps.surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR))
|
||||
transform = surface_caps.surfaceCapabilities.currentTransform;
|
||||
switch (surface_caps.surfaceCapabilities.currentTransform)
|
||||
{
|
||||
case VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR:
|
||||
break;
|
||||
|
||||
case VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR:
|
||||
transform = VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR;
|
||||
window_prerotation = WindowInfo::PreRotation::Rotate90Clockwise;
|
||||
std::swap(size.width, size.height);
|
||||
DEV_LOG("Using VK_SURFACE_TRANSFORM_ROTATE_90_BIT_KHR pretransform.");
|
||||
break;
|
||||
|
||||
case VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR:
|
||||
transform = VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR;
|
||||
window_prerotation = WindowInfo::PreRotation::Rotate180Clockwise;
|
||||
DEV_LOG("Using VK_SURFACE_TRANSFORM_ROTATE_180_BIT_KHR pretransform.");
|
||||
break;
|
||||
|
||||
case VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR:
|
||||
transform = VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR;
|
||||
window_prerotation = WindowInfo::PreRotation::Rotate270Clockwise;
|
||||
std::swap(size.width, size.height);
|
||||
DEV_LOG("Using VK_SURFACE_TRANSFORM_ROTATE_270_BIT_KHR pretransform.");
|
||||
break;
|
||||
|
||||
default:
|
||||
{
|
||||
if (!(surface_caps.surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR))
|
||||
{
|
||||
WARNING_LOG("Unhandled surface transform 0x{:X}, identity unsupported.",
|
||||
static_cast<u32>(surface_caps.surfaceCapabilities.supportedTransforms));
|
||||
transform = surface_caps.surfaceCapabilities.currentTransform;
|
||||
}
|
||||
else
|
||||
{
|
||||
WARNING_LOG("Unhandled surface transform 0x{:X}",
|
||||
static_cast<u32>(surface_caps.surfaceCapabilities.supportedTransforms));
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
VkCompositeAlphaFlagBitsKHR alpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
|
||||
if (!(surface_caps.surfaceCapabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR))
|
||||
|
@ -486,17 +527,18 @@ bool VulkanSwapChain::CreateSwapChain(VulkanDevice& dev, Error* error)
|
|||
if (old_swap_chain != VK_NULL_HANDLE)
|
||||
vkDestroySwapchainKHR(vkdev, old_swap_chain, nullptr);
|
||||
|
||||
if (size.width == 0 || size.width > std::numeric_limits<u16>::max() || size.height == 0 ||
|
||||
size.height > std::numeric_limits<u16>::max())
|
||||
if (window_size.width == 0 || window_size.width > std::numeric_limits<u16>::max() || window_size.height == 0 ||
|
||||
window_size.height > std::numeric_limits<u16>::max())
|
||||
{
|
||||
Error::SetStringFmt(error, "Invalid swap chain dimensions: {}x{}", size.width, size.height);
|
||||
Error::SetStringFmt(error, "Invalid swap chain dimensions: {}x{}", window_size.width, window_size.height);
|
||||
return false;
|
||||
}
|
||||
|
||||
m_present_mode = present_mode.value();
|
||||
m_window_info.surface_width = static_cast<u16>(size.width);
|
||||
m_window_info.surface_height = static_cast<u16>(size.height);
|
||||
m_window_info.surface_width = static_cast<u16>(window_size.width);
|
||||
m_window_info.surface_height = static_cast<u16>(window_size.height);
|
||||
m_window_info.surface_format = VulkanDevice::GetFormatForVkFormat(surface_format->format);
|
||||
m_window_info.surface_prerotation = window_prerotation;
|
||||
if (m_window_info.surface_format == GPUTexture::Format::Unknown)
|
||||
{
|
||||
Error::SetStringFmt(error, "Unknown surface format {}", static_cast<u32>(surface_format->format));
|
||||
|
@ -534,6 +576,8 @@ bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error)
|
|||
return false;
|
||||
}
|
||||
|
||||
const u32 fb_width = GetPostRotatedWidth();
|
||||
const u32 fb_height = GetPostRotatedHeight();
|
||||
m_images.reserve(image_count);
|
||||
m_current_image = 0;
|
||||
for (u32 i = 0; i < image_count; i++)
|
||||
|
@ -565,7 +609,7 @@ bool VulkanSwapChain::CreateSwapChainImages(VulkanDevice& dev, Error* error)
|
|||
Vulkan::FramebufferBuilder fbb;
|
||||
fbb.AddAttachment(image.view);
|
||||
fbb.SetRenderPass(render_pass);
|
||||
fbb.SetSize(m_window_info.surface_width, m_window_info.surface_height, 1);
|
||||
fbb.SetSize(fb_width, fb_height, 1);
|
||||
if ((image.framebuffer = fbb.Create(vkdev)) == VK_NULL_HANDLE)
|
||||
{
|
||||
Error::SetStringView(error, "Failed to create swap chain image framebuffer.");
|
||||
|
|
|
@ -9,8 +9,31 @@
|
|||
#include "common/log.h"
|
||||
#include "common/scoped_guard.h"
|
||||
|
||||
#include <numbers>
|
||||
#include <utility>
|
||||
|
||||
LOG_CHANNEL(WindowInfo);
|
||||
|
||||
void WindowInfo::SetPreRotated(PreRotation prerotation)
|
||||
{
|
||||
if (ShouldSwapDimensionsForPreRotation(prerotation) != ShouldSwapDimensionsForPreRotation(surface_prerotation))
|
||||
std::swap(surface_width, surface_height);
|
||||
|
||||
surface_prerotation = prerotation;
|
||||
}
|
||||
|
||||
float WindowInfo::GetZRotationForPreRotation(PreRotation prerotation)
|
||||
{
|
||||
static constexpr const std::array<float, 4> rotation_radians = {{
|
||||
0.0f, // Identity
|
||||
static_cast<float>(std::numbers::pi * 1.5f), // Rotate90Clockwise
|
||||
static_cast<float>(std::numbers::pi), // Rotate180Clockwise
|
||||
static_cast<float>(std::numbers::pi / 2.0), // Rotate270Clockwise
|
||||
}};
|
||||
|
||||
return rotation_radians[static_cast<size_t>(prerotation)];
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
|
||||
#include "common/windows_headers.h"
|
||||
|
|
|
@ -24,8 +24,17 @@ struct WindowInfo
|
|||
Android,
|
||||
};
|
||||
|
||||
enum class PreRotation : u8
|
||||
{
|
||||
Identity,
|
||||
Rotate90Clockwise,
|
||||
Rotate180Clockwise,
|
||||
Rotate270Clockwise,
|
||||
};
|
||||
|
||||
Type type = Type::Surfaceless;
|
||||
GPUTexture::Format surface_format = GPUTexture::Format::Unknown;
|
||||
PreRotation surface_prerotation = PreRotation::Identity;
|
||||
u16 surface_width = 0;
|
||||
u16 surface_height = 0;
|
||||
float surface_refresh_rate = 0.0f;
|
||||
|
@ -35,5 +44,24 @@ struct WindowInfo
|
|||
|
||||
ALWAYS_INLINE bool IsSurfaceless() const { return type == Type::Surfaceless; }
|
||||
|
||||
ALWAYS_INLINE u32 GetPostRotatedWidth() const
|
||||
{
|
||||
return ShouldSwapDimensionsForPreRotation(surface_prerotation) ? surface_height : surface_width;
|
||||
}
|
||||
ALWAYS_INLINE u32 GetPostRotatedHeight() const
|
||||
{
|
||||
return ShouldSwapDimensionsForPreRotation(surface_prerotation) ? surface_width : surface_height;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE static bool ShouldSwapDimensionsForPreRotation(PreRotation prerotation)
|
||||
{
|
||||
return (prerotation == PreRotation::Rotate90Clockwise || prerotation == PreRotation::Rotate270Clockwise);
|
||||
}
|
||||
|
||||
/// Sets a new pre-rotation, adjusting the virtual width/height to suit.
|
||||
void SetPreRotated(PreRotation prerotation);
|
||||
|
||||
static float GetZRotationForPreRotation(PreRotation prerotation);
|
||||
|
||||
static std::optional<float> QueryRefreshRateForWindow(const WindowInfo& wi, Error* error = nullptr);
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue