mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-22 10:56:20 +08:00
[Perf] Behavior-preserving hot-path allocation/LINQ wins (#264)
* [Perf] Gate event-flag tracing so it allocates nothing when disabled TraceEventFlag built its interpolated argument (and, on the wait path, FormatFrameChain + a new StringBuilder(256) in FormatGuestWaitObject plus ~12 guest-memory reads) on every call, then checked the env var inside the method — so every sceKernelSetEventFlag/Clear/Poll/Wait paid a string allocation and an Environment.GetEnvironmentVariable P/Invoke even with tracing off. Cache the flag once in a static readonly bool and gate every call site, matching the semaphore/event-queue pattern. Behavior is unchanged when SHARPEMU_LOG_EVENT_FLAG=1. * [Perf] Hoist IsNoBlockLeaf classification to import-stub setup The leaf-dispatch path called IsNoBlockLeafImport(nid) — a ~30-literal string pattern match — on every leaf import. IsLeaf/NidHash were already precomputed on ImportStubEntry at stub setup; add IsNoBlockLeaf alongside them and read the field in the hot path. Behavior unchanged. * [Perf] Cache trace env-var flags read on hot paths Two SHARPEMU_LOG_* env vars were read via Environment.GetEnvironmentVariable (a P/Invoke + transient string) on hot paths: SHARPEMU_LOG_FIBER on every fiber context transfer, and SHARPEMU_LOG_DIRECT_MEMORY on every direct-memory op (~8 sites). Cache both once — _logFiber alongside the other backend _log* flags, and _traceDirectMemory behind the existing ShouldTraceDirectMemory helper. Behavior unchanged. * [Perf] Avoid per-iteration thread snapshot in the idle pump loop PumpUntilGuestThreadsIdle allocated a full GuestThreadState[] snapshot (via LINQ Values.ToArray()) on every spin just to tally three run-state booleans. Tally them under the lock with an allocation-free helper, and only materialize the snapshot inside the gated (default-off) diagnostic dump. SnapshotGuestThreads now uses Values.CopyTo instead of LINQ. Behavior unchanged. * [Perf] De-LINQ GetPixelColorExportMask on the per-draw path GetPixelColorExportMask ran a Select/OfType/Where/Aggregate chain over all shader instructions, allocating iterators + closures, and is called per render target (twice per draw via CreateRenderState and HasPixelColorExport). Replace with a manual scan producing the identical mask — no allocation, and it removes an authored-LINQ use the repo bans. * [Perf] De-LINQ per-draw render-target selection in AgcExports The bound-render-target selection used Where(...).OrderBy(...).ToArray() (plus a second Where/ToArray fallback) on every translated draw, allocating LINQ iterators + closures. Replace with an explicit filter into a pre-sized list + List.Sort by slot; slots are distinct so this matches the stable OrderBy. Same result, no per-draw LINQ allocations. * [Perf] De-LINQ per-draw render-target validation in the Vulkan presenter SubmitOffscreenTranslatedDraw validated its render targets with targets.Any(...) twice plus targets.Select(a=>a.Address).Distinct().Count() (a per-draw HashSet), and broadcast a single blend with Enumerable.Repeat(...).ToArray(). Targets are <= 8, so replace with manual scans (invalid-target check; combined dimension-mismatch + pairwise aliasing check) and Array.Fill. Same results, no per-draw LINQ allocations. * [Perf] Binary-search VirtualQuery region lookup (SortedList) _mappedRegions was an unordered Dictionary, so TryFindVirtualQueryRegionLocked scanned every region for containment/next — O(n) per sceKernelVirtualQuery and O(n^2) when an allocator walks the address space with the findNext flag. Store regions in a SortedList keyed by base address (every write already uses the region's own address as the key) and find the containing/next region with a binary search over the sorted keys. Also drops a now-redundant Values.OrderBy. Non-overlapping regions assumed (mmap semantics), so only the floor region can contain the query. Behavior-preserving; worth spot-checking VirtualQuery-heavy titles. * [Perf] Remove stray CLI packages.lock.json committed by mistake git add -A in an earlier commit swept in a regenerated src/SharpEmu.CLI/packages.lock.json. main tracks no lock files (central package management, no RestorePackagesWithLockFile), and REUSE.toml no longer covers packages.lock.json, so the committed file failed the REUSE Compliance check. Remove it.
This commit is contained in:
committed by
GitHub
parent
a8be1daca4
commit
c5a82c1065
@@ -598,7 +598,7 @@ public sealed partial class DirectExecutionBackend
|
|||||||
}
|
}
|
||||||
|
|
||||||
*(ulong*)(argPackPtr + 96) = unchecked((ulong)transferStub);
|
*(ulong*)(argPackPtr + 96) = unchecked((ulong)transferStub);
|
||||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_FIBER"), "1", StringComparison.Ordinal))
|
if (_logFiber)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] fiber.context-transfer rip=0x{transferTarget.Rip:X16} " +
|
$"[LOADER][TRACE] fiber.context-transfer rip=0x{transferTarget.Rip:X16} " +
|
||||||
@@ -1292,7 +1292,7 @@ public sealed partial class DirectExecutionBackend
|
|||||||
}
|
}
|
||||||
|
|
||||||
int returnValue;
|
int returnValue;
|
||||||
if (IsNoBlockLeafImport(importStubEntry.Nid))
|
if (importStubEntry.IsNoBlockLeaf)
|
||||||
{
|
{
|
||||||
cpuContext.ClearRaxWriteFlag();
|
cpuContext.ClearRaxWriteFlag();
|
||||||
returnValue = export.Function(cpuContext);
|
returnValue = export.Function(cpuContext);
|
||||||
|
|||||||
@@ -48,6 +48,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
// hashing are hoisted to stub-setup time.
|
// hashing are hoisted to stub-setup time.
|
||||||
public bool IsLeaf { get; }
|
public bool IsLeaf { get; }
|
||||||
|
|
||||||
|
public bool IsNoBlockLeaf { get; }
|
||||||
|
|
||||||
public bool SuppressStrlenTrace { get; }
|
public bool SuppressStrlenTrace { get; }
|
||||||
|
|
||||||
public bool IsLoopGuardBoundary { get; }
|
public bool IsLoopGuardBoundary { get; }
|
||||||
@@ -59,6 +61,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
string nid,
|
string nid,
|
||||||
ExportedFunction? export,
|
ExportedFunction? export,
|
||||||
bool isLeaf,
|
bool isLeaf,
|
||||||
|
bool isNoBlockLeaf,
|
||||||
bool suppressStrlenTrace,
|
bool suppressStrlenTrace,
|
||||||
bool isLoopGuardBoundary,
|
bool isLoopGuardBoundary,
|
||||||
ulong nidHash)
|
ulong nidHash)
|
||||||
@@ -67,6 +70,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
Nid = nid;
|
Nid = nid;
|
||||||
Export = export;
|
Export = export;
|
||||||
IsLeaf = isLeaf;
|
IsLeaf = isLeaf;
|
||||||
|
IsNoBlockLeaf = isNoBlockLeaf;
|
||||||
SuppressStrlenTrace = suppressStrlenTrace;
|
SuppressStrlenTrace = suppressStrlenTrace;
|
||||||
IsLoopGuardBoundary = isLoopGuardBoundary;
|
IsLoopGuardBoundary = isLoopGuardBoundary;
|
||||||
NidHash = nidHash;
|
NidHash = nidHash;
|
||||||
@@ -318,6 +322,8 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
private bool _logUsleep;
|
private bool _logUsleep;
|
||||||
|
|
||||||
|
private bool _logFiber;
|
||||||
|
|
||||||
private bool _logBootstrap;
|
private bool _logBootstrap;
|
||||||
|
|
||||||
private bool _logAllImports;
|
private bool _logAllImports;
|
||||||
@@ -1067,6 +1073,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
_ignoredGuestInt41Count = 0;
|
_ignoredGuestInt41Count = 0;
|
||||||
_logGuestThreads = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUEST_THREADS"), "1", StringComparison.Ordinal);
|
_logGuestThreads = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_GUEST_THREADS"), "1", StringComparison.Ordinal);
|
||||||
_logUsleep = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal);
|
_logUsleep = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_USLEEP"), "1", StringComparison.Ordinal);
|
||||||
|
_logFiber = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_FIBER"), "1", StringComparison.Ordinal);
|
||||||
_logBootstrap = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_BOOTSTRAP"), "1", StringComparison.Ordinal);
|
_logBootstrap = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_BOOTSTRAP"), "1", StringComparison.Ordinal);
|
||||||
_logAllImports = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_ALL_IMPORTS"), "1", StringComparison.Ordinal);
|
_logAllImports = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_ALL_IMPORTS"), "1", StringComparison.Ordinal);
|
||||||
_logImportFrames = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_FRAMES"), "1", StringComparison.Ordinal);
|
_logImportFrames = string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_IMPORT_FRAMES"), "1", StringComparison.Ordinal);
|
||||||
@@ -1164,6 +1171,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
text2,
|
text2,
|
||||||
resolvedExport,
|
resolvedExport,
|
||||||
IsLeafImport(text2),
|
IsLeafImport(text2),
|
||||||
|
IsNoBlockLeafImport(text2),
|
||||||
ShouldSuppressStrlenTrace(text2),
|
ShouldSuppressStrlenTrace(text2),
|
||||||
IsImportLoopGuardBoundary(text2),
|
IsImportLoopGuardBoundary(text2),
|
||||||
StableHash64(text2));
|
StableHash64(text2));
|
||||||
@@ -3264,31 +3272,15 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
{
|
{
|
||||||
Pump(callerContext, reason);
|
Pump(callerContext, reason);
|
||||||
|
|
||||||
var threads = SnapshotGuestThreads();
|
// Tally run states under the lock without allocating a snapshot every
|
||||||
if (threads.Length == 0)
|
// spin (this loop can iterate rapidly); the full snapshot is only
|
||||||
|
// materialized for the gated diagnostic dump below.
|
||||||
|
GetGuestThreadActivity(out var threadCount, out var hasReadyThread, out var hasRunningThread, out var hasBlockedThread);
|
||||||
|
if (threadCount == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasReadyThread = false;
|
|
||||||
var hasRunningThread = false;
|
|
||||||
var hasBlockedThread = false;
|
|
||||||
foreach (var thread in threads)
|
|
||||||
{
|
|
||||||
switch (thread.State)
|
|
||||||
{
|
|
||||||
case GuestThreadRunState.Ready:
|
|
||||||
hasReadyThread = true;
|
|
||||||
break;
|
|
||||||
case GuestThreadRunState.Running:
|
|
||||||
hasRunningThread = true;
|
|
||||||
break;
|
|
||||||
case GuestThreadRunState.Blocked:
|
|
||||||
hasBlockedThread = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (hasReadyThread)
|
if (hasReadyThread)
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -3301,7 +3293,7 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
|
|
||||||
if (_logGuestThreads && Stopwatch.GetTimestamp() >= nextSnapshotTimestamp)
|
if (_logGuestThreads && Stopwatch.GetTimestamp() >= nextSnapshotTimestamp)
|
||||||
{
|
{
|
||||||
foreach (var thread in threads)
|
foreach (var thread in SnapshotGuestThreads())
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] guest_thread.idle_wait reason={reason} handle=0x{thread.ThreadHandle:X16} " +
|
$"[LOADER][TRACE] guest_thread.idle_wait reason={reason} handle=0x{thread.ThreadHandle:X16} " +
|
||||||
@@ -3321,7 +3313,36 @@ public sealed unsafe partial class DirectExecutionBackend : INativeCpuBackend, I
|
|||||||
{
|
{
|
||||||
using (LockGate("SnapshotGuestThreads"))
|
using (LockGate("SnapshotGuestThreads"))
|
||||||
{
|
{
|
||||||
return _guestThreads.Values.ToArray();
|
var snapshot = new GuestThreadState[_guestThreads.Count];
|
||||||
|
_guestThreads.Values.CopyTo(snapshot, 0);
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allocation-free run-state tally for the idle spin loop.
|
||||||
|
private void GetGuestThreadActivity(out int count, out bool hasReady, out bool hasRunning, out bool hasBlocked)
|
||||||
|
{
|
||||||
|
hasReady = false;
|
||||||
|
hasRunning = false;
|
||||||
|
hasBlocked = false;
|
||||||
|
using (LockGate("GetGuestThreadActivity"))
|
||||||
|
{
|
||||||
|
count = _guestThreads.Count;
|
||||||
|
foreach (var thread in _guestThreads.Values)
|
||||||
|
{
|
||||||
|
switch (thread.State)
|
||||||
|
{
|
||||||
|
case GuestThreadRunState.Ready:
|
||||||
|
hasReady = true;
|
||||||
|
break;
|
||||||
|
case GuestThreadRunState.Running:
|
||||||
|
hasRunning = true;
|
||||||
|
break;
|
||||||
|
case GuestThreadRunState.Blocked:
|
||||||
|
hasBlocked = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5937,16 +5937,30 @@ public static partial class AgcExports
|
|||||||
// draw a multi-render-target G-buffer (up to eight slots) in one pass.
|
// draw a multi-render-target G-buffer (up to eight slots) in one pass.
|
||||||
// Fall back to slot 0 if we cannot match any export to a bound target.
|
// Fall back to slot 0 if we cannot match any export to a bound target.
|
||||||
var allBoundTargets = GetRenderTargets(state.CxRegisters);
|
var allBoundTargets = GetRenderTargets(state.CxRegisters);
|
||||||
var renderTargets = allBoundTargets
|
// At most 8 slots; a manual filter avoids the per-draw LINQ iterator/
|
||||||
.Where(target => HasPixelColorExport(pixelState, target.Slot))
|
// closure allocations. Slots are distinct, so sorting by slot is stable.
|
||||||
.OrderBy(target => target.Slot)
|
var selectedTargets = new List<RenderTargetDescriptor>(allBoundTargets.Count);
|
||||||
.ToArray();
|
foreach (var target in allBoundTargets)
|
||||||
if (renderTargets.Length == 0)
|
|
||||||
{
|
{
|
||||||
renderTargets = allBoundTargets
|
if (HasPixelColorExport(pixelState, target.Slot))
|
||||||
.Where(target => target.Slot == 0)
|
{
|
||||||
.ToArray();
|
selectedTargets.Add(target);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (selectedTargets.Count == 0)
|
||||||
|
{
|
||||||
|
foreach (var target in allBoundTargets)
|
||||||
|
{
|
||||||
|
if (target.Slot == 0)
|
||||||
|
{
|
||||||
|
selectedTargets.Add(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
selectedTargets.Sort(static (left, right) => left.Slot.CompareTo(right.Slot));
|
||||||
|
var renderTargets = selectedTargets.ToArray();
|
||||||
if (_traceAgcShader && allBoundTargets.Count > 1)
|
if (_traceAgcShader && allBoundTargets.Count > 1)
|
||||||
{
|
{
|
||||||
TraceAgcShader(
|
TraceAgcShader(
|
||||||
@@ -6398,12 +6412,23 @@ public static partial class AgcExports
|
|||||||
private static bool HasPixelColorExport(Gen5ShaderState state, uint target) =>
|
private static bool HasPixelColorExport(Gen5ShaderState state, uint target) =>
|
||||||
GetPixelColorExportMask(state, target) != 0;
|
GetPixelColorExportMask(state, target) != 0;
|
||||||
|
|
||||||
private static uint GetPixelColorExportMask(Gen5ShaderState state, uint target) =>
|
private static uint GetPixelColorExportMask(Gen5ShaderState state, uint target)
|
||||||
state.Program.Instructions
|
{
|
||||||
.Select(instruction => instruction.Control)
|
// Called per render target (twice per draw via CreateRenderState +
|
||||||
.OfType<Gen5ExportControl>()
|
// HasPixelColorExport); a manual scan avoids the per-call LINQ iterator
|
||||||
.Where(export => export.Target == target)
|
// and closure allocations. Same result as the previous
|
||||||
.Aggregate(0u, (mask, export) => mask | (export.EnableMask & 0xFu));
|
// Select/OfType/Where/Aggregate chain.
|
||||||
|
var mask = 0u;
|
||||||
|
foreach (var instruction in state.Program.Instructions)
|
||||||
|
{
|
||||||
|
if (instruction.Control is Gen5ExportControl export && export.Target == target)
|
||||||
|
{
|
||||||
|
mask |= export.EnableMask & 0xFu;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mask;
|
||||||
|
}
|
||||||
|
|
||||||
private static uint GetInterpolatedAttributeCount(Gen5ShaderState state)
|
private static uint GetInterpolatedAttributeCount(Gen5ShaderState state)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,6 +25,11 @@ public static class KernelEventFlagCompatExports
|
|||||||
private static readonly ConcurrentDictionary<ulong, EventFlagState> _eventFlags = new();
|
private static readonly ConcurrentDictionary<ulong, EventFlagState> _eventFlags = new();
|
||||||
private static long _nextEventFlagHandle = 1;
|
private static long _nextEventFlagHandle = 1;
|
||||||
|
|
||||||
|
// Cached once: gating every call site avoids building the interpolated
|
||||||
|
// trace string (and FormatFrameChain/FormatGuestWaitObject) when disabled.
|
||||||
|
private static readonly bool _traceEventFlag = string.Equals(
|
||||||
|
Environment.GetEnvironmentVariable("SHARPEMU_LOG_EVENT_FLAG"), "1", StringComparison.Ordinal);
|
||||||
|
|
||||||
private sealed class EventFlagState
|
private sealed class EventFlagState
|
||||||
{
|
{
|
||||||
public required string Name { get; init; }
|
public required string Name { get; init; }
|
||||||
@@ -79,7 +84,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
TraceEventFlag($"create handle=0x{handle:X16} name='{name}' attr=0x{attributes:X2} bits=0x{initialPattern:X16}");
|
if (_traceEventFlag) TraceEventFlag($"create handle=0x{handle:X16} name='{name}' attr=0x{attributes:X2} bits=0x{initialPattern:X16}");
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +106,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
Monitor.PulseAll(state.Gate);
|
Monitor.PulseAll(state.Gate);
|
||||||
}
|
}
|
||||||
|
|
||||||
TraceEventFlag($"delete handle=0x{handle:X16} name='{state.Name}'");
|
if (_traceEventFlag) TraceEventFlag($"delete handle=0x{handle:X16} name='{state.Name}'");
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -124,7 +129,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
{
|
{
|
||||||
state.Bits |= pattern;
|
state.Bits |= pattern;
|
||||||
Monitor.PulseAll(state.Gate);
|
Monitor.PulseAll(state.Gate);
|
||||||
TraceEventFlag($"set handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}");
|
if (_traceEventFlag) TraceEventFlag($"set handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}");
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetEventFlagWakeKey(handle));
|
_ = GuestThreadExecution.Scheduler?.WakeBlockedThreads(GetEventFlagWakeKey(handle));
|
||||||
@@ -148,7 +153,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
lock (state.Gate)
|
lock (state.Gate)
|
||||||
{
|
{
|
||||||
state.Bits &= pattern;
|
state.Bits &= pattern;
|
||||||
TraceEventFlag($"clear handle=0x{handle:X16} mask=0x{pattern:X16} bits=0x{state.Bits:X16}");
|
if (_traceEventFlag) TraceEventFlag($"clear handle=0x{handle:X16} mask=0x{pattern:X16} bits=0x{state.Bits:X16}");
|
||||||
}
|
}
|
||||||
|
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
@@ -189,7 +194,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
ApplyClearMode(state, pattern, waitMode);
|
ApplyClearMode(state, pattern, waitMode);
|
||||||
TraceEventFlag($"poll handle=0x{handle:X16} pattern=0x{pattern:X16} mode=0x{waitMode:X2} bits=0x{state.Bits:X16}");
|
if (_traceEventFlag) TraceEventFlag($"poll handle=0x{handle:X16} pattern=0x{pattern:X16} mode=0x{waitMode:X2} bits=0x{state.Bits:X16}");
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -287,8 +292,8 @@ public static class KernelEventFlagCompatExports
|
|||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
deadline);
|
deadline);
|
||||||
TraceEventFlag($"wait-unsatisfied handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} block={requestedBlock} ret=0x{returnRip:X16} frames={FormatFrameChain(ctx)}");
|
if (_traceEventFlag) TraceEventFlag($"wait-unsatisfied handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} block={requestedBlock} ret=0x{returnRip:X16} frames={FormatFrameChain(ctx)}");
|
||||||
TraceEventFlag($"wait-object handle=0x{handle:X16} name='{state.Name}' {FormatGuestWaitObject(ctx)}");
|
if (_traceEventFlag) TraceEventFlag($"wait-object handle=0x{handle:X16} name='{state.Name}' {FormatGuestWaitObject(ctx)}");
|
||||||
if (!requestedBlock)
|
if (!requestedBlock)
|
||||||
{
|
{
|
||||||
var scheduler = GuestThreadExecution.Scheduler;
|
var scheduler = GuestThreadExecution.Scheduler;
|
||||||
@@ -298,7 +303,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.WaitingThreads++;
|
state.WaitingThreads++;
|
||||||
TraceEventFlag($"wait-pump handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} ret=0x{returnRip:X16}");
|
if (_traceEventFlag) TraceEventFlag($"wait-pump handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} ret=0x{returnRip:X16}");
|
||||||
var releaseWaiter = true;
|
var releaseWaiter = true;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -318,7 +323,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
{
|
{
|
||||||
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
||||||
releaseWaiter = false;
|
releaseWaiter = false;
|
||||||
TraceEventFlag($"wait-wake handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} waiters={state.WaitingThreads} ret=0x{returnRip:X16}");
|
if (_traceEventFlag) TraceEventFlag($"wait-wake handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} waiters={state.WaitingThreads} ret=0x{returnRip:X16}");
|
||||||
return SetReturn(ctx, pumpedWaitResult);
|
return SetReturn(ctx, pumpedWaitResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +334,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
releaseWaiter = false;
|
releaseWaiter = false;
|
||||||
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
_ = TryWriteUInt32(ctx, timeoutAddress, 0);
|
||||||
_ = TryWriteResultPattern(ctx, resultAddress, state.Bits);
|
_ = TryWriteResultPattern(ctx, resultAddress, state.Bits);
|
||||||
TraceEventFlag($"wait-timeout handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}");
|
if (_traceEventFlag) TraceEventFlag($"wait-timeout handle=0x{handle:X16} pattern=0x{pattern:X16} bits=0x{state.Bits:X16} ret=0x{returnRip:X16}");
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_ERROR_TIMED_OUT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +351,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.WaitingThreads++;
|
state.WaitingThreads++;
|
||||||
TraceEventFlag($"wait-block handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} ret=0x{returnRip:X16}");
|
if (_traceEventFlag) TraceEventFlag($"wait-block handle=0x{handle:X16} pattern=0x{pattern:X16} waiters={state.WaitingThreads} guest_thread=0x{currentGuestThread:X16} fiber=0x{currentFiber:X16} managed={managedThread} ret=0x{returnRip:X16}");
|
||||||
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
return SetReturn(ctx, OrbisGen2Result.ORBIS_GEN2_OK);
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -381,7 +386,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
state.Bits = setPattern;
|
state.Bits = setPattern;
|
||||||
state.WaitingThreads = 0;
|
state.WaitingThreads = 0;
|
||||||
Monitor.PulseAll(state.Gate);
|
Monitor.PulseAll(state.Gate);
|
||||||
TraceEventFlag(
|
if (_traceEventFlag) TraceEventFlag(
|
||||||
$"cancel handle=0x{handle:X16} bits=0x{setPattern:X16} " +
|
$"cancel handle=0x{handle:X16} bits=0x{setPattern:X16} " +
|
||||||
$"guest_thread=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} ret=0x{GetCurrentReturnRip():X16}");
|
$"guest_thread=0x{GuestThreadExecution.CurrentGuestThreadHandle:X16} ret=0x{GetCurrentReturnRip():X16}");
|
||||||
}
|
}
|
||||||
@@ -476,7 +481,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
}
|
}
|
||||||
|
|
||||||
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
state.WaitingThreads = Math.Max(0, state.WaitingThreads - 1);
|
||||||
TraceEventFlag(
|
if (_traceEventFlag) TraceEventFlag(
|
||||||
$"wait-wake pattern=0x{pattern:X16} mode=0x{waitMode:X2} bits=0x{state.Bits:X16} waiters={state.WaitingThreads}");
|
$"wait-wake pattern=0x{pattern:X16} mode=0x{waitMode:X2} bits=0x{state.Bits:X16} waiters={state.WaitingThreads}");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -568,7 +573,7 @@ public static class KernelEventFlagCompatExports
|
|||||||
|
|
||||||
private static void TraceEventFlag(string message)
|
private static void TraceEventFlag(string message)
|
||||||
{
|
{
|
||||||
if (string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_EVENT_FLAG"), "1", StringComparison.Ordinal))
|
if (_traceEventFlag)
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine($"[LOADER][TRACE] event_flag.{message}");
|
Console.Error.WriteLine($"[LOADER][TRACE] event_flag.{message}");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -102,7 +102,11 @@ public static partial class KernelMemoryCompatExports
|
|||||||
private static readonly object _guestMountGate = new();
|
private static readonly object _guestMountGate = new();
|
||||||
private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new();
|
private static readonly Dictionary<ulong, DirectAllocation> _directAllocations = new();
|
||||||
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new();
|
private static readonly Dictionary<ulong, LibcHeapAllocation> _libcAllocations = new();
|
||||||
private static readonly Dictionary<ulong, MappedRegion> _mappedRegions = new();
|
// Keyed by (and kept sorted on) region base address so VirtualQuery can find a
|
||||||
|
// containing/next region with a binary search instead of an O(n) scan. Every
|
||||||
|
// write uses the region's own Address as the key (see AddMappedRegionSliceLocked
|
||||||
|
// and the mmap sites), so Values enumerate in ascending address order.
|
||||||
|
private static readonly SortedList<ulong, MappedRegion> _mappedRegions = new();
|
||||||
private static readonly Dictionary<ulong, string> _mappedRegionNames = new();
|
private static readonly Dictionary<ulong, string> _mappedRegionNames = new();
|
||||||
private static readonly Dictionary<string, string> _guestMounts = new(StringComparer.OrdinalIgnoreCase);
|
private static readonly Dictionary<string, string> _guestMounts = new(StringComparer.OrdinalIgnoreCase);
|
||||||
private static readonly HashSet<string> _tracedStatResults = new(StringComparer.Ordinal);
|
private static readonly HashSet<string> _tracedStatResults = new(StringComparer.Ordinal);
|
||||||
@@ -5477,7 +5481,9 @@ public static partial class KernelMemoryCompatExports
|
|||||||
|
|
||||||
var affected = new List<MappedRegion>();
|
var affected = new List<MappedRegion>();
|
||||||
var cursor = address;
|
var cursor = address;
|
||||||
foreach (var region in _mappedRegions.Values.OrderBy(static region => region.Address))
|
// _mappedRegions is a SortedList keyed by address, so Values already
|
||||||
|
// enumerate in ascending address order.
|
||||||
|
foreach (var region in _mappedRegions.Values)
|
||||||
{
|
{
|
||||||
if (!TryAddU64(region.Address, region.Length, out var regionEnd) || regionEnd <= cursor)
|
if (!TryAddU64(region.Address, region.Length, out var regionEnd) || regionEnd <= cursor)
|
||||||
{
|
{
|
||||||
@@ -5638,9 +5644,33 @@ public static partial class KernelMemoryCompatExports
|
|||||||
private static bool TryFindVirtualQueryRegionLocked(ulong queryAddress, bool findNext, out MappedRegion region)
|
private static bool TryFindVirtualQueryRegionLocked(ulong queryAddress, bool findNext, out MappedRegion region)
|
||||||
{
|
{
|
||||||
region = default;
|
region = default;
|
||||||
var foundNext = false;
|
var keys = _mappedRegions.Keys;
|
||||||
foreach (var candidate in _mappedRegions.Values)
|
var values = _mappedRegions.Values;
|
||||||
|
var count = keys.Count;
|
||||||
|
|
||||||
|
// First index whose region address is >= queryAddress.
|
||||||
|
var lo = 0;
|
||||||
|
var hi = count;
|
||||||
|
while (lo < hi)
|
||||||
{
|
{
|
||||||
|
var mid = (int)(((uint)lo + (uint)hi) >> 1);
|
||||||
|
if (keys[mid] < queryAddress)
|
||||||
|
{
|
||||||
|
lo = mid + 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
hi = mid;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Regions do not overlap, so only the one with the greatest base address
|
||||||
|
// <= queryAddress can contain it — index lo when it starts exactly at
|
||||||
|
// queryAddress, otherwise lo - 1.
|
||||||
|
var floorIndex = (lo < count && keys[lo] == queryAddress) ? lo : lo - 1;
|
||||||
|
if (floorIndex >= 0)
|
||||||
|
{
|
||||||
|
var candidate = values[floorIndex];
|
||||||
if (TryAddU64(candidate.Address, candidate.Length, out var candidateEnd) &&
|
if (TryAddU64(candidate.Address, candidate.Length, out var candidateEnd) &&
|
||||||
queryAddress >= candidate.Address &&
|
queryAddress >= candidate.Address &&
|
||||||
queryAddress < candidateEnd)
|
queryAddress < candidateEnd)
|
||||||
@@ -5648,20 +5678,16 @@ public static partial class KernelMemoryCompatExports
|
|||||||
region = candidate;
|
region = candidate;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!findNext || candidate.Address < queryAddress)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundNext || candidate.Address < region.Address)
|
|
||||||
{
|
|
||||||
region = candidate;
|
|
||||||
foundNext = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return foundNext;
|
// findNext: the region with the smallest base address >= queryAddress.
|
||||||
|
if (findNext && lo < count)
|
||||||
|
{
|
||||||
|
region = values[lo];
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void TraceDirectMemoryCall(
|
private static void TraceDirectMemoryCall(
|
||||||
@@ -5690,10 +5716,12 @@ public static partial class KernelMemoryCompatExports
|
|||||||
$"[LOADER][TRACE] {operation}: ret=0x{returnRip:X16} len=0x{length:X16} align=0x{alignment:X16} type=0x{memoryType:X8} out=0x{outAddress:X16} selected=0x{selectedAddress:X16} result={result?.ToString() ?? "<pending>"}");
|
$"[LOADER][TRACE] {operation}: ret=0x{returnRip:X16} len=0x{length:X16} align=0x{alignment:X16} type=0x{memoryType:X8} out=0x{outAddress:X16} selected=0x{selectedAddress:X16} result={result?.ToString() ?? "<pending>"}");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool ShouldTraceDirectMemory()
|
// Cached once so the ~8 direct-memory call sites don't each do a
|
||||||
{
|
// GetEnvironmentVariable P/Invoke per operation.
|
||||||
return string.Equals(Environment.GetEnvironmentVariable("SHARPEMU_LOG_DIRECT_MEMORY"), "1", StringComparison.Ordinal);
|
private static readonly bool _traceDirectMemory = string.Equals(
|
||||||
}
|
Environment.GetEnvironmentVariable("SHARPEMU_LOG_DIRECT_MEMORY"), "1", StringComparison.Ordinal);
|
||||||
|
|
||||||
|
private static bool ShouldTraceDirectMemory() => _traceDirectMemory;
|
||||||
|
|
||||||
private static bool TryReleaseDirectMemoryRangeLocked(ulong start, ulong length)
|
private static bool TryReleaseDirectMemoryRangeLocked(ulong start, ulong length)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -608,6 +608,42 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
shaderAddress);
|
shaderAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manual scans (targets are <= 8) so the per-draw validation does not
|
||||||
|
// allocate LINQ iterators/closures or a Distinct HashSet.
|
||||||
|
private static bool AnyRenderTargetInvalid(IReadOnlyList<GuestRenderTarget> targets)
|
||||||
|
{
|
||||||
|
foreach (var target in targets)
|
||||||
|
{
|
||||||
|
if (target.Address == 0 || target.Width == 0 || target.Height == 0)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RenderTargetsMismatchedOrAliased(IReadOnlyList<GuestRenderTarget> targets, GuestRenderTarget first)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < targets.Count; i++)
|
||||||
|
{
|
||||||
|
if (targets[i].Width != first.Width || targets[i].Height != first.Height)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var j = i + 1; j < targets.Count; j++)
|
||||||
|
{
|
||||||
|
if (targets[i].Address == targets[j].Address)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
public static void SubmitOffscreenTranslatedDraw(
|
public static void SubmitOffscreenTranslatedDraw(
|
||||||
byte[] pixelSpirv,
|
byte[] pixelSpirv,
|
||||||
IReadOnlyList<GuestDrawTexture> textures,
|
IReadOnlyList<GuestDrawTexture> textures,
|
||||||
@@ -627,16 +663,13 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
if (pixelSpirv.Length == 0 ||
|
if (pixelSpirv.Length == 0 ||
|
||||||
targets.Count == 0 ||
|
targets.Count == 0 ||
|
||||||
targets.Count > 8 ||
|
targets.Count > 8 ||
|
||||||
targets.Any(target =>
|
AnyRenderTargetInvalid(targets))
|
||||||
target.Address == 0 || target.Width == 0 || target.Height == 0))
|
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var firstTarget = targets[0];
|
var firstTarget = targets[0];
|
||||||
if (targets.Any(target =>
|
if (RenderTargetsMismatchedOrAliased(targets, firstTarget))
|
||||||
target.Width != firstTarget.Width || target.Height != firstTarget.Height) ||
|
|
||||||
targets.Select(target => target.Address).Distinct().Count() != targets.Count)
|
|
||||||
{
|
{
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
"[LOADER][WARN] Vulkan skipped MRT draw with mismatched dimensions or aliased targets.");
|
"[LOADER][WARN] Vulkan skipped MRT draw with mismatched dimensions or aliased targets.");
|
||||||
@@ -654,9 +687,11 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
var effectiveRenderState = renderState ?? GuestRenderState.Default;
|
var effectiveRenderState = renderState ?? GuestRenderState.Default;
|
||||||
if (effectiveRenderState.Blends.Count == 1 && targets.Count > 1)
|
if (effectiveRenderState.Blends.Count == 1 && targets.Count > 1)
|
||||||
{
|
{
|
||||||
|
var broadcastBlends = new GuestBlendState[targets.Count];
|
||||||
|
Array.Fill(broadcastBlends, effectiveRenderState.Blends[0]);
|
||||||
effectiveRenderState = effectiveRenderState with
|
effectiveRenderState = effectiveRenderState with
|
||||||
{
|
{
|
||||||
Blends = Enumerable.Repeat(effectiveRenderState.Blends[0], targets.Count).ToArray(),
|
Blends = broadcastBlends,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
lock (_gate)
|
lock (_gate)
|
||||||
|
|||||||
Reference in New Issue
Block a user