Fix MPEG movie startup cadence

This commit is contained in:
gamer147
2026-07-28 12:16:16 -04:00
parent b2f1b4a45e
commit fdc7bb2bbc
10 changed files with 269 additions and 34 deletions

View File

@@ -366,6 +366,38 @@ catalog opener as `0x20f`. Both paths now decode the MPEG audio pin through FFmp
float PCM. Godot owns one `AudioStreamGenerator` per playback instance, applies the native flag-selected
mute/music/SE/voice or default movie route, and uses the sound-hardware position as the presentation clock.
**Native first-frame preroll and stream origin (2026-07-28):** AGE does not seek or manually decode an
opening frame. `movie_to_texture_open_asset_graph@0x463e20` finishes with the DirectShow graph stopped.
`movie_start_modal_playback@0x463280` then calls `IMediaControl::Run` at vtable slot `+0x1c`; there is no
preceding `Pause` or `IMediaPosition` mutation in `0x20f`. DirectShow's stopped-to-running transition
necessarily passes through paused/preroll. For a file source, the graph manager waits for each renderer to
receive a sample; a video renderer displays that held first sample as a poster image before the graph enters
running state. See Microsoft's [Filter States](https://learn.microsoft.com/en-us/windows/win32/directshow/filter-states)
contract.
The custom `movie_texture_renderer_receive_sample@0x4628d0` copies that preroll sample into the D3D texture
and sets renderer `+0x5b4`. `movie_consume_renderer_new_frame_flag@0x405120` clears the flag and makes the
outer engine tick render the retained surface. Audio is cued during the same graph transition but begins only
with the running reference clock. Native observation adds the decisive cadence constraint: OP's image advances
immediately with its audio; it does not hold the preroll image for roughly 600 ms. AGE contains no later
timestamp correction, so the DirectShow splitter/graph is necessarily presenting the video stream relative to
its first sample rather than exposing FFmpeg's shared program-stream timestamp origin. This last mapping is an
inference about DirectShow internals, but the visible native behavior is the parity oracle.
The installed FFmpeg probe rules out dropped or transparent source frames. LOGO and OP first report video PTS
600/601 ms on the shim's shared mux timeline, then deliver every tested opening frame at consecutive 33/34 ms
steps; pixels change within the first 20 frames. Every decoded alpha byte in those frames is 255. MPEG-1 has no
alpha plane. Native `movie_texture_renderer_set_media_type@0x463750` accepts RGB24/RGB32, and the RGB24 copy
path promotes pixels to alpha `0xff` before comparing the complete packed ARGB value with its renderer key.
Opcode `0x20f` attaches that renderer to the existing D3D target and never reloads the retained surface or
assigns it an RGB color key. The former port did both `Gfx.SetSurface(..., colorKey: 0)` and RGB-only keying,
which incorrectly made exact-black movie texels transparent and explained grey-background MPEG speckles.
The corrected port publishes frame zero before starting audio, then schedules all video frames from
`source_video_pts - first_video_pts`; audio retains its own complete stream from timestamp zero. No video or
audio samples are discarded. Signal-bearing PCM in OP's first 600 ms proves only that the audio data is real,
not that the MPEG mux's cross-stream start offset is an intended 600 ms visible lead.
An existing native operand trace identifies every observed heap codebase by a 100% match against its static
instruction-offset set. The captured New Game route is:
@@ -1293,18 +1325,22 @@ their later lifecycle/presentation slices.
### Movie-to-surface opcode `0x236` (2026-07-11)
The exact ABI is `play-movie-to-surface(resource_id, surface_slot, movie_flags, sync_mask)`. Handler
The exact ABI is `play-movie-to-surface(resource_id, surface_slot, movie_flags, start_delay_ms)`. Handler
`op_0x236_play_movie_to_surface@0x423ee0` records a 9-dword instruction length and requires the destination texture to exist.
It allocates/reuses a 0x478-byte `CMovieToTexture` object, binds the D3D device/backing texture, opens
operand 1 through `asset_open_indexed_entry`, constructs a DirectShow FilterGraph, and starts it. The graph
operand 1 through `asset_open_indexed_entry`, constructs a stopped DirectShow FilterGraph, and configures its
pending start. The graph
queries `IGraphBuilder`, `IMediaControl`, `IMediaPosition`, `IMediaEvent`, and `IBasicAudio`; its custom
`CMovieTextureRenderer` accepts RGB samples and copies the bottom-up frame into the retained texture.
Operand 3 is retained movie mode plus sound-route policy. Bits `0x10000/0x20000/0x40000/0x80000` force
mute/music/SE/voice routing; without an override, native setting `set:DependMovieSound` supplies the normal
movie route. The port applies those categories through Godot audio buses. Operand 4 is stored at movie object `+0x42c` as
the sync/device mask; it is not a duration or loop count. Replacing or releasing the owning surface stops
the graph and detaches the renderer.
movie route. The port applies those categories through Godot audio buses. Operand 4 is stored at movie object
`+0x42c` as a millisecond start delay: `movie_start_pending_after_sync_delay` records `timeGetTime()` on the
first service tick and starts the graph once `current - start >= operand4`. It is not a device mask or movie
duration. Replacing or releasing the owning surface stops the graph and detaches the renderer. The port still
threads this value through a legacy `syncMask`-named host parameter but does not yet delay FFmpeg start for
nonzero values. The corpus has 25 sites: 24 pass literal zero, while BTL supplies a dynamic local-pointer value.
Graph construction/open is synchronous, but playback and sample delivery are asynchronous. The handler
returns normally and the interpreter advances one instruction: at SC0000 `0x13c8`, the native operand
@@ -1314,6 +1350,11 @@ 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.
Non-modal start is deferred through `movie_play_configure@0x4625e0`: the outer tick's
`movie_start_pending_after_sync_delay@0x4633b0` records the first service time and calls the same shared
`IMediaControl::Run` worker once operand 4's sync delay has elapsed. A zero mask starts on the first service
tick. It therefore receives the same stopped-to-paused preroll and first-frame poster behavior as modal `0x20f`.
**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