Fix ADV and BUNKI animation polish

This commit is contained in:
gamer147
2026-07-27 11:15:41 -04:00
parent 4b5b451df0
commit 9c99254a9a
11 changed files with 199 additions and 19 deletions

View File

@@ -69,6 +69,8 @@ public sealed class GodotAdvHost : IHost
private GfxState? _foregroundGfx;
private readonly object _screenTransitionLock = new();
private readonly Dictionary<int, IReadOnlyList<RenderObject>> _renderTargetSnapshots = new();
private readonly object _backbufferRangeLock = new();
private GfxHandleRange _backbufferRange = GfxHandleRange.All;
private LegacyScreenTransition? _screenTransition;
public volatile bool IsWaiting;
public volatile bool IsTransitionWaiting;
@@ -277,10 +279,28 @@ public sealed class GodotAdvHost : IHost
PublishObjectRangeToSurface(gfx, firstHandle, count);
return;
}
// The software/GPU port reconstructs the backbuffer instead of preserving native D3D pixels.
// Zero-based ranges therefore define the complete published scene. A nonzero range is an
// incremental overlay in native code, so retain the full reconstruction for those call sites.
lock (_backbufferRangeLock)
_backbufferRange = firstHandle == 0
? new GfxHandleRange(firstHandle, count)
: GfxHandleRange.All;
System.Threading.Interlocked.Exchange(ref _presentRequested, 1);
_timeline?.Event("present-object-range", new() { ["first"] = firstHandle, ["count"] = count });
}
public void SnapshotBackbufferObjects(GfxState gfx, long nowMs, List<RenderObject> snapshot)
{
gfx.SnapshotVisibleObjects(nowMs, snapshot);
GfxHandleRange range;
lock (_backbufferRangeLock) range = _backbufferRange;
int write = 0;
for (int read = 0; read < snapshot.Count; read++)
if (range.Contains(snapshot[read].Handle)) snapshot[write++] = snapshot[read];
if (write < snapshot.Count) snapshot.RemoveRange(write, snapshot.Count - write);
}
public void ConfigureAdvWaitIndicator(AdvWaitIndicatorConfig config)
{
lock (_textLock) _waitIndicators[config.LayoutSlot] = config;
@@ -323,9 +343,7 @@ public sealed class GodotAdvHost : IHost
var asset = _res.ResolveTexture(resourceId);
var image = asset != null ? Decode(asset) : null;
if (asset == null || image == null || config.CellWidth <= 0 || config.CellHeight <= 0) return null;
int frames = System.Math.Max(1, config.TerminalFrame + 1);
long period = System.Math.Max(1, config.FramePeriodMs);
int frame = (int)((_clock.NowMs - _waitIndicatorStartedMs) / period % frames);
int frame = config.FrameAt(_clock.NowMs - _waitIndicatorStartedMs);
return new AdvWaitIndicatorSnapshot(image, asset.Name, asset.PackedId, config, frame);
}
@@ -584,6 +602,7 @@ public sealed class GodotAdvHost : IHost
});
return;
}
lock (_backbufferRangeLock) _backbufferRange = GfxHandleRange.All;
int started = gfx.StartForegroundTransitions(_clock.NowMs);
int completed = gfx.CompleteForegroundTransitions(_clock.NowMs);
if (started > 0 || completed > 0)
@@ -702,6 +721,7 @@ public sealed class GodotAdvHost : IHost
// scene_context_init_reset releases ordinary surface/movie bindings but keeps decoded asset
// caches and process-owned audio/configuration available to the reloaded SYSTEM4 root.
ReleaseSurfaceRange(0, 1000);
lock (_backbufferRangeLock) _backbufferRange = GfxHandleRange.All;
lock (_textLock)
{
_surfaceText.Clear();
@@ -1162,6 +1182,8 @@ public sealed class GodotAdvHost : IHost
});
lock (_imageLock) _surfaceImages[targetSlot] = destination;
PublishSurfaceTextRangeToSurface(
gfx, visible, firstHandle, count, targetSlot, dimensions.W, dimensions.H);
IReadOnlyList<RenderObject> retained = visible
.Where(item => item.Handle >= firstHandle && item.Handle - firstHandle < count)
.ToArray();
@@ -1176,6 +1198,51 @@ public sealed class GodotAdvHost : IHost
});
}
private void PublishSurfaceTextRangeToSurface(
GfxState gfx, IReadOnlyList<RenderObject> visible, long firstHandle, long count,
int targetSlot, int targetWidth, int targetHeight)
{
List<SurfaceTextDraw> projected;
lock (_textLock)
projected = _surfaceText.TryGetValue(targetSlot, out var retained)
? new List<SurfaceTextDraw>(retained)
: new List<SurfaceTextDraw>();
foreach (RenderObject item in visible)
{
if (item.Handle < firstHandle || item.Handle - firstHandle >= count) continue;
var raw = gfx.TryGet(item.Handle);
if (raw == null) continue;
List<SurfaceTextDraw>? source;
lock (_textLock)
source = _surfaceText.TryGetValue(raw.SourceSlot, out var draws)
? new List<SurfaceTextDraw>(draws)
: null;
if (source == null) continue;
Affine2D localToTarget =
Transform2DMath.Build(item.Transform, item.Rotation).FromLocalOrigin(item.DstX, item.DstY);
if (item.RangeTransform is { } rangeTransform)
localToTarget = localToTarget.Then(rangeTransform);
foreach (SurfaceTextDraw draw in source)
{
if (draw.X < item.SrcX || draw.X >= item.SrcX + item.W ||
draw.Y < item.SrcY || draw.Y >= item.SrcY + item.H) continue;
var position = localToTarget.Apply(draw.X - item.SrcX, draw.Y - item.SrcY);
int x = (int)System.Math.Round(position.X);
int y = (int)System.Math.Round(position.Y);
if (x < 0 || x >= targetWidth || y < 0 || y >= targetHeight) continue;
projected.Add(new SurfaceTextDraw(x, y, draw.Text, draw.Style));
}
}
lock (_textLock)
{
if (projected.Count == 0) _surfaceText.Remove(targetSlot);
else _surfaceText[targetSlot] = projected;
}
}
public void ReleaseSurfaceRange(int firstSlot, int count)
{
IReadOnlyList<MovieSurfaceBinding> stoppedMovies = _movieSurfaces.ReleaseRange(firstSlot, count);

View File

@@ -864,7 +864,7 @@ public partial class Main : Godot.Control
long phase = _perf != null ? PerformanceFrameLog.Timestamp() : 0;
long allocationPhase = _perf != null ? PerformanceFrameLog.AllocatedBytes() : 0;
if (_host.TrySnapshotScreenTransition(out _)) return false; // P4: whole-screen offscreen targets
_vm.Gfx.SnapshotVisibleObjects(_clock.NowMs, _visibleSnapshot);
_host.SnapshotBackbufferObjects(_vm.Gfx, _clock.NowMs, _visibleSnapshot);
snapshotCaptured = true;
_perf?.RecordSnapshotAllocation(PerformanceFrameLog.AllocatedBytes() - allocationPhase);
_perf?.RecordSnapshot(PerformanceFrameLog.Timestamp() - phase);
@@ -952,9 +952,9 @@ public partial class Main : Godot.Control
{
if (surfaceText.X < v.SrcX || surfaceText.X >= v.SrcX + v.W ||
surfaceText.Y < v.SrcY || surfaceText.Y >= v.SrcY + v.H) continue;
var textPos = affine.Apply(surfaceText.X - v.SrcX, surfaceText.Y - v.SrcY);
var label = GetSurfaceTextLabel(surfaceTextLabelIndex++);
label.Position = new Vector2((float)textPos.X, (float)textPos.Y);
ApplySurfaceTextTransform(
label, affine, surfaceText.X - v.SrcX, surfaceText.Y - v.SrcY);
label.Size = new Vector2(System.Math.Max(1, v.W - (surfaceText.X - v.SrcX)),
System.Math.Max(1, v.H - (surfaceText.Y - v.SrcY)));
label.Text = surfaceText.Text;
@@ -1078,7 +1078,8 @@ public partial class Main : Godot.Control
phase = _perf != null ? PerformanceFrameLog.Timestamp() : 0;
allocationPhase = _perf != null ? PerformanceFrameLog.AllocatedBytes() : 0;
if (sampledVisible == null)
_vm.Gfx.SnapshotVisibleObjects(_clock.NowMs, _visibleSnapshot); // synchronized objects + ranges
_host.SnapshotBackbufferObjects(
_vm.Gfx, _clock.NowMs, _visibleSnapshot); // synchronized objects + publication range
_perf?.RecordSnapshotAllocation(PerformanceFrameLog.AllocatedBytes() - allocationPhase);
_perf?.RecordSnapshot(PerformanceFrameLog.Timestamp() - phase);
allocationPhase = _perf != null ? PerformanceFrameLog.AllocatedBytes() : 0;
@@ -1197,9 +1198,9 @@ public partial class Main : Godot.Control
{
if (surfaceText.X < v.SrcX || surfaceText.X >= v.SrcX + v.W ||
surfaceText.Y < v.SrcY || surfaceText.Y >= v.SrcY + v.H) continue;
var textPos = localToDest.Apply(surfaceText.X - v.SrcX, surfaceText.Y - v.SrcY);
var label = GetSurfaceTextLabel(surfaceTextLabelIndex++);
label.Position = new Vector2((float)textPos.X, (float)textPos.Y);
ApplySurfaceTextTransform(
label, localToDest, surfaceText.X - v.SrcX, surfaceText.Y - v.SrcY);
label.Size = new Vector2(System.Math.Max(1, v.W - (surfaceText.X - v.SrcX)),
System.Math.Max(1, v.H - (surfaceText.Y - v.SrcY)));
label.Text = surfaceText.Text;
@@ -1254,6 +1255,17 @@ public partial class Main : Godot.Control
return _surfaceTextLabels[index];
}
private static void ApplySurfaceTextTransform(Label label, Affine2D localToDestination,
int localX, int localY)
{
var position = localToDestination.Apply(localX, localY);
var (rotation, scaleX, scaleY) = localToDestination.DecomposeCanvasAxes();
label.Position = new Vector2((float)position.X, (float)position.Y);
label.PivotOffset = Vector2.Zero;
label.Rotation = (float)rotation;
label.Scale = new Vector2((float)scaleX, (float)scaleY);
}
private Label CreateAdvPresentationLabel()
{
var label = new Label