Skip to content

dmd: support DMD Studio colorization bypass#1

Draft
freezy wants to merge 3 commits into
masterfrom
feature/dmd-studio
Draft

dmd: support DMD Studio colorization bypass#1
freezy wants to merge 3 commits into
masterfrom
feature/dmd-studio

Conversation

@freezy

@freezy freezy commented Jul 15, 2026

Copy link
Copy Markdown
Member

Overview

This is the display-pipeline companion to VisualPinball.Engine#570. It ensures DMD Studio output remains visible when a display has colorization configured but the incoming frame format cannot be consumed by that colorizer.

Without this behavior, native RGB content and certain startup races can leave the display blank because frames continue to enter a pipeline that only accepts indexed monochrome input.

Functional behavior

  • Standard 2-shade and 4-shade monochrome frames continue through the configured colorization pipeline.
  • Native RGB frames switch the display to passthrough output, allowing DMD Studio RGB projects to retain their authored colors instead of being rejected by an incompatible colorizer.
  • A transient 8-bit frame can temporarily use passthrough so the triggering frame is not lost, while the bridge continues requesting a supported monochrome format.
  • When supported 2-shade or 4-shade content arrives after that transient condition, colorization is restored automatically.
  • Native RGB output remains in passthrough for the current display topology because it represents intentionally RGB-authored content rather than a recoverable negotiation mismatch.
  • The frame that triggers a pipeline change is submitted again after the pipeline is rebuilt, preventing a visible one-frame gap.
  • Repeated display announcements with unchanged configuration preserve the current output instead of clearing and rebuilding it, avoiding unnecessary flicker.
  • A genuine display-topology change resets the prior bypass decision so the new display configuration can be evaluated independently.
  • Each distinct transient bypass produces one diagnostic, while repeated frames within the same bypass state do not flood the log.

User-visible result

Tables can combine configured DMD colorization with DMD Studio content without producing a black display when RGB content is played. Existing monochrome colorization remains active for compatible frames, and a temporary negotiation mismatch can recover without requiring a scene reload or display reconfiguration.

Verification

  • 18 DMD pipeline tests pass.
  • Coverage includes compatible colorized input, native RGB passthrough, repeated transient 8-bit recovery, triggering-frame preservation, repeat announcements, topology changes, and diagnostic latching.

@greptile-apps

greptile-apps Bot commented Jul 15, 2026

Copy link
Copy Markdown

Greptile Summary

This PR introduces DmdColorizationPolicy, a small state machine that decides whether the DMD pipeline should bypass its colorization converter when the incoming frame format is incompatible (Dmd24/sticky, Dmd8/transient), and automatically restore colorization when a compatible frame arrives. DmdBridgePlayer is updated to consult the policy on every frame and force-rebuild the pipeline on a bypass or restore transition, with the triggering frame pushed to the newly rebuilt pipeline to prevent a one-frame gap.

  • DmdColorizationPolicy tracks two bypass modes: a permanent (sticky) bypass for Dmd24 RGB content, and a transient bypass for Dmd8 frames where colorization is restored as soon as Dmd2/Dmd4 arrives.
  • DmdPipeline gains a UsesColorization property so the policy can distinguish a passthrough pipeline from a colorized one without needing to re-examine the converter.
  • 17 new unit tests are added covering compatible flow, sticky RGB bypass, transient recovery, topology-change reset, repeat-announcement preservation, and diagnostic latching.

Confidence Score: 5/5

Safe to merge. The bypass state machine is well-scoped, all transitions are covered by tests, and the pipeline rebuild is correctly guarded on the main thread.

The new DmdColorizationPolicy is a small, self-contained state machine with 17 tests that exercise every documented transition. The integration into DmdBridgePlayer is straightforward: policy observation and pipeline rebuilds happen on the same main thread that Unity's frame handler runs on, avoiding any new threading concerns. _warningIssued is correctly reset in the Restore branch, and the triggering frame is forwarded to the newly built pipeline without a gap. No correctness issues were found.

No files require special attention.

Important Files Changed

