Split GfxState surface operations
This commit is contained in:
@@ -160,6 +160,12 @@ transitions, diagnostic snapshots, frame/completion publication, and mask teardo
|
||||
control, and blocking BGM fades; presentation/input retains the message-skip, reset, and frame-pulse consumers
|
||||
of that state through the sealed partial class.
|
||||
|
||||
`engine/Age.Engine/Model/GfxState.cs` retains cross-domain retained-graphics coordination.
|
||||
`engine/Age.Engine/Model/GfxState.Contracts.cs` owns its public render, transition, diagnostic, persistence,
|
||||
animation, numeric-glyph, and handle-range contracts. `engine/Age.Engine/Model/GfxState.Surfaces.cs` owns surface
|
||||
resource/color-key state, created/reloadable classification, movie stop-time metadata, render-target/tile
|
||||
configuration, and surface lifecycle operations.
|
||||
|
||||
The disposable `build/page-map-<SCENE>.jsonl` files are produced by editor/development Godot runs and map
|
||||
runtime ADV page ordinals to their authoritative script offsets for `tools/locate_page.py`. Packaged exports
|
||||
have no repository output tree and write their automatic maps below `user://diagnostics/page-maps` instead.
|
||||
|
||||
@@ -601,6 +601,12 @@ do not mix mechanical moves with semantic changes.
|
||||
`GfxState` remains the same sealed runtime type, and every moved declaration is textually unchanged; runtime
|
||||
validation remains green.
|
||||
|
||||
The second bounded `GfxState` split converted the sealed runtime type to a sealed partial class and moved
|
||||
surface resource/color-key state, created/reloadable classification, movie stop-time metadata,
|
||||
render-target/tile configuration, and surface lifecycle operations into
|
||||
`engine/Age.Engine/Model/GfxState.Surfaces.cs`. Reset, persistence, retained-object sampling, and transition
|
||||
teardown retain direct access through the partial class; runtime validation remains green.
|
||||
|
||||
**Gate:** no externally visible behavior or command changes; generated artifacts are byte-identical where
|
||||
deterministic, and the corresponding engine, Python, Godot, and corpus validations remain green after
|
||||
each domain move.
|
||||
@@ -972,7 +978,7 @@ layer's rendering diverges from ADV; save layout.
|
||||
## 8. Immediate next step
|
||||
Continue step 2 of the **codebase consolidation** maintenance slice: behavior-neutral physical splits backed
|
||||
by the tracked launcher and layered validation driver. With the planned `Main` and `GodotAdvHost` domains
|
||||
isolated and the `GfxState` public contract layer separated, move `GfxState` surface ownership next, preserving
|
||||
public types, commands, and generated output.
|
||||
isolated and the first two `GfxState` domains separated, move retained-object ownership next, preserving public
|
||||
types, commands, and generated output.
|
||||
Concrete playthrough blockers may still preempt this bounded maintenance work; the consolidation effort does
|
||||
not replace Phase B gameplay validation or the open cross-platform gates.
|
||||
|
||||
123
engine/Age.Engine/Model/GfxState.Surfaces.cs
Normal file
123
engine/Age.Engine/Model/GfxState.Surfaces.cs
Normal file
@@ -0,0 +1,123 @@
|
||||
namespace Age.Engine.Model;
|
||||
|
||||
public sealed partial class GfxState
|
||||
{
|
||||
/// <summary>The D3D render target selected by op 0x20d. -1 denotes the main backbuffer.</summary>
|
||||
public int CurrentRenderTargetSlot { get; private set; } = -1;
|
||||
/// <summary>Legacy mode-1 surface tile edge selected by op 0x248. The portable backend retains
|
||||
/// the native process-global value for parity but stores every surface as one contiguous image.</summary>
|
||||
public int TiledSurfaceEdgeLength { get; private set; }
|
||||
|
||||
// ---- surfaces (image buffers per slot): ctx+0x52bd4[slot], from create/set-texture ----
|
||||
private readonly Dictionary<int, (long ResId, long ColorKey)> _surfaces = new();
|
||||
// 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();
|
||||
|
||||
public void SetTiledSurfaceEdgeLength(long edgeLength)
|
||||
{
|
||||
// Native stores the complete operand in one signed dword. It does not invalidate or rebuild
|
||||
// already-created mode-1 surfaces, nor does it mark the retained compositor dirty.
|
||||
lock (_lock) TiledSurfaceEdgeLength = unchecked((int)edgeLength);
|
||||
}
|
||||
|
||||
public void SetSurface(int slot, long resId, long colorKey)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_surfaces[slot] = (resId, colorKey);
|
||||
_createdSurfaces.Remove(slot);
|
||||
_movieStopTimesMs.Remove(slot);
|
||||
MarkRetainedMutation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
lock (_lock) _movieStopTimesMs[slot] = stopTimeMs;
|
||||
}
|
||||
|
||||
/// <summary>Op 0x23f query. False means no movie object occupies the slot; true with a null value
|
||||
/// means the movie exists but its stop-time query failed or returned unusable metadata.</summary>
|
||||
public bool TryGetMovieStopTime(int slot, out long? stopTimeMs)
|
||||
{
|
||||
lock (_lock) return _movieStopTimesMs.TryGetValue(slot, out stopTimeMs);
|
||||
}
|
||||
|
||||
/// <summary>Op 0x20d: select a surface as the D3D render target; values at or above 1000 restore the
|
||||
/// device backbuffer in the native engine.</summary>
|
||||
public void SelectRenderTarget(long slot)
|
||||
{
|
||||
lock (_lock) CurrentRenderTargetSlot = slot is >= 0 and < 1000 ? (int)slot : -1;
|
||||
}
|
||||
|
||||
/// <summary>Op 0x23d: release the transient surface range while retaining system-owned low slots.</summary>
|
||||
public void ReleaseSurfaceRange(int firstSlot, int count)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
int end = checked(firstSlot + count);
|
||||
for (int slot = firstSlot; slot < end; slot++)
|
||||
{
|
||||
_surfaces.Remove(slot);
|
||||
_createdSurfaces.Remove(slot);
|
||||
_movieStopTimesMs.Remove(slot);
|
||||
_surfaceTransitions.Remove(slot);
|
||||
_movieMaskTransitions.Remove(slot);
|
||||
}
|
||||
if (CurrentRenderTargetSlot >= firstSlot && CurrentRenderTargetSlot < end)
|
||||
CurrentRenderTargetSlot = -1;
|
||||
MarkRetainedMutation();
|
||||
}
|
||||
}
|
||||
|
||||
public void CreateSurface(int slot)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_surfaces[slot] = (0, -1); // create-texture: real mutable pixels, no asset id or color key
|
||||
_createdSurfaces.Add(slot);
|
||||
_movieStopTimesMs.Remove(slot);
|
||||
MarkRetainedMutation();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSurface(int slot)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_surfaces.Remove(slot);
|
||||
_createdSurfaces.Remove(slot);
|
||||
_movieStopTimesMs.Remove(slot);
|
||||
_surfaceTransitions.Remove(slot);
|
||||
_movieMaskTransitions.Remove(slot);
|
||||
MarkRetainedMutation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace Age.Engine.Model;
|
||||
/// slot (returned by 0x215) and three 3-vectors: V18 (set 0x217 / get 0x218, anchor), V24 (set 0x219 /
|
||||
/// get 0x21a, position), V16c (set 0x1ff). The native DirectDraw workers are NOT modelled — only the data
|
||||
/// the query ops read back, which is all the bytecode geometry math needs.</summary>
|
||||
public sealed class GfxState
|
||||
public sealed partial class GfxState
|
||||
{
|
||||
private sealed class SurfaceTransition
|
||||
{
|
||||
@@ -115,11 +115,6 @@ public sealed class GfxState
|
||||
public long CurrentObject { get; private set; }
|
||||
/// <summary>EngineCtx+0x14e08, selected by op 0x80 and used by op 0x1d9 when its slot is zero.</summary>
|
||||
public int DefaultObjectSlot { get; private set; }
|
||||
/// <summary>The D3D render target selected by op 0x20d. -1 denotes the main backbuffer.</summary>
|
||||
public int CurrentRenderTargetSlot { get; private set; } = -1;
|
||||
/// <summary>Legacy mode-1 surface tile edge selected by op 0x248. The portable backend retains
|
||||
/// the native process-global value for parity but stores every surface as one contiguous image.</summary>
|
||||
public int TiledSurfaceEdgeLength { get; private set; }
|
||||
|
||||
// ---- Separate global animation service clock (op 0x238; ctx+0x51b7c total / +0x51b78 elapsed).
|
||||
// Retained for its opcode family; 0x21e scale and 0x220 translation use frame-time directly instead. ----
|
||||
@@ -165,12 +160,6 @@ public sealed class GfxState
|
||||
lock (_lock) DefaultObjectSlot = slot;
|
||||
}
|
||||
|
||||
public void SetTiledSurfaceEdgeLength(long edgeLength)
|
||||
{
|
||||
// Native stores the complete operand in one signed dword. It does not invalidate or rebuild
|
||||
// already-created mode-1 surfaces, nor does it mark the retained compositor dirty.
|
||||
lock (_lock) TiledSurfaceEdgeLength = unchecked((int)edgeLength);
|
||||
}
|
||||
|
||||
public void SetObjectAnchor(long handle, (long X, long Y, long Z) anchor)
|
||||
{
|
||||
@@ -426,88 +415,8 @@ public sealed class GfxState
|
||||
|
||||
private readonly object _lock = new();
|
||||
|
||||
// ---- surfaces (image buffers per slot): ctx+0x52bd4[slot], from create/set-texture ----
|
||||
private readonly Dictionary<int, (long ResId, long ColorKey)> _surfaces = new();
|
||||
// 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();
|
||||
private readonly Dictionary<int, SurfaceTransition> _surfaceTransitions = new();
|
||||
private readonly Dictionary<int, MovieMaskTransition> _movieMaskTransitions = new();
|
||||
public void SetSurface(int slot, long resId, long colorKey)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_surfaces[slot] = (resId, colorKey);
|
||||
_createdSurfaces.Remove(slot);
|
||||
_movieStopTimesMs.Remove(slot);
|
||||
MarkRetainedMutation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
lock (_lock) _movieStopTimesMs[slot] = stopTimeMs;
|
||||
}
|
||||
|
||||
/// <summary>Op 0x23f query. False means no movie object occupies the slot; true with a null value
|
||||
/// means the movie exists but its stop-time query failed or returned unusable metadata.</summary>
|
||||
public bool TryGetMovieStopTime(int slot, out long? stopTimeMs)
|
||||
{
|
||||
lock (_lock) return _movieStopTimesMs.TryGetValue(slot, out stopTimeMs);
|
||||
}
|
||||
|
||||
/// <summary>Op 0x20d: select a surface as the D3D render target; values at or above 1000 restore the
|
||||
/// device backbuffer in the native engine.</summary>
|
||||
public void SelectRenderTarget(long slot)
|
||||
{
|
||||
lock (_lock) CurrentRenderTargetSlot = slot is >= 0 and < 1000 ? (int)slot : -1;
|
||||
}
|
||||
|
||||
/// <summary>Op 0x23d: release the transient surface range while retaining system-owned low slots.</summary>
|
||||
public void ReleaseSurfaceRange(int firstSlot, int count)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
int end = checked(firstSlot + count);
|
||||
for (int slot = firstSlot; slot < end; slot++)
|
||||
{
|
||||
_surfaces.Remove(slot);
|
||||
_createdSurfaces.Remove(slot);
|
||||
_movieStopTimesMs.Remove(slot);
|
||||
_surfaceTransitions.Remove(slot);
|
||||
_movieMaskTransitions.Remove(slot);
|
||||
}
|
||||
if (CurrentRenderTargetSlot >= firstSlot && CurrentRenderTargetSlot < end)
|
||||
CurrentRenderTargetSlot = -1;
|
||||
MarkRetainedMutation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Ops 0x202/0x203: record a packed 0xAARRGGBB color/alpha modulation on the object and mark it
|
||||
/// HasColor so the compositor applies alpha+tint (vs the opaque default).</summary>
|
||||
@@ -603,29 +512,6 @@ public sealed class GfxState
|
||||
o.ColorAnim = true;
|
||||
}
|
||||
}
|
||||
public void CreateSurface(int slot)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_surfaces[slot] = (0, -1); // create-texture: real mutable pixels, no asset id or color key
|
||||
_createdSurfaces.Add(slot);
|
||||
_movieStopTimesMs.Remove(slot);
|
||||
MarkRetainedMutation();
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearSurface(int slot)
|
||||
{
|
||||
lock (_lock)
|
||||
{
|
||||
_surfaces.Remove(slot);
|
||||
_createdSurfaces.Remove(slot);
|
||||
_movieStopTimesMs.Remove(slot);
|
||||
_surfaceTransitions.Remove(slot);
|
||||
_movieMaskTransitions.Remove(slot);
|
||||
MarkRetainedMutation();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Op 0x223: queue a type-0 timed alpha transition into a target surface slot.</summary>
|
||||
public void QueueSurfaceAlphaTransition(long commandKey, int targetSlot,
|
||||
|
||||
Reference in New Issue
Block a user