Merge branch 'stenzek:master' into master

This commit is contained in:
Daniel Nylander 2024-12-12 07:01:05 +01:00 committed by GitHub
commit ef7d4f3fb7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
86 changed files with 3287 additions and 2576 deletions

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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());
}
}

View File

@ -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
}

View File

@ -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)

View File

@ -367,7 +367,7 @@ void CrashHandler::WriteDumpForCaller()
LogCallstack(0, nullptr);
}
#else
#elif !defined(__ANDROID__)
bool CrashHandler::Install(CleanupHandler cleanup_handler)
{

View File

@ -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

View File

@ -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);

View File

@ -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));
}

View File

@ -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];
};

View File

@ -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

View File

@ -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

View File

@ -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 \
{ \

View File

@ -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;

View File

@ -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 = "...");

View File

@ -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()

View File

@ -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.

View File

@ -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();

View File

@ -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);
}
}

View File

@ -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));

View File

@ -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);

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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;
}

View File

@ -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.");

View File

@ -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);
}
}

View File

@ -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;

View File

@ -3,8 +3,10 @@
#ifdef __INTELLISENSE__
#include "common/gsvector.h"
#include "gpu.h"
#include "common/gsvector.h"
#include <algorithm>
#define USE_VECTOR 1

View File

@ -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;

View File

@ -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)

View File

@ -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,

View File

@ -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();
}
}

View File

@ -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

View File

@ -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;

View File

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

View File

@ -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)

View File

@ -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,

View File

@ -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);

View File

@ -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>

View File

@ -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();
}

View File

@ -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;

View File

@ -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>

View File

@ -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)));
}
}

View File

@ -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")));

View File

@ -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();

View File

@ -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()

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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%

View File

@ -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

View File

@ -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

View File

@ -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);

View File

@ -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

View File

@ -44,6 +44,7 @@ public:
BGRA8,
RGB565,
RGB5A1,
A1BGR5,
R8,
D16,
D24S8,

View File

@ -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();

View File

@ -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();

View File

@ -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

View File

@ -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

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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
{

View File

@ -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;

View File

@ -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);

View 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

View File

@ -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);

View File

@ -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();
});

View File

@ -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]

View File

@ -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,

View File

@ -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 {};
}

View File

@ -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;

View File

@ -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;
}

View File

@ -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;

View File

@ -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;

View File

@ -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;

View File

@ -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);

View File

@ -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.");

View File

@ -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"

View File

@ -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);
};