Filename Overview
VisualPinball.Engine.DMD.Unity/Runtime/DmdColorizationPolicy.cs New policy class; logic is correct and well-covered. _warningIssued is properly reset in the Restore branch (line 53). SameTopology intentionally excludes colour fields, matching documented intent.
VisualPinball.Engine.DMD.Unity/Runtime/DmdBridgePlayer.cs Integration of the policy into the bridge is sound. Pipeline rebuild on bypass/restore is guarded behind the existing null-pipeline early-return, _currentDisplay is always non-null when the colorization block is reached, and EnsurePipeline with force:true always rebuilds. Minor readability nit in PreferredSourceFormat.
VisualPinball.Engine.DMD.Unity/Runtime/VpeGleSource.cs Adds a per-instance _unsupportedFormatWarningLogged guard and a default branch to ColorizableVpeGleSource.Push; guards against log spam during the brief worker-thread window before the main-thread rebuild completes. Logger field added correctly.
VisualPinball.Engine.DMD.Unity/Runtime/DmdPipeline.cs Minimal, safe change: adds UsesColorization auto-property set from converter != null in the constructor.
VisualPinball.Engine.DMD.Unity/Tests/Runtime/DmdColorizationPolicyTests.cs Comprehensive test suite covering all documented policy behaviors; uses reflection to access VpeGleSource.Create for the source-advertisement tests.
VisualPinball.Engine.DMD.Unity/Runtime/AssemblyInfo.cs Adds InternalsVisibleTo for the new test assembly so internal types are reachable from tests.
VisualPinball.Engine.DMD.Unity/Tests/Runtime/VisualPinball.Engine.DMD.Unity.Test.asmdef Test assembly definition targeting Editor platform with UNITY_INCLUDE_TESTS guard; references runtime assembly correctly.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant GLE as GamelogicEngine
    participant DBP as DmdBridgePlayer (main thread)
    participant DCP as DmdColorizationPolicy
    participant DP as DmdPipeline

    GLE->>DBP: OnDisplaysRequested(display)
    DBP->>DCP: SelectDisplay(display)
    DBP->>DP: new DmdPipeline(colorized)

    GLE->>DBP: OnDisplayUpdateFrame(Dmd24 frame)
    DBP->>DCP: "ObserveFrame(Dmd24, usesColorization=true)"
    DCP-->>DBP: "Bypass (sticky), shouldWarn=true"
    DBP->>DBP: Log warning
    DBP->>DP: Dispose old pipeline
    DBP->>DCP: SelectDisplay(same display) no-op
    DBP->>DP: new DmdPipeline(passthrough)
    DBP->>GLE: RequestSourceFrameFormat(Dmd8)
    DBP->>DP: Push(Dmd24 frame) passthrough

    GLE->>DBP: OnDisplayUpdateFrame(Dmd8 frame)
    DBP->>DCP: "ObserveFrame(Dmd8, usesColorization=false)"
    DCP-->>DBP: None (sticky bypass, no recovery)
    DBP->>DP: Push(Dmd8 frame) passthrough

    Note over DBP,DP: Transient Dmd8 recovery path
    GLE->>DBP: OnDisplayUpdateFrame(Dmd8 frame)
    DBP->>DCP: "ObserveFrame(Dmd8, usesColorization=true)"
    DCP-->>DBP: "Bypass (transient), AwaitingSupportedColorizedFrame=true"
    DBP->>DP: Dispose then new passthrough pipeline
    DBP->>GLE: RequestSourceFrameFormat(Dmd4)
    DBP->>DP: Push(Dmd8 frame)

    GLE->>DBP: OnDisplayUpdateFrame(Dmd4 frame)
    DBP->>DCP: "ObserveFrame(Dmd4, usesColorization=false)"
    DCP-->>DBP: Restore
    DBP->>DP: Dispose then new colorized pipeline
    DBP->>DP: Push(Dmd4 frame) colorized
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant GLE as GamelogicEngine
    participant DBP as DmdBridgePlayer (main thread)
    participant DCP as DmdColorizationPolicy
    participant DP as DmdPipeline

    GLE->>DBP: OnDisplaysRequested(display)
    DBP->>DCP: SelectDisplay(display)
    DBP->>DP: new DmdPipeline(colorized)

    GLE->>DBP: OnDisplayUpdateFrame(Dmd24 frame)
    DBP->>DCP: "ObserveFrame(Dmd24, usesColorization=true)"
    DCP-->>DBP: "Bypass (sticky), shouldWarn=true"
    DBP->>DBP: Log warning
    DBP->>DP: Dispose old pipeline
    DBP->>DCP: SelectDisplay(same display) no-op
    DBP->>DP: new DmdPipeline(passthrough)
    DBP->>GLE: RequestSourceFrameFormat(Dmd8)
    DBP->>DP: Push(Dmd24 frame) passthrough

    GLE->>DBP: OnDisplayUpdateFrame(Dmd8 frame)
    DBP->>DCP: "ObserveFrame(Dmd8, usesColorization=false)"
    DCP-->>DBP: None (sticky bypass, no recovery)
    DBP->>DP: Push(Dmd8 frame) passthrough

    Note over DBP,DP: Transient Dmd8 recovery path
    GLE->>DBP: OnDisplayUpdateFrame(Dmd8 frame)
    DBP->>DCP: "ObserveFrame(Dmd8, usesColorization=true)"
    DCP-->>DBP: "Bypass (transient), AwaitingSupportedColorizedFrame=true"
    DBP->>DP: Dispose then new passthrough pipeline
    DBP->>GLE: RequestSourceFrameFormat(Dmd4)
    DBP->>DP: Push(Dmd8 frame)

    GLE->>DBP: OnDisplayUpdateFrame(Dmd4 frame)
    DBP->>DCP: "ObserveFrame(Dmd4, usesColorization=false)"
    DCP-->>DBP: Restore
    DBP->>DP: Dispose then new colorized pipeline
    DBP->>DP: Push(Dmd4 frame) colorized
Loading

Reviews (2): Last reviewed commit: "dmd: rearm transient bypass diagnostics" | Re-trigger Greptile

Comment thread VisualPinball.Engine.DMD.Unity/Runtime/DmdColorizationPolicy.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant