mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-31 15:09:42 +08:00
Fix/deadcells crash (#262)
* Boot compatibility fixes for UE titles, GUI toggles, and DeS render/boot work Checkpoint of the Monster Truck Championship and Demon's Souls boot work. Each piece is independently useful and verified against the titles. - playgo: scePlayGoGetLocus now returns BAD_CHUNK_ID for chunk ids outside the known set, matching real firmware. Titles enumerate chunk ids until that error; answering OK for every id made the scan wrap the ushort range and spin forever. Missing-sidecar and no-app0 fallbacks report a fully-installed single chunk 0 so scePlayGoOpen keeps succeeding. - kernel: restore the SHARPEMU_WRITABLE_APP0 opt-in. Unpackaged UE dumps write their Saved tree under /app0 during PS5 component init and treat the denial as a fatal boot error. - pad: accept handle 0 as the primary pad across all pad calls. Real firmware hands out small non-negative handles and some titles read state with handle 0. - bthid: env-gated experiment hooks for the Thrustmaster wheel middleware investigation (fail-only-RegisterCallback modes and a synthetic enumeration callback with a zeroed event struct). All default off. - gui: add SHARPEMU_LOG_IO and SHARPEMU_WRITABLE_APP0 toggles to the Environment tab. - videoout: per-swapchain-image render-finished semaphores (the shared semaphore raced the swapchain); whole-mip-chain layout init for offscreen guest images (sampled binds read mips stuck in Undefined); GPU-resident texture availability now canonicalizes through the texture format table and accepts compatibility-class aliases, cutting per-frame CPU texture re-reads (143 GB -> 55 GB per 300 s in Demon's Souls, 0.2 -> 0.5 fps). - hle: add sceSystemServiceGetNoticeScreenSkipFlag, sceSystemServiceGetMainAppTitleId (title id published from the runtime), and sceNpWebApi2CreateUserContext (refuses so the online layer backs off). - rtc: SHARPEMU_RTC_PROBE_RANGE diagnostic dumps the code around a busy-wait caller of sceRtcGetCurrentTick once; costs nothing when unset. * [ShaderCompiler] Fix VReadlaneB32 scalar destination field The scalar destination lives in the low vdst byte (bits 0-7); it was read from bits 8-14, the VOP3B carry-out field readlane does not have, sending every readlane result to s0. Verified against raw gfx10 encodings and LLVM's assembler tests (v_readlane_b32 s5, v1, s2 -> low byte 0x05). * [VideoOut] Survive device loss and flip-order asserts without dying Two ways a frame could take down the whole presenter: - Device loss between any two Vulkan calls in a frame unwound the window thread, and the Dispose-time fence check then threw again, masking the original error. Catch the loss at the frame boundary, retire presentations and guest submissions whose fences can never signal, and keep the window loop pumping so the game (audio, logic) carries on. - The ordered-flip capture invariant is violated ~100 times per run by Demon's Souls (PPSA01342); on debug builds the Debug.Assert fail-fasts the process with nothing in the log. Downgrade it to a once-per-version warning until the capture/wait ordering is understood. * Fix Dead Cells shader cache regression --------- Co-authored-by: StealUrKill <35749471+StealUrKill@users.noreply.github.com>
This commit is contained in:
@@ -4160,6 +4160,48 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_submitTimeline;
|
||||
}
|
||||
|
||||
private void TransitionNewGuestImageToSampled(Image image, uint mipLevels)
|
||||
{
|
||||
var commandBuffer = AllocateGuestCommandBuffer();
|
||||
var beginInfo = new CommandBufferBeginInfo
|
||||
{
|
||||
SType = StructureType.CommandBufferBeginInfo,
|
||||
Flags = CommandBufferUsageFlags.OneTimeSubmitBit,
|
||||
};
|
||||
Check(
|
||||
_vk.BeginCommandBuffer(commandBuffer, &beginInfo),
|
||||
"vkBeginCommandBuffer(guest image init)");
|
||||
var barrier = new ImageMemoryBarrier
|
||||
{
|
||||
SType = StructureType.ImageMemoryBarrier,
|
||||
SrcAccessMask = 0,
|
||||
DstAccessMask = AccessFlags.ShaderReadBit,
|
||||
OldLayout = ImageLayout.Undefined,
|
||||
NewLayout = ImageLayout.ShaderReadOnlyOptimal,
|
||||
SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
|
||||
Image = image,
|
||||
SubresourceRange = ColorSubresourceRange(0, mipLevels),
|
||||
};
|
||||
_vk.CmdPipelineBarrier(
|
||||
commandBuffer,
|
||||
PipelineStageFlags.TopOfPipeBit,
|
||||
PipelineStageFlags.AllCommandsBit,
|
||||
0,
|
||||
0,
|
||||
null,
|
||||
0,
|
||||
null,
|
||||
1,
|
||||
&barrier);
|
||||
Check(
|
||||
_vk.EndCommandBuffer(commandBuffer),
|
||||
"vkEndCommandBuffer(guest image init)");
|
||||
// Same-queue submission order makes the transition visible to any
|
||||
// later use of the image; no CPU-side wait is needed.
|
||||
SubmitGuestCommandBuffer(commandBuffer, [], []);
|
||||
}
|
||||
|
||||
private void EnsureGuestSubmissionCapacity()
|
||||
{
|
||||
CollectCompletedGuestSubmissions(waitForOldest: false);
|
||||
@@ -4220,23 +4262,43 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
|
||||
Check(result, $"vkWaitForFences(guest: {oldest.DebugName})");
|
||||
if (result == Result.ErrorDeviceLost)
|
||||
{
|
||||
_deviceLost = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Check(result, $"vkWaitForFences(guest: {oldest.DebugName})");
|
||||
}
|
||||
}
|
||||
|
||||
while (_pendingGuestSubmissions.TryPeek(out var submission))
|
||||
{
|
||||
var status = _vk.GetFenceStatus(_device, submission.Fence);
|
||||
if (status == Result.NotReady)
|
||||
if (status == Result.NotReady && !_deviceLost)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
Check(status, $"vkGetFenceStatus(guest: {submission.DebugName})");
|
||||
if (status == Result.ErrorDeviceLost)
|
||||
{
|
||||
// Pending fences never signal on a lost device; retire the
|
||||
// submission anyway so teardown and back-pressure survive.
|
||||
_deviceLost = true;
|
||||
}
|
||||
else if (status != Result.NotReady)
|
||||
{
|
||||
Check(status, $"vkGetFenceStatus(guest: {submission.DebugName})");
|
||||
}
|
||||
|
||||
_pendingGuestSubmissions.Dequeue();
|
||||
|
||||
foreach (var image in submission.TraceImages)
|
||||
if (!_deviceLost)
|
||||
{
|
||||
TraceGuestImageContents(image);
|
||||
foreach (var image in submission.TraceImages)
|
||||
{
|
||||
TraceGuestImageContents(image);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var resources in submission.Resources)
|
||||
@@ -4514,13 +4576,20 @@ internal static unsafe class VulkanVideoPresenter
|
||||
$"queue={_activeGuestQueue.Name} submission={_activeGuestQueue.SubmissionId} " +
|
||||
$"handle={work.VideoOutHandle} index={work.DisplayBufferIndex} " +
|
||||
$"capture_complete={(captured ? 1 : 0)}");
|
||||
#if DEBUG
|
||||
System.Diagnostics.Debug.Assert(
|
||||
work.Version == 0 || captured,
|
||||
"An ordered wait-safe marker must execute after its flip capture.");
|
||||
#endif
|
||||
// Demon's Souls executes wait-safe markers before their flip capture;
|
||||
// an assert here would fail-fast the process, so warn once instead.
|
||||
// Dedup on a flag, not the (per-frame-unique) version, to bound growth.
|
||||
if (work.Version != 0 && !captured && !_loggedFlipWaitOrderViolation)
|
||||
{
|
||||
_loggedFlipWaitOrderViolation = true;
|
||||
Console.Error.WriteLine(
|
||||
$"[LOADER][WARN] vk.flip_wait_order version={work.Version} " +
|
||||
"executed before its flip capture; continuing.");
|
||||
}
|
||||
}
|
||||
|
||||
private bool _loggedFlipWaitOrderViolation;
|
||||
|
||||
private GuestImageResource CreateGuestFlipSnapshot(
|
||||
GuestImageResource source,
|
||||
long version)
|
||||
@@ -8300,7 +8369,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return (properties.OptimalTilingFeatures & FormatFeatureFlags.ColorAttachmentBit) != 0;
|
||||
}
|
||||
|
||||
private static Format GetTextureFormat(uint format, uint numberType) =>
|
||||
internal static Format GetTextureFormat(uint format, uint numberType) =>
|
||||
(format, numberType) switch
|
||||
{
|
||||
(9, _) => Format.A2B10G10R10UnormPack32,
|
||||
@@ -10170,6 +10239,9 @@ internal static unsafe class VulkanVideoPresenter
|
||||
_vk.AllocateMemory(_device, &allocationInfo, null, out var memory),
|
||||
"vkAllocateMemory(offscreen)");
|
||||
Check(_vk.BindImageMemory(_device, image, memory, 0), "vkBindImageMemory(offscreen)");
|
||||
// Rendering and uploads only define the mips they touch; define the whole
|
||||
// chain once so full-chain sampled binds never read Undefined layout.
|
||||
TransitionNewGuestImageToSampled(image, mipLevels);
|
||||
|
||||
var viewInfo = new ImageViewCreateInfo
|
||||
{
|
||||
@@ -10937,7 +11009,7 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return view;
|
||||
}
|
||||
|
||||
private static bool IsCompatibleViewFormat(Format imageFormat, Format viewFormat)
|
||||
internal static bool IsCompatibleViewFormat(Format imageFormat, Format viewFormat)
|
||||
{
|
||||
if (imageFormat == viewFormat)
|
||||
{
|
||||
@@ -11003,6 +11075,23 @@ internal static unsafe class VulkanVideoPresenter
|
||||
}
|
||||
|
||||
private void Render(double _)
|
||||
{
|
||||
try
|
||||
{
|
||||
RenderCore();
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
// Device loss can strike between any two Vulkan calls in the frame;
|
||||
// keep the window loop pumping instead of tearing the presenter down.
|
||||
if (!TryMarkDeviceLost(exception))
|
||||
{
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void RenderCore()
|
||||
{
|
||||
if (Volatile.Read(ref _presenterCloseRequested))
|
||||
{
|
||||
@@ -11016,6 +11105,18 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
|
||||
if (_deviceLost)
|
||||
{
|
||||
// Drain queued work so producers aren't back-pressured, then
|
||||
// return without any Vulkan call (fences never signal post-loss).
|
||||
while (TryTakeGuestWork(out var lostWork))
|
||||
{
|
||||
CompleteGuestWork(lostWork);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Reuse of a frame slot waits only on that slot's fence, keeping
|
||||
// up to MaxFramesInFlight frames pipelined between CPU and GPU.
|
||||
var frameSlot = _currentFrameSlot;
|
||||
@@ -13836,20 +13937,38 @@ internal static unsafe class VulkanVideoPresenter
|
||||
return;
|
||||
}
|
||||
|
||||
if (result == Result.ErrorDeviceLost)
|
||||
{
|
||||
throw new VulkanDeviceLostException(operation);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"{operation} failed with {result}.");
|
||||
}
|
||||
|
||||
private static void Check(Result result, string operation)
|
||||
{
|
||||
if (result == Result.ErrorDeviceLost)
|
||||
{
|
||||
throw new VulkanDeviceLostException(operation);
|
||||
}
|
||||
|
||||
if (result != Result.Success)
|
||||
{
|
||||
throw new InvalidOperationException($"{operation} failed with {result}.");
|
||||
}
|
||||
}
|
||||
|
||||
// Typed so the frame-boundary catch can recognize device loss without
|
||||
// depending on the exact wording of the exception message.
|
||||
private sealed class VulkanDeviceLostException(string operation)
|
||||
: InvalidOperationException($"{operation} failed with {Result.ErrorDeviceLost}.");
|
||||
|
||||
private bool TryMarkDeviceLost(Exception exception)
|
||||
{
|
||||
if (!exception.Message.Contains(nameof(Result.ErrorDeviceLost), StringComparison.Ordinal))
|
||||
// Prefer the typed signal; fall back to the message for losses that
|
||||
// surface through other layers (e.g. Silk.NET bindings).
|
||||
if (exception is not VulkanDeviceLostException &&
|
||||
!exception.Message.Contains(nameof(Result.ErrorDeviceLost), StringComparison.Ordinal))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user