Compare commits

...

4 Commits

Author SHA1 Message Date
Marcin Mitura e79a1cc70a [GUI] Polish language (#832) 2026-08-20 19:34:52 +02:00
Mathias a2241d0e83 Fix/agc zero dim and storage (#828)
* fix(videoout): promote guest images to storage usage

* fix(agc): treat zero-dimension indirect compute dispatches as valid no-ops
2026-08-18 21:20:54 +02:00
Foued Attar fe6521f617 Fix unaligned BufferLoad/GlobalLoad dword access
BufferLoadDword/x2/x3/x4, BufferStoreDword/x2/x3/x4, and their GLOBAL
counterparts were routed through LoadUnalignedBufferWord /
StoreBufferBytes, which reconstruct every dword one byte at a time
(4 bounds-checked buffer accesses per dword, each with its own
OpArrayLength + OpSelect + OpAccessChain + OpLoad/OpStore, plus
shift/mask/or reassembly on top).

The GCN ISA guarantees these opcodes are always dword-aligned - only
the byte/short/D16 variants legitimately need unaligned access, and
those already have their own dedicated path
(LoadSubdwordBufferValue / StoreBufferBytes with an explicit byte
count). The generic dword-count loop reached by every other
BufferLoad*/BufferStore*/GlobalLoad*/GlobalStore* opcode was paying
the same per-byte cost for no reason.

Route the dword-granularity path straight through the existing
LoadBufferWord / StoreBufferWord helpers (one bounds check and one
load/store per dword) instead. Measured on a compute shader with 6
BufferLoadDwordx4 instructions in its hottest basic block, this drops
GPU dispatch time for that shader from ~430-448ms to ~86-92ms (~5x)
with no change in output correctness - it is a pure translation
inefficiency fix, independent of any specific title.
2026-08-18 12:20:24 +02:00
Mathias 034ddcc092 fix(vmem): use ConcurrentDictionary for _pageProtections to prevent race corruption (#823) 2026-08-18 12:13:32 +02:00
5 changed files with 290 additions and 84 deletions
@@ -1,6 +1,7 @@
// Copyright (C) 2026 SharpEmu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
using System.Collections.Concurrent;
using System.Runtime.InteropServices;
using SharpEmu.Core.Loader;
using SharpEmu.HLE;
@@ -18,7 +19,7 @@ public sealed unsafe class PhysicalVirtualMemory : IVirtualMemory, IGuestMemoryA
private readonly object _allocationSearchHintGate = new();
private readonly List<MemoryRegion> _regions = new();
private readonly Dictionary<(ulong DesiredAddress, ulong Alignment, bool Executable), ulong> _allocationSearchHints = new();
private readonly Dictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
private readonly ConcurrentDictionary<ulong, ProgramHeaderFlags> _pageProtections = new();
private bool _disposed;
[ThreadStatic]
+213
View File
@@ -0,0 +1,213 @@
{
"_languageName": "Polski",
"Page.Library": "Biblioteka",
"Page.Options": "Opcje",
"Page.GameCount.One": "1 gra",
"Page.GameCount.Other": "{0} gier",
"Library.SearchWatermark": "Szukaj w bibliotece…",
"Library.AddFolder": "Dodaj folder",
"Library.OpenFile": "Otwórz plik…",
"Library.View.Grid": "Widok siatki",
"Library.View.Carousel": "Widok listy",
"Library.Context.Launch": "Uruchom",
"Library.Context.OpenFolder": "Otwórz folder gry",
"Library.Context.CopyPath": "Kopiuj ścieżkę",
"Library.Context.CopyTitleId": "Kopiuj numer seryjny",
"Library.Context.GameSettings": "Ustawienia gry…",
"Library.Context.Remove": "Usuń z biblioteki",
"Library.Empty.Title": "Twoja biblioteka jest pusta",
"Library.Empty.Hint": "Dodaj folder zawierający twoje gry, aby rozpocząć.",
"Library.Empty.SearchTitle": "Brak gier pasujących do twojego wyszukiwania",
"Library.Empty.SearchHint": "Nic w bibliotece nie pasuje do “{0}”.",
"Library.Empty.AddFolder": " Dodaj folder z grami",
"Library.Loading": "Ładowanie biblioteki…",
"Library.Stat.Version": "Wersja",
"Library.Stat.Installed": "Zainstalowano",
"Library.Stat.TitleId": "Numer seryjny",
"Common.Back": "Wstecz",
"Options.General": "Ogólne",
"Options.Logging": "Dziennik zdarzeń",
"Options.Env.Tab": "Środowisko",
"Options.Section.Environment": "ZMIENNE ŚRODOWISKOWE",
"Options.Env.Desc": "Przełączniki przekazywane emulatorowi jako zmienne środowiskowe przy uruchomieniu.",
"Options.Env.Bthid.Desc": "Zgłaszaj Bluetooth HID jako niedostępny dla tytułów, których middleware kierownicy/FFB odpytuje w nieskończoność.\nZostaw wyłączone normalnie. Niektóre tytuły zawieszają się, gdy inicjalizacja zawiedzie.",
"Options.Env.LoopGuard.Desc": "Nie wymuszaj zamknięcia tytułów, które powtarzają to samo wywołanie zbyt długo.\nSpróbuj tego, gdy gra zamyka się sama podczas ładowania.",
"Options.Env.WritableApp0.Desc": "Pozwól tytułom tworzyć i zapisywać pliki wewnątrz folderu instalacji.\nPotrzebne w przypadku niespakowanych zrzutów, które zapisują dane zapisu lub konfiguracji w /app0.",
"Options.Env.VkValidation.Desc": "Włącz warstwy walidacji Vulkan do debugowania GPU.\nWolne. Wymaga zainstalowanego Vulkan SDK.",
"Options.Env.DumpSpirv.Desc": "Zrzuć shadery AGC i ich translacje SPIR-V do folderu shader-dumps.\nUżyj przy zgłaszaniu błędów shaderów lub renderowania.",
"Options.Env.LogDirectMemory.Desc": "Loguj alokacje pamięci bezpośredniej i błędy do konsoli.\nUżyj, gdy gra przerywa działanie lub zamyka się podczas uruchamiania.",
"Options.Env.LogIo.Desc": "Loguj otwieranie plików, odczyt i rozwiązywanie ścieżek do konsoli.\nUżyj, gdy gra nie może znaleźć swoich plików danych podczas uruchamiania.",
"Options.Env.LogNp.Desc": "Loguj wywołania biblioteki NP (PlayStation Network) do konsoli.",
"Options.Env.Group.Debug": "Debugowanie",
"Options.Env.Group.General": "Ogólne",
"Options.Env.RenderDoc.Desc": "Załaduj API RenderDoc w aplikacji, aby można było przechwytywać klatki z wnętrza emulatora.\nNaciśnij F10 podczas działania gry, aby przechwycić jedną klatkę; przechwycone klatki trafiają do user/logs/capture_logs/<TITLE_ID>.\nWymaga zainstalowanego RenderDoc. Spowalnia GPU i zawiesza niektóre tytuły, więc zostaw wyłączone, chyba że debugujesz.",
"Options.Env.GuestImageCpuSync.Desc": "Ponownie przesyłaj powierzchnie gościa, które procesor gry nadpisuje.\nZostaw wyłączone normalnie. Włącz dla tytułów, których powierzchnie rysowane przez CPU nigdy nie docierają na ekran.\nKosztuje wydajność i pogarsza działanie niektórych tytułów, takich jak GTA V.",
"Options.Env.ForceSubmitOrphanPreambles.Desc": "Dostarczaj preambuły buforów poleceń GPU nawet gdy ich docelowa kolejka ich nie odbiera.\nZostaw wyłączone normalnie. Włącz dla tytułów, które zawieszają się w oczekiwaniu na sygnał z GPU.",
"Options.DefaultProfile.Label": "Domyślna nazwa profilu",
"Options.DefaultProfile.Desc": "Nazwa używana, gdy gra prosi o wprowadzenie tekstu. Domyślnie Sharp.",
"Options.Section.Emulation": "EMULACJA",
"Options.Section.Logging": "DZIENNIK ZDARZEŃ",
"Options.Section.Launcher": "LAUNCHER",
"Options.Section.Rendering": "RENDEROWANIE",
"Options.Section.Display": "WYŚWIETLANIE",
"Options.Graphics": "Grafika",
"Options.RenderResolution.Label": "Rozdzielczość wewnętrzna",
"Options.RenderResolution.Desc": "Renderuj cele pozaekranowe poniżej natywnej rozdzielczości i skaluj w górę przy prezentacji. Niższe wartości zamieniają jakość obrazu na zapas GPU; działa od następnego uruchomienia.",
"Options.RenderResolution.Native": "100% (natywna)",
"Options.WindowMode.Label": "Tryb okna",
"Options.WindowMode.Desc": "Zwykłe okno, bezramkowe na pulpicie lub pełny ekran wyłączny.",
"Options.WindowMode.Windowed": "W oknie",
"Options.WindowMode.Borderless": "Bezramkowe",
"Options.WindowMode.Exclusive": "Wyłączne",
"Options.Resolution.Label": "Rozdzielczość",
"Options.Resolution.Desc": "Początkowy rozmiar okna lub rozdzielczość pełnego ekranu wyłącznego.",
"Options.Display.Label": "Wyświetlacz",
"Options.Display.Desc": "Monitor używany do centrowania i trybu pełnoekranowego.",
"Options.RefreshRate.Label": "Częstotliwość odświeżania",
"Options.RefreshRate.Desc": "Częstotliwość odświeżania w trybie pełnego ekranu wyłącznego. Automatyczna wybiera najbliższy tryb.",
"Options.RefreshRate.Automatic": "Automatyczna",
"Options.Scaling.Label": "Skalowanie",
"Options.Scaling.Desc": "Skaluj natywny obraz gościa bez zmiany jego wewnętrznej rozdzielczości.",
"Options.Scaling.Fit": "Dopasuj",
"Options.Scaling.Cover": "Wypełnij",
"Options.Scaling.Stretch": "Rozciągnij",
"Options.Scaling.Integer": "Całkowitoliczbowe",
"Options.VSync.Label": "VSync",
"Options.VSync.Desc": "Używaj prezentacji FIFO dla obrazu bez rozrywania.",
"Options.Hdr.Label": "Wyjście HDR",
"Options.Hdr.Desc": "Używaj HDR, gdy wybrany wyświetlacz i backend graficzny to obsługują. Automatyczny przełącza na SDR.",
"Options.Hdr.Auto": "Automatyczny",
"Options.CpuEngine.Label": "Silnik CPU",
"Options.CpuEngine.Desc": "Silnik wykonawczy używany do uruchamiania kodu gry.",
"Options.CpuEngine.Native": "Natywny",
"Options.Strict.Label": "Ścisłe rozwiązywanie dynlib",
"Options.Strict.Desc": "Przerwij uruchomienie, gdy importowany symbol nie może zostać rozwiązany.",
"Options.LogLevel.Label": "Poziom logowania",
"Options.LogLevel.Desc": "Szczegółowość wyjścia konsoli emulatora.",
"Options.LogLevel.Trace": "Śledzenie",
"Options.LogLevel.Debug": "Debugowanie",
"Options.LogLevel.Info": "Informacja",
"Options.LogLevel.Warning": "Ostrzeżenie",
"Options.LogLevel.Error": "Błąd",
"Options.LogLevel.Critical": "Krytyczny",
"Options.TraceImports.Label": "Limit śledzenia importów",
"Options.TraceImports.Desc": "Śledź pierwszych N importów na moduł (0 = wyłączone).",
"Options.LogToFile.Label": "Loguj do pliku",
"Options.LogToFile.Desc": "Kopiuj wyjście emulatora do pliku logu.",
"Options.LogFilePath.Label": "Ścieżka pliku logu",
"Options.LogFilePath.Default": "Brak niestandardowej ścieżki — logi trafiają do user/logs obok emulatora.",
"Options.LogFilePath.Select": "Wybierz…",
"Options.OverrideLogFile.Label": "Nadpisz plik logu",
"Options.OverrideLogFile.Desc": "Użyj dokładnej ścieżki pliku zamiast dopisywania ID tytułu i znacznika czasu.",
"Options.TitleMusic.Label": "Muzyka tytułowa",
"Options.TitleMusic.Desc": "Odtwarzaj w pętli muzykę podglądu wybranej gry w bibliotece.",
"Options.Discord.Label": "Discord Rich Presence",
"Options.Discord.Desc": "Pokazuj uruchomioną grę na swoim profilu Discord.",
"Options.Language.Label": "Język emulatora",
"Options.Language.Desc": "Język używany w całym launcherze. Działa natychmiast.",
"Common.On": "Włącz",
"Common.Off": "Wyłącz",
"Common.Save": "Zapisz",
"Common.Cancel": "Anuluj",
"Console.Title": "KONSOLA",
"Console.SearchWatermark": "Szukaj...",
"Console.AutoScroll": "Autoprzewijanie",
"Console.Split": "Podziel",
"Console.Copy": "Kopiuj",
"Console.Clear": "Wyczyść",
"Console.WindowTitle": "Konsola SharpEmu",
"Launch.NoGameSelected": "Nie wybrano gry",
"Launch.NoGameHint": "Wybierz grę z biblioteki lub otwórz bezpośrednio plik eboot.bin.",
"Launch.Idle": "Bezczynny",
"Launch.Console": "≡ Konsola",
"Launch.Launch": "▶ Uruchom",
"Launch.Stop": "■ Zatrzymaj",
"Launch.Running": "Działa — {0}",
"Launch.Stopping": "Zatrzymywanie…",
"Launch.Exited": "Zakończono z kodem {0} ({1})",
"Launch.ExeNotFound": "Nie znaleziono pliku wykonywalnego SharpEmu. Najpierw zbuduj projekt SharpEmu.CLI (dotnet build).",
"Launch.LogFile": "Plik logu: {0}",
"Launch.Command": "$ SharpEmu {0}",
"Launch.StartFailed": "Nie udało się uruchomić emulatora: {0}",
"Launch.ProcessExited": "Proces zakończony z kodem {0} ({1}).",
"Exit.Ok": "OK",
"Exit.InvalidArguments": "nieprawidłowe argumenty",
"Exit.EbootNotFound": "nie znaleziono eboot",
"Exit.RuntimeException": "wyjątek czasu wykonania",
"Exit.EmulationError": "błąd emulacji",
"Exit.Unknown": "nieznany",
"Status.EmulatorLocating": "Emulator: lokalizowanie…",
"Status.EmulatorPath": "Emulator: {0}",
"Status.EmulatorNotFound": "Emulator: nie znaleziono pliku wykonywalnego SharpEmu — najpierw zbuduj SharpEmu.CLI.",
"Status.ScanningLibrary": "Skanowanie biblioteki…",
"Status.AddFolderPrompt": "Dodaj folder z grami, aby wypełnić bibliotekę.",
"Status.LibraryScanned": "Biblioteka przeskanowana: {0} gier w {1} folderach.",
"Status.CouldNotOpenFolder": "Nie można otworzyć folderu: {0}",
"Status.CopiedToClipboard": "{0} skopiowano do schowka.",
"Status.RemovedFromLibrary": "Usunięto \u201c{0}\u201d z biblioteki. Dodaj ponownie folder, aby przywrócić.",
"Status.Running": "Uruchomiono {0}",
"Status.Stopping": "Zatrzymywanie…",
"Status.Idle": "Bezczynny",
"Clipboard.Path": "Ścieżka",
"Clipboard.TitleId": "ID tytułu",
"Discord.Playing": "Gra w {0}",
"Discord.Browsing": "Przegląda bibliotekę",
"Dialog.ChooseGameFolder": "Wybierz folder zawierający gry",
"Dialog.OpenExecutable": "Otwórz plik wykonywalny do uruchomienia",
"Dialog.PsExecutables": "Pliki wykonywalne PS",
"Dialog.SaveLogFile": "Wybierz, gdzie zapisać plik logu",
"Dialog.PlainTextFiles": "Pliki tekstowe",
"Dialog.LogFiles": "Pliki logów",
"Options.About" : "O programie",
"About.Github.Label": "GitHub",
"About.Github.Desc": "Kod źródłowy, zgłoszenia i rozwój projektu.",
"About.Github.LatestCommitLabel": "Najnowszy commit",
"About.Github.LatestCommitDescription": "Najnowszy commit na gałęzi głównej",
"About.Discord.Label": "Discord",
"About.Discord.Desc": "Dołącz do społeczności, uzyskaj wsparcie i śledź rozwój.",
"About.GithubButton": "Wspieraj na GitHubie!",
"About.DiscordComingSoon": "Wkrótce",
"Updater.Auto.Label": "Sprawdzaj aktualizacje przy uruchomieniu",
"Updater.Auto.Desc": "Sprawdza GitHub bez opóźniania uruchomienia.",
"Updater.Label": "Aktualizacje",
"Updater.Check": "Sprawdź aktualizacje",
"Updater.DownloadRestart": "Pobierz i uruchom ponownie",
"Updater.Status.Ready": "Obecna wersja: {0}",
"Updater.Status.Checking": "Sprawdzanie aktualizacji…",
"Updater.Status.Current": "Masz najnowszą wersję ({0}).",
"Updater.Status.Available": "Dostępna nowa wersja: {0}",
"Updater.Status.Downloading": "Pobieranie aktualizacji… {0}%",
"Updater.Status.Installing": "Instalowanie aktualizacji…",
"Updater.Status.Timeout": "Sprawdzanie aktualizacji przekroczyło limit 10 sekund.",
"Updater.Status.Failed": "Nie udało się sprawdzić aktualizacji.",
"Updater.Status.ChecksumFailed": "Pobrana aktualizacja nie przeszła weryfikacji SHA-256.",
"Updater.Status.Unsupported": "Automatyczna aktualizacja wymaga wersji dla Windows, Linux lub macOS x64."
}
+11 -8
View File
@@ -12174,15 +12174,18 @@ public static partial class AgcExports
if (dispatchEndX == 0 || dispatchEndY == 0 || dispatchEndZ == 0)
{
// Indirect dispatches read their dimensions from a guest buffer a
// prior GPU dispatch fills. Zero here means that producer has not run
// yet — signal the caller to suspend on the dims buffer and retry,
// rather than dropping the work (which black-screens GPU-driven games
// like Astro Bot). Direct dispatches carry dims inline, so a zero is
// genuinely malformed and still rejected.
if (opcode == ItDispatchIndirect)
// For indirect dispatches (both absolute and base), zero dimensions are a valid outcome
// of GPU culling passes (0 workgroups). VulkanVideoPresenter handles groupCount = 0 as a clean no-op.
if (opcode == ItDispatchIndirect || dispatchSource is "absolute-indirect" or "base-indirect")
{
indirectDimsRetryAddress = dimensionsAddress;
var waveCount = (initiator & (1u << 15)) != 0 ? 32u : 64u;
dispatch = new ComputeDispatch(
0, 0, 0,
0, 0, 0,
waveCount,
IsIndirect: true,
0, 0, 0);
return true;
}
return RejectComputeDispatch(
@@ -13828,16 +13828,10 @@ internal static unsafe class VulkanVideoPresenter
existing.LogicalDepth == depth &&
existing.Type == type &&
existing.MipLevels == mipLevels &&
(!requiresStorage || existing.SupportsStorageUsage) &&
(exactFormatMatch ||
(IsAliasableGuestImageFormat(existing.Format, format) &&
(!requiresStorage || existing.SupportsStorageUsage))))
IsAliasableGuestImageFormat(existing.Format, format)))
{
if (requiresStorage && !existing.SupportsStorageUsage)
{
throw new InvalidOperationException(
$"Guest image 0x{target.Address:X16} was created without storage usage.");
}
existing.IsCpuBacked = false;
existing.CpuContentFingerprint = 0;
if (existing.RenderPass.Handle == 0 &&
@@ -13870,14 +13864,9 @@ internal static unsafe class VulkanVideoPresenter
if (existing.Width == target.Width &&
existing.Height == target.Height &&
existing.MipLevels == mipLevels &&
(!requiresStorage || existing.SupportsStorageUsage) &&
IsCompatibleViewFormat(existing.Format, format))
{
if (requiresStorage && !existing.SupportsStorageUsage)
{
throw new InvalidOperationException(
$"Guest image 0x{target.Address:X16} was created without storage usage.");
}
if (_traceGuestImageEvents)
{
Console.Error.WriteLine(
@@ -13952,10 +13941,11 @@ internal static unsafe class VulkanVideoPresenter
{
if (requiresStorage && !retained.SupportsStorageUsage)
{
throw new InvalidOperationException(
$"Retained guest image 0x{target.Address:X16} was created without storage usage.");
// Do not reuse retained image if it lacks required storage usage
DestroyGuestImage(retained);
}
else
{
retained.IsCpuBacked = false;
retained.CpuContentFingerprint = 0;
_guestImages.Add(target.Address, retained);
@@ -13997,6 +13987,7 @@ internal static unsafe class VulkanVideoPresenter
return retained;
}
}
var imageInfo = new ImageCreateInfo
{
@@ -2366,17 +2366,16 @@ public static partial class Gen5SpirvTranslator
return;
}
// GLOBAL_STORE/LOAD_DWORD(x2/x3/x4) are dword-aligned by the GCN ISA, so read/write dwords directly instead of the per-byte loop.
for (uint index = 0; index < control.DwordCount; index++)
{
var address = index == 0
? byteAddress
: IAdd(byteAddress, UInt(index * sizeof(uint)));
StoreBufferBytes(
var indexedDwordAddress = index == 0
? dwordAddress
: IAdd(dwordAddress, UInt(index));
StoreBufferWord(
bindingIndex,
address,
LoadV(control.VectorData + index),
sizeof(uint),
0);
indexedDwordAddress,
LoadV(control.VectorData + index));
}
});
return true;
@@ -2404,12 +2403,12 @@ public static partial class Gen5SpirvTranslator
for (uint index = 0; index < control.DwordCount; index++)
{
var address = index == 0
? byteAddress
: IAdd(byteAddress, UInt(index * sizeof(uint)));
var indexedDwordAddress = index == 0
? dwordAddress
: IAdd(dwordAddress, UInt(index));
StoreV(
control.VectorData + index,
LoadUnalignedBufferWord(bindingIndex, address));
LoadBufferWord(bindingIndex, indexedDwordAddress));
}
return true;
@@ -2510,17 +2509,16 @@ public static partial class Gen5SpirvTranslator
return;
}
// BUFFER_STORE/LOAD_DWORD(x2/x3/x4) are dword-aligned by the GCN ISA, same as the GLOBAL case above — no per-byte reassembly needed.
for (uint index = 0; index < control.DwordCount; index++)
{
var address = index == 0
? byteAddress
: IAdd(byteAddress, UInt(index * sizeof(uint)));
StoreBufferBytes(
var indexedDwordAddress = index == 0
? dwordAddress
: IAdd(dwordAddress, UInt(index));
StoreBufferWord(
bindingIndex,
address,
LoadV(control.VectorData + index),
sizeof(uint),
0);
indexedDwordAddress,
LoadV(control.VectorData + index));
}
});
@@ -2576,12 +2574,12 @@ public static partial class Gen5SpirvTranslator
for (uint index = 0; index < control.DwordCount; index++)
{
var address = index == 0
? byteAddress
: IAdd(byteAddress, UInt(index * sizeof(uint)));
var indexedDwordAddress = index == 0
? dwordAddress
: IAdd(dwordAddress, UInt(index));
StoreV(
control.VectorData + index,
LoadUnalignedBufferWord(bindingIndex, address));
LoadBufferWord(bindingIndex, indexedDwordAddress));
}
return true;