Match native surface persistence policy
This commit is contained in:
@@ -56,8 +56,11 @@ public sealed record GfxDiagnosticSnapshot(
|
||||
BlockingGfxObjectDiagnostic? BlockingRangeTransform,
|
||||
IReadOnlyList<BlockingGfxObjectDiagnostic> BlockingObjects);
|
||||
|
||||
public readonly record struct GfxSurfacePersistenceState(
|
||||
int Slot, long ResourceId, long ColorKey, bool Created, bool ReloadOnRestore);
|
||||
|
||||
public sealed record GfxPersistenceSnapshot(
|
||||
IReadOnlyList<(int Slot, long ResourceId, long ColorKey, bool Created)> Surfaces,
|
||||
IReadOnlyList<GfxSurfacePersistenceState> Surfaces,
|
||||
IReadOnlyList<(long Handle, GfxState.GfxObject Object)> Objects,
|
||||
long RangeFirst,
|
||||
long RangeCount,
|
||||
@@ -383,6 +386,7 @@ public sealed class GfxState
|
||||
_fieldTable.Clear();
|
||||
_surfaces.Clear();
|
||||
_createdSurfaces.Clear();
|
||||
_reloadableSurfaces.Clear();
|
||||
_movieStopTimesMs.Clear();
|
||||
_surfaceTransitions.Clear();
|
||||
CurrentObject = 0;
|
||||
@@ -405,11 +409,18 @@ public sealed class GfxState
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
var surfaces = _surfaces
|
||||
.Select(pair => (
|
||||
pair.Key, pair.Value.ResId, pair.Value.ColorKey,
|
||||
_createdSurfaces.Contains(pair.Key)))
|
||||
.OrderBy(item => item.Key)
|
||||
var surfaces = _surfaces.Keys
|
||||
.Concat(_reloadableSurfaces)
|
||||
.Distinct()
|
||||
.Select(slot =>
|
||||
{
|
||||
bool active = _surfaces.TryGetValue(slot, out var value);
|
||||
return new GfxSurfacePersistenceState(
|
||||
slot, active ? value.ResId : -1, active ? value.ColorKey : 0,
|
||||
active && _createdSurfaces.Contains(slot),
|
||||
_reloadableSurfaces.Contains(slot));
|
||||
})
|
||||
.OrderBy(item => item.Slot)
|
||||
.ToArray();
|
||||
var objects = _orderedObjectHandles
|
||||
.Select(handle => (handle, CloneState(_objects[handle])))
|
||||
@@ -427,10 +438,13 @@ public sealed class GfxState
|
||||
{
|
||||
_surfaces.Clear();
|
||||
_createdSurfaces.Clear();
|
||||
foreach (var (slot, resourceId, colorKey, created) in snapshot.Surfaces)
|
||||
_reloadableSurfaces.Clear();
|
||||
foreach (GfxSurfacePersistenceState surface in snapshot.Surfaces)
|
||||
{
|
||||
_surfaces[slot] = (resourceId, colorKey);
|
||||
if (created) _createdSurfaces.Add(slot);
|
||||
if (surface.ResourceId >= 0 || surface.Created)
|
||||
_surfaces[surface.Slot] = (surface.ResourceId, surface.ColorKey);
|
||||
if (surface.Created) _createdSurfaces.Add(surface.Slot);
|
||||
if (surface.ReloadOnRestore) _reloadableSurfaces.Add(surface.Slot);
|
||||
}
|
||||
_objects.Clear();
|
||||
_orderedObjectHandles.Clear();
|
||||
@@ -481,6 +495,9 @@ public sealed class GfxState
|
||||
// Created surfaces have real pixels but no asset resource id. Keep their class separate from both
|
||||
// loaded textures and truly surfaceless objects because native mode-0 consumes packed alpha differently.
|
||||
private readonly HashSet<int> _createdSurfaces = new();
|
||||
// Native surface record +0x08. Ordinary create/load/release workers leave this bit unchanged;
|
||||
// the numbered-save restore loop consults it to decide which asset-backed surfaces to reopen.
|
||||
private readonly HashSet<int> _reloadableSurfaces = new();
|
||||
// A separate entry models the native CMovieToTexture object attached to a surface. A null value means
|
||||
// the movie object exists but its host decoder supplied no usable IMediaPosition stop time.
|
||||
private readonly Dictionary<int, long?> _movieStopTimesMs = new();
|
||||
@@ -496,6 +513,24 @@ public sealed class GfxState
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Set the native surface-record +0x08 reload policy. This is separate from loading a
|
||||
/// texture because AGE's ordinary create/load/release workers preserve the existing bit.</summary>
|
||||
public void SetSurfaceReloadOnRestore(int slot, bool reload)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
if (reload) _reloadableSurfaces.Add(slot);
|
||||
else _reloadableSurfaces.Remove(slot);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Opcode 0x259 script-entry lifecycle: clear record +0x08 for every surface.
|
||||
/// Native also clears the adjacent unknown +0x0c field, which the port does not otherwise model.</summary>
|
||||
public void ClearSurfaceReloadPolicies()
|
||||
{
|
||||
lock (_lock) _reloadableSurfaces.Clear();
|
||||
}
|
||||
|
||||
/// <summary>Op 0x236 handoff: retain the initialized movie graph's IMediaPosition stop time. Null
|
||||
/// deliberately distinguishes a movie surface with unavailable metadata from an empty movie slot.</summary>
|
||||
public void SetMovieStopTime(int slot, long? stopTimeMs)
|
||||
|
||||
@@ -17,13 +17,17 @@ internal static class NativeGfxPersistenceCodec
|
||||
{
|
||||
GfxPersistenceSnapshot snapshot = gfx.CapturePersistenceSnapshot();
|
||||
byte[] surfaces = new byte[NativeNumberedSaveState.SurfaceRecordsSize];
|
||||
foreach (var (slot, resourceId, colorKey, created) in snapshot.Surfaces)
|
||||
for (int slot = 0; slot < SurfaceCount; slot++)
|
||||
WriteInt(surfaces, slot * SurfaceRecordSize, -1);
|
||||
foreach (GfxSurfacePersistenceState surface in snapshot.Surfaces)
|
||||
{
|
||||
if ((uint)slot >= SurfaceCount || created || resourceId < 0) continue;
|
||||
int at = slot * SurfaceRecordSize;
|
||||
WriteInt(surfaces, at, unchecked((int)resourceId));
|
||||
WriteInt(surfaces, at + 4, PackNativeColorKey(colorKey));
|
||||
WriteInt(surfaces, at + 8, 1);
|
||||
if ((uint)surface.Slot >= SurfaceCount) continue;
|
||||
int at = surface.Slot * SurfaceRecordSize;
|
||||
WriteInt(surfaces, at,
|
||||
surface.Created ? -1 : unchecked((int)surface.ResourceId));
|
||||
WriteInt(surfaces, at + 4, PackNativeColorKey(surface.ColorKey));
|
||||
WriteInt(surfaces, at + 8, surface.ReloadOnRestore ? 1 : 0);
|
||||
WriteInt(surfaces, at + 0x10, surface.Created ? 1 : 0);
|
||||
}
|
||||
NativeSavedGfxObject[] objects = snapshot.Objects
|
||||
.Select(item => new NativeSavedGfxObject(item.Handle, EncodeObject(item.Object)))
|
||||
@@ -35,15 +39,20 @@ internal static class NativeGfxPersistenceCodec
|
||||
|
||||
public static GfxPersistenceSnapshot Decode(NativeNumberedSaveState state)
|
||||
{
|
||||
var surfaces = new List<(int Slot, long ResourceId, long ColorKey, bool Created)>();
|
||||
var surfaces = new List<GfxSurfacePersistenceState>();
|
||||
for (int slot = 0; slot < SurfaceCount; slot++)
|
||||
{
|
||||
int at = slot * SurfaceRecordSize;
|
||||
int resourceId = ReadInt(state.SurfaceRecords, at);
|
||||
int present = ReadInt(state.SurfaceRecords, at + 8);
|
||||
if (present == 1 && resourceId >= 0)
|
||||
surfaces.Add((slot, unchecked((uint)resourceId),
|
||||
UnpackNativeColorKey(ReadInt(state.SurfaceRecords, at + 4)), false));
|
||||
bool reload = ReadInt(state.SurfaceRecords, at + 8) == 1;
|
||||
bool created = ReadInt(state.SurfaceRecords, at + 0x10) == 1;
|
||||
if (resourceId >= 0 || reload || created)
|
||||
surfaces.Add(new GfxSurfacePersistenceState(
|
||||
slot,
|
||||
resourceId < 0 ? resourceId : unchecked((uint)resourceId),
|
||||
UnpackNativeColorKey(ReadInt(state.SurfaceRecords, at + 4)),
|
||||
created,
|
||||
reload));
|
||||
}
|
||||
var objects = state.GfxObjects
|
||||
.Select(item => (item.Handle, DecodeObject(item.Record)))
|
||||
|
||||
@@ -177,12 +177,20 @@ public static class NativeNumberedSaveCodec
|
||||
=> new(
|
||||
0, 0, new int[NativeNumberedSaveState.SoundEffectChannelCount],
|
||||
new byte[NativeNumberedSaveState.ResourceRecordsSize],
|
||||
new byte[NativeNumberedSaveState.SurfaceRecordsSize],
|
||||
EmptySurfaceRecords(),
|
||||
frames, Array.Empty<int>(), Array.Empty<int>(), Array.Empty<string>(),
|
||||
Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>(),
|
||||
Array.Empty<NativeSavedGfxObject>(), 0, 0,
|
||||
new byte[NativeNumberedSaveState.GfxRecordSize]);
|
||||
|
||||
private static byte[] EmptySurfaceRecords()
|
||||
{
|
||||
byte[] result = new byte[NativeNumberedSaveState.SurfaceRecordsSize];
|
||||
for (int slot = 0; slot < 1000; slot++)
|
||||
WriteInt(result, slot * 20, -1);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static void WriteFrame(Span<byte> payload, int offset, NativeSavedScriptFrame frame, bool terminal)
|
||||
{
|
||||
if (frame.ReturnIndices.Count > FrameReturnCapacity)
|
||||
|
||||
@@ -896,12 +896,24 @@ public sealed class VirtualMachine
|
||||
}
|
||||
|
||||
GfxPersistenceSnapshot gfxSnapshot = NativeGfxPersistenceCodec.Decode(state);
|
||||
for (int slot = 0; slot < 1000; slot++) _host.ReleaseSurface(slot);
|
||||
Gfx.RestorePersistenceSnapshot(gfxSnapshot);
|
||||
foreach (var (slot, resourceId, colorKey, created) in gfxSnapshot.Surfaces)
|
||||
bool releaseAllSurfaces = _o.CreateObject && _o.AutoFreeTextures;
|
||||
if (releaseAllSurfaces)
|
||||
{
|
||||
if (!created) _host.SetTexture(resourceId, slot, colorKey);
|
||||
Gfx.ReleaseSurfaceRange(0, 1000);
|
||||
_host.ReleaseSurfaceRange(0, 1000);
|
||||
}
|
||||
var restoredSurfaces = Gfx.CapturePersistenceSnapshot().Surfaces
|
||||
.ToDictionary(item => item.Slot);
|
||||
foreach (GfxSurfacePersistenceState surface in gfxSnapshot.Surfaces
|
||||
.Where(item => item.ReloadOnRestore && item.ResourceId >= 0))
|
||||
restoredSurfaces[surface.Slot] = surface;
|
||||
Gfx.RestorePersistenceSnapshot(gfxSnapshot with
|
||||
{
|
||||
Surfaces = restoredSurfaces.Values.OrderBy(item => item.Slot).ToArray(),
|
||||
});
|
||||
foreach (GfxSurfacePersistenceState surface in gfxSnapshot.Surfaces
|
||||
.Where(item => item.ReloadOnRestore && item.ResourceId >= 0))
|
||||
_host.SetTexture(surface.ResourceId, surface.Slot, surface.ColorKey);
|
||||
}
|
||||
|
||||
private static void ReplaceDensePrefix<T>(
|
||||
@@ -1025,6 +1037,8 @@ public sealed class VirtualMachine
|
||||
var a = ins.Args;
|
||||
switch (_t.Label(op))
|
||||
{
|
||||
case "script-entry":
|
||||
Gfx.ClearSurfaceReloadPolicies(); return pc + 1;
|
||||
case "add": Write(a[0], Read(a[1]) + Read(a[2])); return pc + 1;
|
||||
case "sub": Write(a[0], Read(a[1]) - Read(a[2])); return pc + 1;
|
||||
case "mul": Write(a[0], Read(a[1]) * Read(a[2])); return pc + 1;
|
||||
|
||||
@@ -10,6 +10,11 @@ namespace Age.Engine.Vm;
|
||||
/// post-exit bytecode can be explored. Leave false for native-faithful execution.</param>
|
||||
/// <param name="NativeStringCodePage">Encoding used when an opcode measures the engine's byte-string
|
||||
/// representation. SYS4 defaults to CP932; another container frontend can select its own code page.</param>
|
||||
/// <param name="CreateObject">Native set:CreateObject profile setting. Together with
|
||||
/// <paramref name="AutoFreeTextures"/>, controls the optional all-surface release before numbered load.</param>
|
||||
/// <param name="AutoFreeTextures">Native set:AutoFreeTex profile setting. Himegari defaults this off,
|
||||
/// so initialized system textures survive a numbered load unless an explicit reload record replaces them.</param>
|
||||
public sealed record VmOptions(int EmitCap = 2, long MaxSteps = 2_000_000, int CallDepthCap = 64,
|
||||
bool HaltAtWaitForInput = false, bool IgnoreExitRequests = false,
|
||||
int NativeStringCodePage = 932);
|
||||
int NativeStringCodePage = 932, bool CreateObject = true,
|
||||
bool AutoFreeTextures = false);
|
||||
|
||||
Reference in New Issue
Block a user