Fix movie compositor publication
This commit is contained in:
@@ -280,9 +280,12 @@ frame; both still pass with the entire `extracted/` tree physically moved aside
|
||||
SC0000's native capture verifies operands `(0x33, 0, 2, 0)` and immediate VM continuation at `0x13d1`.
|
||||
In Godot the real site opens the same 8,194,052 VFS bytes, publishes changing 800x600 frames, and retains
|
||||
them across pre-yield static surface preparation. The later `0x21c` presentation service remains parked
|
||||
until DirectShow EOF, then scene cleanup stops the movie. Manual interactive validation still does not show
|
||||
the movie despite those successful lifecycle logs; visible compositor/layer publication is the next bounded
|
||||
investigation. The real-scene trace remains a separate extracted-present test
|
||||
until DirectShow EOF, then scene cleanup stops the movie. The initial invisible result was compositor-only:
|
||||
the static `(assetId,colorKey)` image cache froze the first movie sample, while an extra current-sample
|
||||
background copy was covered by the correctly positioned retained movie object. Dynamic movie surfaces now
|
||||
bypass that cache and publish only at their retained z-position. A windowed run reached first frame 101 and
|
||||
stop frame 190, and manual observation confirmed visible changing video. Movie audio remains intentionally
|
||||
unrendered. The real-scene trace remains a separate extracted-present test
|
||||
because the current `Paths.Scripts()` test bootstrap still locates its root `*.BIN` fixtures there; migrating
|
||||
that test/bootstrap path is unrelated to movie asset loading and was not folded into this slice.
|
||||
|
||||
|
||||
@@ -871,14 +871,15 @@ not itself block the VM. SC0000 prepares additional static layers, then reaches
|
||||
service continues sampling the retained movie until DirectShow EOF, after which the following script
|
||||
cleanup releases it. The static preparation before `0x21c` is not a movie teardown boundary.
|
||||
|
||||
**Manual-test correction (2026-07-11):** the initial port incorrectly treated those pre-yield static loads
|
||||
as surface replacement, producing start/first-frame/stop all in render frame 0. The bounded host now keeps
|
||||
the movie as an independently updating retained layer, parks `0x21c` until the DirectShow completion event,
|
||||
then permits cleanup. A real-render lifecycle run records first frame 98 and stop frame 181. The screenshot
|
||||
sequence's known stale-CG/transition behavior can obscure this layer, so screenshot appearance is not used
|
||||
as the visual oracle. **Manual recheck remains unresolved:** normal interactive SC0000 logs start, changing
|
||||
frame delivery, EOF, and delayed stop, but the movie is not visibly presented in the application. The next
|
||||
slice must diagnose compositor/layer publication rather than reopening decode or VM-lifecycle timing.
|
||||
**Manual-test corrections (2026-07-11):** the initial port incorrectly treated pre-yield static loads as
|
||||
surface replacement, producing start/first-frame/stop all in render frame 0. The bounded host now retains
|
||||
the movie through `0x21c` until DirectShow completion. A follow-up compositor trace proved the movie object
|
||||
was present at the correct z-position, but the static AGF cache reused the first decoded sample forever
|
||||
because every sample has the same `(assetId,colorKey)`. An unconditional current-sample backbuffer copy did
|
||||
not help because normal retained composition immediately covered it with that cached first sample. Movie
|
||||
surfaces now bypass the static cache and publish only through the retained object. A windowed run records
|
||||
first frame 101 and stop frame 190; the user manually confirmed visible playback. The separate lower white
|
||||
textbox-area object remains outside this finding, as does the deliberately unrendered movie audio stream.
|
||||
|
||||
The `/v2` image names/comments the handler; movie ctor/interface/open/play/volume/release workers; sound
|
||||
route helpers; renderer media-type/sample workers; and stop/detach/destructor lifecycle. The image was
|
||||
|
||||
@@ -1226,10 +1226,13 @@ saved. Validation covers the exact real-SC0000 operands and `0x13c8 -> 0x13d1` b
|
||||
payload identity, actual 800x600 RGBA DirectShow decode, .NET/Godot builds, and the real Godot site opening
|
||||
the same VFS byte count. Manual testing exposed and corrected an initial same-frame teardown: pre-yield
|
||||
static loads no longer destroy the movie, `0x21c` waits for DirectShow completion, and a real-render run
|
||||
records first frame 98 / stop frame 181. The shot-sequence's known stale-CG behavior can cover the movie,
|
||||
so it is not treated as visual proof. **Manual recheck is not visually complete:** the normal application
|
||||
logs start, changing frame delivery, EOF, and delayed stop, but shows no movie. Carry compositor/layer
|
||||
publication forward as the next bounded diagnostic; do not reopen the proven VFS/decode/resume lifecycle.
|
||||
records first frame 98 / stop frame 181. A follow-up compositor-only investigation found that the movie
|
||||
object was present at the correct retained z-position, but the static AGF image cache keyed by
|
||||
`(assetId,colorKey)` froze its first sample. A duplicate unconditional backbuffer copy of the current sample
|
||||
was then covered by that same retained object. Dynamic movie surfaces now bypass the static-image cache and
|
||||
publish only through their retained object. A windowed auto-play run recorded first frame 101 / stop frame
|
||||
190, and the user manually confirmed that the video visibly played. The known lower white/textbox panel is
|
||||
an unrelated retained-object issue. Movie audio remains deliberately unrendered and out of scope.
|
||||
The decoder test
|
||||
asserts two delivered MPEG frames differ. The two archive/decode tests
|
||||
also pass with all of `extracted/` physically moved aside. The real-scene trace is intentionally separate:
|
||||
|
||||
@@ -257,14 +257,14 @@ public sealed class GodotAdvHost : IHost
|
||||
|
||||
/// <summary>Resolve a gfx surface through scene-local or universal raw-id addressing and decode it
|
||||
/// from the loose-first asset store.</summary>
|
||||
public (RgbaImage Image, string Name, int AssetId)? ResolveResIdTexture(long resId)
|
||||
public (RgbaImage Image, string Name, int AssetId, bool IsDynamic)? ResolveResIdTexture(long resId)
|
||||
{
|
||||
lock (_imageLock)
|
||||
if (_movieFrames.TryGetValue(resId, out var movie))
|
||||
return (movie.Image, movie.Name, movie.RawIndex);
|
||||
return (movie.Image, movie.Name, movie.RawIndex, true);
|
||||
var asset = _res.ResolveTexture(_scene, resId);
|
||||
var image = asset != null ? Decode(asset) : null;
|
||||
return asset != null && image != null ? (image, asset.Name, asset.RawIndex) : null;
|
||||
return asset != null && image != null ? (image, asset.Name, asset.RawIndex, false) : null;
|
||||
}
|
||||
|
||||
public void PlayMovieToSurface(long resourceId, int surfaceSlot, long movieFlags, long syncMask)
|
||||
@@ -333,19 +333,6 @@ public sealed class GodotAdvHost : IHost
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool TryGetActiveMovieFrame(out RgbaImage frame)
|
||||
{
|
||||
lock (_imageLock)
|
||||
foreach (long resourceId in _movieBySurface.Values)
|
||||
if (_movieFrames.TryGetValue(resourceId, out var movie))
|
||||
{
|
||||
frame = movie.Image;
|
||||
return true;
|
||||
}
|
||||
frame = default!;
|
||||
return false;
|
||||
}
|
||||
|
||||
private RgbaImage? Decode(AssetEntry asset)
|
||||
{
|
||||
lock (_imageLock)
|
||||
|
||||
@@ -306,11 +306,6 @@ public partial class Main : Godot.Control
|
||||
private void Recomposite()
|
||||
{
|
||||
_screen.Fill(new Color(0, 0, 0, 0));
|
||||
// SC0000's movie is an independently updating retained background. The script prepares later
|
||||
// static surfaces before its 0x21c yield; those layers composite above the current movie sample.
|
||||
if (_host.TryGetActiveMovieFrame(out var movieFrame) &&
|
||||
movieFrame.Width == _screen.GetWidth() && movieFrame.Height == _screen.GetHeight())
|
||||
_screen.SetData(movieFrame.Width, movieFrame.Height, false, Image.Format.Rgba8, movieFrame.Pixels);
|
||||
_speaker.Visible = false;
|
||||
System.Collections.Generic.Dictionary<long, string>? decisions = _gfxLogPath != null || _timeline != null ? new() : null;
|
||||
int z = 0;
|
||||
@@ -357,7 +352,7 @@ public partial class Main : Godot.Control
|
||||
else
|
||||
{
|
||||
BlitLayer(texture.Value.Image, texture.Value.AssetId, v.ColorKey, v.Tint, strength, v.SrcX, v.SrcY, v.W, v.H,
|
||||
localToDest, opacity, v.MultiplyTint);
|
||||
localToDest, opacity, v.MultiplyTint, texture.Value.IsDynamic);
|
||||
var raw = _vm.Gfx.TryGet(v.Handle);
|
||||
outcome = $"slot={raw?.SourceSlot} DRAWN resId=0x{v.SurfaceResId:x} {texture.Value.Name} " +
|
||||
$"src=({v.SrcX},{v.SrcY} {v.W}x{v.H}) base=({v.DstX},{v.DstY}) " +
|
||||
@@ -422,7 +417,8 @@ public partial class Main : Godot.Control
|
||||
var texture = _host.ResolveResIdTexture(source.SurfaceResId);
|
||||
if (texture == null) continue;
|
||||
BlitLayer(texture.Value.Image, texture.Value.AssetId, source.ColorKey, source.Tint, source.TintStrength / 255f,
|
||||
source.SrcX, source.SrcY, source.W, source.H, affine, opacity, source.MultiplyTint);
|
||||
source.SrcX, source.SrcY, source.W, source.H, affine, opacity, source.MultiplyTint,
|
||||
texture.Value.IsDynamic);
|
||||
}
|
||||
drawn++;
|
||||
}
|
||||
@@ -464,10 +460,19 @@ public partial class Main : Godot.Control
|
||||
// Mode 0 uses tintStrength to LERP texel RGB toward tint. Mode 1 sets multiplyTint and uses packed RGB as
|
||||
// multiplicative modulation while alpha is object opacity.
|
||||
private void BlitLayer(RgbaImage decoded, int assetId, long colorKey, long tint, float tintStrength, int srcX, int srcY, int w, int h,
|
||||
Age.Engine.Model.Affine2D localToDest, float alpha = 1f, bool multiplyTint = false)
|
||||
Age.Engine.Model.Affine2D localToDest, float alpha = 1f, bool multiplyTint = false,
|
||||
bool dynamic = false)
|
||||
{
|
||||
var cacheKey = (assetId, colorKey);
|
||||
if (!_imgCache.TryGetValue(cacheKey, out var src))
|
||||
Image? src;
|
||||
if (dynamic)
|
||||
{
|
||||
// Decoder samples replace the pixels of one retained surface. Catalog identity is stable across
|
||||
// those samples, so the static AGF cache key would otherwise freeze the very first movie frame.
|
||||
src = Image.CreateFromData(decoded.Width, decoded.Height, false, Image.Format.Rgba8, decoded.Pixels);
|
||||
if (Age.Engine.Model.BlendMath.HasColorKey(colorKey)) BakeColorKey(src, colorKey);
|
||||
}
|
||||
else if (!_imgCache.TryGetValue(cacheKey, out src))
|
||||
{
|
||||
src = Image.CreateFromData(decoded.Width, decoded.Height, false, Image.Format.Rgba8, decoded.Pixels);
|
||||
if (Age.Engine.Model.BlendMath.HasColorKey(colorKey)) BakeColorKey(src, colorKey);
|
||||
|
||||
Reference in New Issue
Block a user