mirror of
https://github.com/par274/sharpemu.git
synced 2026-07-30 22:49:53 +08:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b804c84ad8 | |||
| c306f01630 | |||
| 6b09f1dd22 |
@@ -242,7 +242,9 @@ public partial class MainWindow : Window
|
|||||||
EnvLogNpToggle.IsCheckedChanged += (_, _) =>
|
EnvLogNpToggle.IsCheckedChanged += (_, _) =>
|
||||||
SetEnvironmentToggle("SHARPEMU_LOG_NP", EnvLogNpToggle.IsChecked == true);
|
SetEnvironmentToggle("SHARPEMU_LOG_NP", EnvLogNpToggle.IsChecked == true);
|
||||||
EnvGuestImageCpuSyncToggle.IsCheckedChanged += (_, _) =>
|
EnvGuestImageCpuSyncToggle.IsCheckedChanged += (_, _) =>
|
||||||
SetGuestImageCpuSync(EnvGuestImageCpuSyncToggle.IsChecked == true);
|
SetEnvironmentToggle(
|
||||||
|
"SHARPEMU_GUEST_IMAGE_CPU_SYNC",
|
||||||
|
EnvGuestImageCpuSyncToggle.IsChecked == true);
|
||||||
LanguageBox.SelectionChanged += (_, _) => OnLanguageChanged();
|
LanguageBox.SelectionChanged += (_, _) => OnLanguageChanged();
|
||||||
|
|
||||||
GameList.AddHandler(ContextRequestedEvent, OnGameContextRequested, RoutingStrategies.Tunnel);
|
GameList.AddHandler(ContextRequestedEvent, OnGameContextRequested, RoutingStrategies.Tunnel);
|
||||||
@@ -853,10 +855,8 @@ public partial class MainWindow : Window
|
|||||||
EnvLogDirectMemoryToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_DIRECT_MEMORY");
|
EnvLogDirectMemoryToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_DIRECT_MEMORY");
|
||||||
EnvLogIoToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_IO");
|
EnvLogIoToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_IO");
|
||||||
EnvLogNpToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_NP");
|
EnvLogNpToggle.IsChecked = _settings.EnvironmentToggles.Contains("SHARPEMU_LOG_NP");
|
||||||
EnvGuestImageCpuSyncToggle.IsChecked = IsEnvironmentEnabled(
|
EnvGuestImageCpuSyncToggle.IsChecked =
|
||||||
_settings.EnvironmentToggles,
|
_settings.EnvironmentToggles.Contains("SHARPEMU_GUEST_IMAGE_CPU_SYNC");
|
||||||
"SHARPEMU_GUEST_IMAGE_CPU_SYNC",
|
|
||||||
defaultValue: true);
|
|
||||||
WindowModeBox.SelectedIndex = ChoiceIndex(_settings.WindowMode, "Windowed", "Borderless", "Exclusive");
|
WindowModeBox.SelectedIndex = ChoiceIndex(_settings.WindowMode, "Windowed", "Borderless", "Exclusive");
|
||||||
LoadHostDisplayOptions();
|
LoadHostDisplayOptions();
|
||||||
ScalingModeBox.SelectedIndex = ChoiceIndex(_settings.ScalingMode, "Fit", "Cover", "Stretch", "Integer");
|
ScalingModeBox.SelectedIndex = ChoiceIndex(_settings.ScalingMode, "Fit", "Cover", "Stretch", "Integer");
|
||||||
@@ -1083,40 +1083,6 @@ public partial class MainWindow : Window
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void SetGuestImageCpuSync(bool enabled)
|
|
||||||
{
|
|
||||||
const string name = "SHARPEMU_GUEST_IMAGE_CPU_SYNC";
|
|
||||||
_settings.EnvironmentToggles.RemoveAll(entry =>
|
|
||||||
string.Equals(entry, name, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(entry, name + "=0", StringComparison.OrdinalIgnoreCase));
|
|
||||||
if (!enabled)
|
|
||||||
{
|
|
||||||
_settings.EnvironmentToggles.Add(name + "=0");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsEnvironmentEnabled(
|
|
||||||
IEnumerable<string> entries,
|
|
||||||
string name,
|
|
||||||
bool defaultValue)
|
|
||||||
{
|
|
||||||
foreach (var entry in entries)
|
|
||||||
{
|
|
||||||
if (string.Equals(entry, name + "=0", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (string.Equals(entry, name, StringComparison.OrdinalIgnoreCase) ||
|
|
||||||
string.Equals(entry, name + "=1", StringComparison.OrdinalIgnoreCase))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
private string SelectedLogLevel()
|
private string SelectedLogLevel()
|
||||||
{
|
{
|
||||||
return LogLevelBox.SelectedIndex switch
|
return LogLevelBox.SelectedIndex switch
|
||||||
|
|||||||
@@ -241,7 +241,7 @@ public sealed class PerGameSettingsDialog : Window
|
|||||||
_hdrMode.SelectedItem = ChoiceOrDefault(HdrModes, global.HdrMode, "Auto");
|
_hdrMode.SelectedItem = ChoiceOrDefault(HdrModes, global.HdrMode, "Auto");
|
||||||
foreach (var (name, box) in _envBoxes)
|
foreach (var (name, box) in _envBoxes)
|
||||||
{
|
{
|
||||||
box.IsChecked = IsEnvironmentEnabled(global.EnvironmentToggles, name, defaultValue: name == "SHARPEMU_GUEST_IMAGE_CPU_SYNC");
|
box.IsChecked = IsEnvironmentEnabled(global.EnvironmentToggles, name, defaultValue: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing is null)
|
if (existing is null)
|
||||||
@@ -295,7 +295,7 @@ public sealed class PerGameSettingsDialog : Window
|
|||||||
_envRow.IsOverridden = true;
|
_envRow.IsOverridden = true;
|
||||||
foreach (var (name, box) in _envBoxes)
|
foreach (var (name, box) in _envBoxes)
|
||||||
{
|
{
|
||||||
box.IsChecked = IsEnvironmentEnabled(env, name, defaultValue: name == "SHARPEMU_GUEST_IMAGE_CPU_SYNC");
|
box.IsChecked = IsEnvironmentEnabled(env, name, defaultValue: false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -396,17 +396,10 @@ public sealed class PerGameSettingsDialog : Window
|
|||||||
|
|
||||||
private List<string> BuildEnvironmentEntries()
|
private List<string> BuildEnvironmentEntries()
|
||||||
{
|
{
|
||||||
const string guestImageCpuSync = "SHARPEMU_GUEST_IMAGE_CPU_SYNC";
|
return _envBoxes
|
||||||
var entries = _envBoxes
|
.Where(entry => entry.Box.IsChecked == true)
|
||||||
.Where(entry => entry.Name != guestImageCpuSync && entry.Box.IsChecked == true)
|
|
||||||
.Select(entry => entry.Name)
|
.Select(entry => entry.Name)
|
||||||
.ToList();
|
.ToList();
|
||||||
if (_envBoxes.First(entry => entry.Name == guestImageCpuSync).Box.IsChecked != true)
|
|
||||||
{
|
|
||||||
entries.Add(guestImageCpuSync + "=0");
|
|
||||||
}
|
|
||||||
|
|
||||||
return entries;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsEnvironmentEnabled(
|
private static bool IsEnvironmentEnabled(
|
||||||
|
|||||||
@@ -87,13 +87,10 @@ public static unsafe class GuestImageWriteTracker
|
|||||||
|
|
||||||
private static RangeSnapshot _rangeSnapshot = RangeSnapshot.Empty;
|
private static RangeSnapshot _rangeSnapshot = RangeSnapshot.Empty;
|
||||||
|
|
||||||
// CPU-written guest image synchronization is the compatible default. A few
|
|
||||||
// titles (currently GTA V) require the lower-overhead watch-only path and
|
|
||||||
// opt out explicitly with SHARPEMU_GUEST_IMAGE_CPU_SYNC=0.
|
|
||||||
private static readonly bool _enabled =
|
private static readonly bool _enabled =
|
||||||
!string.Equals(
|
string.Equals(
|
||||||
Environment.GetEnvironmentVariable("SHARPEMU_GUEST_IMAGE_CPU_SYNC"),
|
Environment.GetEnvironmentVariable("SHARPEMU_GUEST_IMAGE_CPU_SYNC"),
|
||||||
"0",
|
"1",
|
||||||
StringComparison.Ordinal);
|
StringComparison.Ordinal);
|
||||||
private static readonly (bool Wildcard, ulong[] Addresses) _lifetimeTraceFilter =
|
private static readonly (bool Wildcard, ulong[] Addresses) _lifetimeTraceFilter =
|
||||||
ParseAddressList(Environment.GetEnvironmentVariable("SHARPEMU_TRACE_GUEST_IMAGE_ADDRS"));
|
ParseAddressList(Environment.GetEnvironmentVariable("SHARPEMU_TRACE_GUEST_IMAGE_ADDRS"));
|
||||||
|
|||||||
@@ -42,6 +42,15 @@ public static class AudioOut2Exports
|
|||||||
// with ContextMemoryAlignment (0x100).
|
// with ContextMemoryAlignment (0x100).
|
||||||
private const int PortStateSize = 0x20;
|
private const int PortStateSize = 0x20;
|
||||||
private const int SpeakerInfoSize = 0x20;
|
private const int SpeakerInfoSize = 0x20;
|
||||||
|
|
||||||
|
private static readonly string _stackOutBufferModes =
|
||||||
|
Environment.GetEnvironmentVariable("SHARPEMU_AUDIO_OUT2_STACK_WRITES") ?? "1";
|
||||||
|
|
||||||
|
private static bool AllowStackOut(string which) =>
|
||||||
|
string.Equals(_stackOutBufferModes, "1", StringComparison.Ordinal) ||
|
||||||
|
_stackOutBufferModes
|
||||||
|
.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries)
|
||||||
|
.Contains(which, StringComparer.OrdinalIgnoreCase);
|
||||||
private const int PortParamSize = 0x40;
|
private const int PortParamSize = 0x40;
|
||||||
private const int AttributeEntrySize = 0x18;
|
private const int AttributeEntrySize = 0x18;
|
||||||
private const uint PortAttributeIdPcm = 0;
|
private const uint PortAttributeIdPcm = 0;
|
||||||
@@ -51,6 +60,7 @@ public static class AudioOut2Exports
|
|||||||
private static int _nextPortId;
|
private static int _nextPortId;
|
||||||
private static long _pushTraceCount;
|
private static long _pushTraceCount;
|
||||||
private static long _submitTraceCount;
|
private static long _submitTraceCount;
|
||||||
|
private static long _submitSkipTraceCount;
|
||||||
private static long _attributePcmTraceCount;
|
private static long _attributePcmTraceCount;
|
||||||
|
|
||||||
private static readonly ConcurrentDictionary<ulong, byte> SpeakerArrays = new();
|
private static readonly ConcurrentDictionary<ulong, byte> SpeakerArrays = new();
|
||||||
@@ -126,6 +136,9 @@ public static class AudioOut2Exports
|
|||||||
public uint SamplingFrequency { get; }
|
public uint SamplingFrequency { get; }
|
||||||
public uint GrainSamples { get; }
|
public uint GrainSamples { get; }
|
||||||
public ulong PcmAddress;
|
public ulong PcmAddress;
|
||||||
|
|
||||||
|
public int PcmPending;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Two host streams: primary FMOD context (menus) and everything else
|
// Two host streams: primary FMOD context (menus) and everything else
|
||||||
@@ -194,6 +207,19 @@ public static class AudioOut2Exports
|
|||||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var contextMemorySize = (ulong)AudioOut2ContextMemorySize;
|
||||||
|
Span<byte> param = stackalloc byte[AudioOut2ContextParamSize];
|
||||||
|
if (ctx.Memory.TryRead(paramAddress, param))
|
||||||
|
{
|
||||||
|
var queueDepth = BinaryPrimitives.ReadUInt32LittleEndian(param[0x0C..]);
|
||||||
|
if (queueDepth == 0)
|
||||||
|
{
|
||||||
|
queueDepth = 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
contextMemorySize = checked(0x10000UL + (queueDepth * 0x590UL));
|
||||||
|
}
|
||||||
|
|
||||||
// Heap: {size, alignment} (16 bytes), matching sceAudioPropagationSystemQueryMemory.
|
// Heap: {size, alignment} (16 bytes), matching sceAudioPropagationSystemQueryMemory.
|
||||||
// Stack: SIZE ONLY as a full ulong (8 bytes). Writing alignment at +8 is how
|
// Stack: SIZE ONLY as a full ulong (8 bytes). Writing alignment at +8 is how
|
||||||
// [rbp-0x30] became 0x100 on GTA V Enhanced. Do NOT shrink this to uint32 —
|
// [rbp-0x30] became 0x100 on GTA V Enhanced. Do NOT shrink this to uint32 —
|
||||||
@@ -202,10 +228,10 @@ public static class AudioOut2Exports
|
|||||||
if (IsGuestStackAddress(memoryInfoAddress))
|
if (IsGuestStackAddress(memoryInfoAddress))
|
||||||
{
|
{
|
||||||
Span<byte> sizeOnly = stackalloc byte[sizeof(ulong)];
|
Span<byte> sizeOnly = stackalloc byte[sizeof(ulong)];
|
||||||
BinaryPrimitives.WriteUInt64LittleEndian(sizeOnly, AudioOut2ContextMemorySize);
|
BinaryPrimitives.WriteUInt64LittleEndian(sizeOnly, contextMemorySize);
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] audio_out2.context-query-memory stack-size-only " +
|
$"[LOADER][TRACE] audio_out2.context-query-memory stack-size-only " +
|
||||||
$"out=0x{memoryInfoAddress:X} size=0x{AudioOut2ContextMemorySize:X}");
|
$"out=0x{memoryInfoAddress:X} size=0x{contextMemorySize:X}");
|
||||||
return ctx.Memory.TryWrite(memoryInfoAddress, sizeOnly)
|
return ctx.Memory.TryWrite(memoryInfoAddress, sizeOnly)
|
||||||
? SetReturn(ctx, 0)
|
? SetReturn(ctx, 0)
|
||||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
@@ -213,11 +239,11 @@ public static class AudioOut2Exports
|
|||||||
|
|
||||||
Span<byte> memoryInfo = stackalloc byte[0x10];
|
Span<byte> memoryInfo = stackalloc byte[0x10];
|
||||||
memoryInfo.Clear();
|
memoryInfo.Clear();
|
||||||
BinaryPrimitives.WriteUInt64LittleEndian(memoryInfo[0x00..], AudioOut2ContextMemorySize);
|
BinaryPrimitives.WriteUInt64LittleEndian(memoryInfo[0x00..], contextMemorySize);
|
||||||
BinaryPrimitives.WriteUInt64LittleEndian(memoryInfo[0x08..], AudioOut2ContextMemoryAlignment);
|
BinaryPrimitives.WriteUInt64LittleEndian(memoryInfo[0x08..], AudioOut2ContextMemoryAlignment);
|
||||||
Console.Error.WriteLine(
|
Console.Error.WriteLine(
|
||||||
$"[LOADER][TRACE] audio_out2.context-query-memory out=0x{memoryInfoAddress:X} " +
|
$"[LOADER][TRACE] audio_out2.context-query-memory out=0x{memoryInfoAddress:X} " +
|
||||||
$"size=0x{AudioOut2ContextMemorySize:X} align=0x{AudioOut2ContextMemoryAlignment:X}");
|
$"size=0x{contextMemorySize:X} align=0x{AudioOut2ContextMemoryAlignment:X}");
|
||||||
return ctx.Memory.TryWrite(memoryInfoAddress, memoryInfo)
|
return ctx.Memory.TryWrite(memoryInfoAddress, memoryInfo)
|
||||||
? SetReturn(ctx, 0)
|
? SetReturn(ctx, 0)
|
||||||
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
@@ -325,7 +351,10 @@ public static class AudioOut2Exports
|
|||||||
{
|
{
|
||||||
if (Contexts.TryGetValue(ctx[CpuRegister.Rdi], out var state))
|
if (Contexts.TryGetValue(ctx[CpuRegister.Rdi], out var state))
|
||||||
{
|
{
|
||||||
state.PaceAdvance();
|
if (!TrySubmitContextAudio(ctx, state))
|
||||||
|
{
|
||||||
|
state.PaceAdvance();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return SetReturn(ctx, 0);
|
return SetReturn(ctx, 0);
|
||||||
@@ -342,14 +371,16 @@ public static class AudioOut2Exports
|
|||||||
// uint64 write into a stack slot at [rbp-0x14] next to the canary at
|
// uint64 write into a stack slot at [rbp-0x14] next to the canary at
|
||||||
// [rbp-0x10] zeroed the canary low half and killed Bink Snd @ eboot+0xAE36.
|
// [rbp-0x10] zeroed the canary low half and killed Bink Snd @ eboot+0xAE36.
|
||||||
var outLevelAddress = ctx[CpuRegister.Rsi];
|
var outLevelAddress = ctx[CpuRegister.Rsi];
|
||||||
|
var outAvailableAddress = ctx[CpuRegister.Rdx];
|
||||||
if (outLevelAddress == 0)
|
if (outLevelAddress == 0)
|
||||||
{
|
{
|
||||||
outLevelAddress = ctx[CpuRegister.Rdx];
|
outLevelAddress = outAvailableAddress;
|
||||||
|
outAvailableAddress = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Span<byte> level = stackalloc byte[sizeof(uint)];
|
||||||
if (outLevelAddress != 0)
|
if (outLevelAddress != 0)
|
||||||
{
|
{
|
||||||
Span<byte> level = stackalloc byte[sizeof(uint)];
|
|
||||||
BinaryPrimitives.WriteUInt32LittleEndian(level, 0);
|
BinaryPrimitives.WriteUInt32LittleEndian(level, 0);
|
||||||
if (!ctx.Memory.TryWrite(outLevelAddress, level))
|
if (!ctx.Memory.TryWrite(outLevelAddress, level))
|
||||||
{
|
{
|
||||||
@@ -357,6 +388,20 @@ public static class AudioOut2Exports
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (outAvailableAddress != 0 &&
|
||||||
|
outAvailableAddress != outLevelAddress &&
|
||||||
|
IsWritableOutBuffer(outAvailableAddress))
|
||||||
|
{
|
||||||
|
var available = Contexts.TryGetValue(ctx[CpuRegister.Rdi], out var context)
|
||||||
|
? context.QueueDepth
|
||||||
|
: 4u;
|
||||||
|
BinaryPrimitives.WriteUInt32LittleEndian(level, available);
|
||||||
|
if (!ctx.Memory.TryWrite(outAvailableAddress, level))
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return SetReturn(ctx, 0);
|
return SetReturn(ctx, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -418,6 +463,7 @@ public static class AudioOut2Exports
|
|||||||
}
|
}
|
||||||
|
|
||||||
port.PcmAddress = BinaryPrimitives.ReadUInt64LittleEndian(pcm);
|
port.PcmAddress = BinaryPrimitives.ReadUInt64LittleEndian(pcm);
|
||||||
|
Volatile.Write(ref port.PcmPending, port.PcmAddress != 0 ? 1 : 0);
|
||||||
var n = Interlocked.Increment(ref _attributePcmTraceCount);
|
var n = Interlocked.Increment(ref _attributePcmTraceCount);
|
||||||
if (n <= 8 || n % 500 == 0)
|
if (n <= 8 || n % 500 == 0)
|
||||||
{
|
{
|
||||||
@@ -475,13 +521,14 @@ public static class AudioOut2Exports
|
|||||||
// Handle encodes only the low type byte; PortState keeps the full type
|
// Handle encodes only the low type byte; PortState keeps the full type
|
||||||
// so object ports (0x01xx) can still be filtered at submit time.
|
// so object ports (0x01xx) can still be filtered at submit time.
|
||||||
var handle = 0x2000_0000UL | ((ulong)(portType & 0xFF) << 16) | portId;
|
var handle = 0x2000_0000UL | ((ulong)(portType & 0xFF) << 16) | portId;
|
||||||
Ports[handle] = new PortState(
|
var portState = new PortState(
|
||||||
handle,
|
handle,
|
||||||
contextHandle,
|
contextHandle,
|
||||||
portType,
|
portType,
|
||||||
dataFormat,
|
dataFormat,
|
||||||
samplingFrequency,
|
samplingFrequency,
|
||||||
grainSamples);
|
grainSamples);
|
||||||
|
Ports[handle] = portState;
|
||||||
if (!TryWriteUInt64(ctx, outPortAddress, handle))
|
if (!TryWriteUInt64(ctx, outPortAddress, handle))
|
||||||
{
|
{
|
||||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
@@ -512,7 +559,8 @@ public static class AudioOut2Exports
|
|||||||
// caller frames / canaries (state=0x7FFFDE1FF688 right before fail).
|
// caller frames / canaries (state=0x7FFFDE1FF688 right before fail).
|
||||||
// Heap outs still get a real state blob even when the handle wasn't
|
// Heap outs still get a real state blob even when the handle wasn't
|
||||||
// minted by PortCreate — this title synthesizes port ids itself.
|
// minted by PortCreate — this title synthesizes port ids itself.
|
||||||
if (IsGuestStackAddress(stateAddress))
|
if (IsGuestStackAddress(stateAddress) &&
|
||||||
|
!(AllowStackOut("portstate") && Ports.ContainsKey(portHandle)))
|
||||||
{
|
{
|
||||||
TraceAudioOut2(
|
TraceAudioOut2(
|
||||||
$"port-get-state skip-stack handle=0x{portHandle:X} state=0x{stateAddress:X}");
|
$"port-get-state skip-stack handle=0x{portHandle:X} state=0x{stateAddress:X}");
|
||||||
@@ -545,6 +593,48 @@ public static class AudioOut2Exports
|
|||||||
return SetReturn(ctx, 0);
|
return SetReturn(ctx, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "4dq2rblWlg0",
|
||||||
|
ExportName = "sceAudioOut2ContextSetAttributes",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAudioOut2")]
|
||||||
|
public static int AudioOut2ContextSetAttributes(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var attributeAddress = ctx[CpuRegister.Rsi];
|
||||||
|
var count = unchecked((uint)ctx[CpuRegister.Rdx]);
|
||||||
|
return SetReturn(
|
||||||
|
ctx,
|
||||||
|
count != 0 && attributeAddress == 0
|
||||||
|
? (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT
|
||||||
|
: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SysAbiExport(
|
||||||
|
Nid = "bkBN+CMLwRc",
|
||||||
|
ExportName = "sceAudioOut2GetSystemState",
|
||||||
|
Target = Generation.Gen5,
|
||||||
|
LibraryName = "libSceAudioOut2")]
|
||||||
|
public static int AudioOut2GetSystemState(CpuContext ctx)
|
||||||
|
{
|
||||||
|
var stateAddress = ctx[CpuRegister.Rdi];
|
||||||
|
if (stateAddress == 0)
|
||||||
|
{
|
||||||
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IsGuestStackAddress(stateAddress) && !AllowStackOut("systemstate"))
|
||||||
|
{
|
||||||
|
TraceAudioOut2($"get-system-state skip-stack out=0x{stateAddress:X}");
|
||||||
|
return SetReturn(ctx, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
Span<byte> state = stackalloc byte[0x40];
|
||||||
|
state.Clear();
|
||||||
|
return ctx.Memory.TryWrite(stateAddress, state)
|
||||||
|
? SetReturn(ctx, 0)
|
||||||
|
: SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_MEMORY_FAULT);
|
||||||
|
}
|
||||||
|
|
||||||
// rdi=out buffer, rsi=type/flag (not a pointer). Fixed-size write only.
|
// rdi=out buffer, rsi=type/flag (not a pointer). Fixed-size write only.
|
||||||
[SysAbiExport(
|
[SysAbiExport(
|
||||||
Nid = "DImz2Ft9E2g",
|
Nid = "DImz2Ft9E2g",
|
||||||
@@ -559,8 +649,7 @@ public static class AudioOut2Exports
|
|||||||
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
return SetReturn(ctx, (int)OrbisGen2Result.ORBIS_GEN2_ERROR_INVALID_ARGUMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same rule as PortGetState — never bulk-write speaker info onto the stack.
|
if (IsGuestStackAddress(infoAddress) && !AllowStackOut("speaker"))
|
||||||
if (IsGuestStackAddress(infoAddress))
|
|
||||||
{
|
{
|
||||||
TraceAudioOut2($"get-speaker-info skip-stack out=0x{infoAddress:X}");
|
TraceAudioOut2($"get-speaker-info skip-stack out=0x{infoAddress:X}");
|
||||||
return SetReturn(ctx, 0);
|
return SetReturn(ctx, 0);
|
||||||
@@ -788,25 +877,8 @@ public static class AudioOut2Exports
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize submits per process so ArrayPool buffers stay private; primary
|
|
||||||
// and secondary devices still receive independent PCM (no digital sum).
|
|
||||||
lock (HostSubmitGate)
|
lock (HostSubmitGate)
|
||||||
{
|
{
|
||||||
if (!TryPickMainBed(context.Handle, out var mainPort))
|
|
||||||
{
|
|
||||||
mainPort = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!TryPickAuxBed(context.Handle, out var auxPort))
|
|
||||||
{
|
|
||||||
auxPort = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mainPort is null && auxPort is null)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var mix = ArrayPool<float>.Shared.Rent(frames * 2);
|
var mix = ArrayPool<float>.Shared.Rent(frames * 2);
|
||||||
var source = ArrayPool<byte>.Shared.Rent(frames * 16 * sizeof(float));
|
var source = ArrayPool<byte>.Shared.Rent(frames * 16 * sizeof(float));
|
||||||
var output = ArrayPool<byte>.Shared.Rent(frames * AudioPcmConversion.OutputFrameSize);
|
var output = ArrayPool<byte>.Shared.Rent(frames * AudioPcmConversion.OutputFrameSize);
|
||||||
@@ -814,16 +886,11 @@ public static class AudioOut2Exports
|
|||||||
{
|
{
|
||||||
mix.AsSpan(0, frames * 2).Clear();
|
mix.AsSpan(0, frames * 2).Clear();
|
||||||
var mixedPorts = 0;
|
var mixedPorts = 0;
|
||||||
ulong lastPort = 0;
|
foreach (var port in Ports.Values)
|
||||||
uint lastFormat = 0;
|
|
||||||
var lastChannels = 0;
|
|
||||||
|
|
||||||
// At most one MAIN/BGM + one AUX on this context. Menus stay on
|
|
||||||
// MAIN; intro/Bink rides AUX without stacking every bed.
|
|
||||||
for (var bed = 0; bed < 2; bed++)
|
|
||||||
{
|
{
|
||||||
var port = bed == 0 ? mainPort : auxPort;
|
if (port.ContextHandle != context.Handle ||
|
||||||
if (port is null ||
|
port.PcmAddress == 0 ||
|
||||||
|
Interlocked.Exchange(ref port.PcmPending, 0) == 0 ||
|
||||||
!TryDecodeDataFormat(port.DataFormat, out var ch, out var bps, out var isFloat))
|
!TryDecodeDataFormat(port.DataFormat, out var ch, out var bps, out var isFloat))
|
||||||
{
|
{
|
||||||
continue;
|
continue;
|
||||||
@@ -850,9 +917,6 @@ public static class AudioOut2Exports
|
|||||||
isFloat,
|
isFloat,
|
||||||
additive: mixedPorts > 0);
|
additive: mixedPorts > 0);
|
||||||
mixedPorts++;
|
mixedPorts++;
|
||||||
lastPort = port.Handle;
|
|
||||||
lastFormat = port.DataFormat;
|
|
||||||
lastChannels = ch;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mixedPorts == 0)
|
if (mixedPorts == 0)
|
||||||
@@ -862,22 +926,11 @@ public static class AudioOut2Exports
|
|||||||
|
|
||||||
var outputSpan = output.AsSpan(0, frames * AudioPcmConversion.OutputFrameSize);
|
var outputSpan = output.AsSpan(0, frames * AudioPcmConversion.OutputFrameSize);
|
||||||
var peak = 0f;
|
var peak = 0f;
|
||||||
var any = false;
|
|
||||||
for (var frame = 0; frame < frames; frame++)
|
for (var frame = 0; frame < frames; frame++)
|
||||||
{
|
{
|
||||||
var left = Math.Clamp(mix[frame * 2], -1f, 1f);
|
var left = Math.Clamp(mix[frame * 2], -1f, 1f);
|
||||||
var right = Math.Clamp(mix[(frame * 2) + 1], -1f, 1f);
|
var right = Math.Clamp(mix[(frame * 2) + 1], -1f, 1f);
|
||||||
var framePeak = Math.Max(Math.Abs(left), Math.Abs(right));
|
peak = Math.Max(peak, Math.Max(Math.Abs(left), Math.Abs(right)));
|
||||||
if (framePeak > peak)
|
|
||||||
{
|
|
||||||
peak = framePeak;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (framePeak > 1e-7f)
|
|
||||||
{
|
|
||||||
any = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
BinaryPrimitives.WriteInt16LittleEndian(
|
BinaryPrimitives.WriteInt16LittleEndian(
|
||||||
outputSpan[(frame * AudioPcmConversion.OutputFrameSize)..],
|
outputSpan[(frame * AudioPcmConversion.OutputFrameSize)..],
|
||||||
FloatToPcm16(left));
|
FloatToPcm16(left));
|
||||||
@@ -886,13 +939,6 @@ public static class AudioOut2Exports
|
|||||||
FloatToPcm16(right));
|
FloatToPcm16(right));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!any)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bind primary only after a real grain so silent early contexts
|
|
||||||
// do not steal the menu device.
|
|
||||||
var backend = ResolveContextBackend(context, out var backendName);
|
var backend = ResolveContextBackend(context, out var backendName);
|
||||||
if (backend is null)
|
if (backend is null)
|
||||||
{
|
{
|
||||||
@@ -900,12 +946,11 @@ public static class AudioOut2Exports
|
|||||||
}
|
}
|
||||||
|
|
||||||
var n = Interlocked.Increment(ref _submitTraceCount);
|
var n = Interlocked.Increment(ref _submitTraceCount);
|
||||||
if (n <= 8 || n % 200 == 0)
|
if (n <= 8 || n % 500 == 0)
|
||||||
{
|
{
|
||||||
TraceAudioOut2(
|
TraceAudioOut2(
|
||||||
$"context-submit#{n} handle=0x{context.Handle:X} frames={frames} " +
|
$"context-submit#{n} handle=0x{context.Handle:X} frames={frames} " +
|
||||||
$"ports={mixedPorts} lastPort=0x{lastPort:X} format=0x{lastFormat:X} " +
|
$"ports={mixedPorts} peak={peak:F4} backend={backendName}");
|
||||||
$"ch={lastChannels} peak={peak:F4} backend={backendName}");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return backend.Submit(outputSpan);
|
return backend.Submit(outputSpan);
|
||||||
@@ -919,75 +964,6 @@ public static class AudioOut2Exports
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool TryPickMainBed(ulong contextHandle, out PortState? chosen)
|
|
||||||
{
|
|
||||||
chosen = null;
|
|
||||||
var chosenScore = int.MinValue;
|
|
||||||
foreach (var port in Ports.Values)
|
|
||||||
{
|
|
||||||
if (port.ContextHandle != contextHandle ||
|
|
||||||
port.PcmAddress == 0 ||
|
|
||||||
IsObjectPort(port.PortType) ||
|
|
||||||
!IsMainOrBgmPort(port.PortType) ||
|
|
||||||
!TryDecodeDataFormat(port.DataFormat, out var channels, out _, out _))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var score = channels switch
|
|
||||||
{
|
|
||||||
2 => 300,
|
|
||||||
1 => 200,
|
|
||||||
8 => 100,
|
|
||||||
_ => 50,
|
|
||||||
};
|
|
||||||
if ((port.PortType & 0xFF) == 0)
|
|
||||||
{
|
|
||||||
score += 20; // MAIN over BGM
|
|
||||||
}
|
|
||||||
|
|
||||||
if (score > chosenScore)
|
|
||||||
{
|
|
||||||
chosenScore = score;
|
|
||||||
chosen = port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return chosen is not null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool TryPickAuxBed(ulong contextHandle, out PortState? chosen)
|
|
||||||
{
|
|
||||||
chosen = null;
|
|
||||||
var chosenScore = int.MinValue;
|
|
||||||
foreach (var port in Ports.Values)
|
|
||||||
{
|
|
||||||
if (port.ContextHandle != contextHandle ||
|
|
||||||
port.PcmAddress == 0 ||
|
|
||||||
IsObjectPort(port.PortType) ||
|
|
||||||
(port.PortType & 0xFF) != 6 ||
|
|
||||||
!TryDecodeDataFormat(port.DataFormat, out var channels, out _, out _))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
var score = channels switch
|
|
||||||
{
|
|
||||||
2 => 300,
|
|
||||||
1 => 200,
|
|
||||||
8 => 100,
|
|
||||||
_ => 50,
|
|
||||||
};
|
|
||||||
if (score > chosenScore)
|
|
||||||
{
|
|
||||||
chosenScore = score;
|
|
||||||
chosen = port;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return chosen is not null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool IsMainOrBgmPort(ushort portType)
|
private static bool IsMainOrBgmPort(ushort portType)
|
||||||
{
|
{
|
||||||
var kind = portType & 0xFF;
|
var kind = portType & 0xFF;
|
||||||
|
|||||||
@@ -8459,15 +8459,13 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
private void DrainGuestImageCpuSync()
|
private void DrainGuestImageCpuSync()
|
||||||
{
|
{
|
||||||
if (!SharpEmu.HLE.GuestImageWriteTracker.Enabled)
|
var syncEnabled = SharpEmu.HLE.GuestImageWriteTracker.Enabled;
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
_ = Interlocked.Exchange(ref _cpuWrittenGuestImageSyncRequested, 0);
|
|
||||||
|
|
||||||
HashSet<ulong>? dirtyAddresses = null;
|
HashSet<ulong>? dirtyAddresses = null;
|
||||||
List<(ulong Address, uint Width, uint Height, ulong ByteCount)>? extents = null;
|
List<(ulong Address, uint Width, uint Height, ulong ByteCount)>? extents = null;
|
||||||
|
if (syncEnabled)
|
||||||
|
{
|
||||||
|
_ = Interlocked.Exchange(ref _cpuWrittenGuestImageSyncRequested, 0);
|
||||||
|
|
||||||
lock (_gate)
|
lock (_gate)
|
||||||
{
|
{
|
||||||
if (_guestImageExtents.Count > 0)
|
if (_guestImageExtents.Count > 0)
|
||||||
@@ -8536,6 +8534,8 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
if (_textureCache.Count == 0)
|
if (_textureCache.Count == 0)
|
||||||
{
|
{
|
||||||
if (dirtyAddresses is not null)
|
if (dirtyAddresses is not null)
|
||||||
@@ -14508,27 +14508,26 @@ internal static unsafe class VulkanVideoPresenter
|
|||||||
|
|
||||||
if (!tookPresentation)
|
if (!tookPresentation)
|
||||||
{
|
{
|
||||||
// Upstream also replays the last host splash here after an
|
|
||||||
// embedded-surface resize. That path is gone with the SDL
|
|
||||||
// window: there is no host surface to resize around, and the
|
|
||||||
// swapchain recreate above already covers SDL's own resize.
|
|
||||||
//
|
|
||||||
// A render-loop tick with no newer flip is normal. Warn only when
|
// A render-loop tick with no newer flip is normal. Warn only when
|
||||||
// an actual queued presentation is waiting on unfinished guest work.
|
// an actual queued presentation is waiting on unfinished guest work.
|
||||||
var hasPendingPresentation =
|
if (SharpEmu.Libs.Diagnostics.LoadProgressDiagnostics.IsActive ||
|
||||||
HasPendingGuestPresentation(_presentedSequence);
|
ShouldTracePresentedGuestImageContentsForDiagnostics())
|
||||||
SharpEmu.Libs.Diagnostics.LoadProgressDiagnostics.TracePresentNotTaken(
|
|
||||||
_presentedSequence,
|
|
||||||
hasPendingPresentation);
|
|
||||||
SharpEmu.Libs.Diagnostics.LoadProgressDiagnostics.TraceGpuWaitSnapshot();
|
|
||||||
if (ShouldTracePresentedGuestImageContentsForDiagnostics() &&
|
|
||||||
hasPendingPresentation &&
|
|
||||||
_presentNotTakenLoggedSequence != _presentedSequence)
|
|
||||||
{
|
{
|
||||||
_presentNotTakenLoggedSequence = _presentedSequence;
|
var hasPendingPresentation =
|
||||||
Console.Error.WriteLine(
|
HasPendingGuestPresentation(_presentedSequence);
|
||||||
$"[LOADER][WARN] vk.present_not_taken seq={_presentedSequence} " +
|
SharpEmu.Libs.Diagnostics.LoadProgressDiagnostics.TracePresentNotTaken(
|
||||||
"— presentation submitted but its required guest work isn't complete; nothing shown.");
|
_presentedSequence,
|
||||||
|
hasPendingPresentation);
|
||||||
|
SharpEmu.Libs.Diagnostics.LoadProgressDiagnostics.TraceGpuWaitSnapshot();
|
||||||
|
if (ShouldTracePresentedGuestImageContentsForDiagnostics() &&
|
||||||
|
hasPendingPresentation &&
|
||||||
|
_presentNotTakenLoggedSequence != _presentedSequence)
|
||||||
|
{
|
||||||
|
_presentNotTakenLoggedSequence = _presentedSequence;
|
||||||
|
Console.Error.WriteLine(
|
||||||
|
$"[LOADER][WARN] vk.present_not_taken seq={_presentedSequence} " +
|
||||||
|
"— presentation submitted but its required guest work isn't complete; nothing shown.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|||||||
Reference in New Issue
Block a user