Skip to content

nv2a: Update Conexant field counter on vblank#2537

Open
abaire wants to merge 2 commits into
xemu-project:masterfrom
abaire:fix_1962_update_conexant_field_count_on_vblank
Open

nv2a: Update Conexant field counter on vblank#2537
abaire wants to merge 2 commits into
xemu-project:masterfrom
abaire:fix_1962_update_conexant_field_count_on_vblank

Conversation

@abaire

@abaire abaire commented Nov 13, 2025

Copy link
Copy Markdown
Member

DreamWorks: Over the Hedge performs D3D resets before playing FMVs at startup. As a part of this process, an initialization is performed that checks to see if a DirectX frame counter aligns with the counter reported by the TV encoder (at least if Conexant is the encoder, it looks like it is bypassed for other cases). If there is a mismatch, the DirectX frame counter is incremented.

This can cause an issue where the vblank interrupt handler misses the expected frame counter, causing it to fail to update the nv2a buffer read index via the NV_PGRAPH_INCREMENT PGRAPH register. In this particular title, this leads to a livelock where the pushbuffer is waiting on a buffer flip that cannot be triggered until the current buffer is marked as read via the register write.

This change populates the low nibble of the Conexant device with a simple frame counter. See https://xboxdevwiki.net/Video_Encoder for a link to the datasheet.

Unfortunately testing this on HW has proved difficult, as some state is changed during D3D Present calls that causes SMBus reads to stop providing accurate data. Thus, this change is mostly a guess at the actual behavior along with some light testing without using D3D that shows the field value incrementing from 0-3 and wrapping around forever, presumably aligned with vsync.

Fixes #1962

Comment thread hw/xbox/nv2a/nv2a.c Outdated
VGACommonState *vga = opaque;
vga->hw_ops->gfx_update(vga);

smbus_cx25871_notify_vblank();

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When investigating #1962 it appeared that the code checking for the field count was only run for the Conexant encoder. That said, it seems plausible that the other encoders might have a similar mechanism that could be queried in other cases.

I wonder if there's a callback mechanism that we could use to notify (at least TV encoder) devices of vblank instead of calling out from here? That would potentially let us remove the object_resolve_path_type call as well.

@abaire abaire marked this pull request as draft November 14, 2025 00:58
@abaire abaire force-pushed the fix_1962_update_conexant_field_count_on_vblank branch from dc764c1 to 37f262a Compare November 14, 2025 01:47
@abaire abaire marked this pull request as ready for review November 15, 2025 01:27
@abaire

abaire commented Nov 15, 2025

Copy link
Copy Markdown
Member Author

Verified that the report of the fix not working is related to M8+ bios use. I suspect the bios does something that stops it from checking the field count.

@abaire

abaire commented Dec 9, 2025

Copy link
Copy Markdown
Member Author

/gemini review

(Testing to see if code assist is enabled)

@abaire abaire force-pushed the fix_1962_update_conexant_field_count_on_vblank branch from 37f262a to 2ce1d00 Compare January 19, 2026 13:44
@abaire abaire force-pushed the fix_1962_update_conexant_field_count_on_vblank branch from 2ce1d00 to 6a7ee08 Compare February 10, 2026 19:14
@abaire abaire force-pushed the fix_1962_update_conexant_field_count_on_vblank branch from 6a7ee08 to f9350ff Compare February 16, 2026 04:41

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request implements a VBLANK notification mechanism for the Conexant CX25871 SMBus device to update its internal field counter. The feedback identifies that the notification call should be moved outside the interrupt-pending check to ensure the counter increments independently of the guest's interrupt handling. Additionally, the implementation should cache the device object lookup to improve performance in the high-frequency VBLANK path and refine the register masking logic to avoid clearing unrelated status bits.

Comment thread hw/xbox/nv2a/nv2a.c
Comment on lines +204 to +207
if (!(d->pcrtc.pending_interrupts & NV_PCRTC_INTR_0_VBLANK)) {
smbus_cx25871_notify_vblank();
d->pcrtc.pending_interrupts |= NV_PCRTC_INTR_0_VBLANK;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The smbus_cx25871_notify_vblank() call is currently inside the if block that checks if a VBLANK interrupt is already pending. This means the field counter will stop incrementing if the guest is slow to acknowledge the interrupt. Since the hardware field counter should tick independently of the interrupt status, it is better to move the notification outside the conditional block.

    smbus_cx25871_notify_vblank();
    if (!(d->pcrtc.pending_interrupts & NV_PCRTC_INTR_0_VBLANK)) {
        d->pcrtc.pending_interrupts |= NV_PCRTC_INTR_0_VBLANK;
    }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it's guaranteed that nv2a_vga_gfx_update is only called during a vblank; if we're missing a vblank interrupt I would imagine that the guest software would believe that the FIELD_CNT should not have been increased yet and it would be erroneous to let it increment late (arguably early from the guest point of view?).

Comment thread hw/xbox/smbus_cx25871.c Outdated
Comment on lines +123 to +128
Object *obj = object_resolve_path_type("", TYPE_SMBUS_CX25871, NULL);
if (obj) {
SMBusCX25871Device *cx = SMBUS_CX25871(obj);
cx->registers[0x06] =
(cx->registers[0x06] & 0xF0) | ((cx->registers[0x06] + 1) & 0x03);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The current implementation performs a full object tree lookup (object_resolve_path_type) on every vblank, which is inefficient for a high-frequency path (typically 60Hz). Caching the resolved object pointer would improve performance.

Additionally, the register update logic (cx->registers[0x06] & 0xF0) | ((cx->registers[0x06] + 1) & 0x03) clears bits 2 and 3 of the register. Since the field counter is described as a 2-bit value (0-3), it is safer to use a mask that only affects those two bits (e.g., ~0x03) to avoid side effects on other status bits in the same register.

    static SMBusCX25871Device *cx;
    static bool searched = false;

    if (unlikely(!searched)) {
        Object *obj = object_resolve_path_type("", TYPE_SMBUS_CX25871, NULL);
        cx = obj ? SMBUS_CX25871(obj) : NULL;
        searched = true;
    }

    if (cx) {
        cx->registers[0x06] = (cx->registers[0x06] & ~0x03) |
                              ((cx->registers[0x06] + 1) & 0x03);
    }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caching seems like a reasonable thing to do; implemented. I believe the object will only be created due to smbus_cx25871_init being called so we could probably cache in the init function itself rather than doing a deferred static hack in the vsync handler.

I'm not sure if objects can move around or be reallocated; @mborgerson do you know if it's safe to permacache the object given that it's not a removable device?

The mask was technically overly conservative (FIELD_CNT is the low nibble of register 6 based on the datasheet) though in practice the kernel only seems to use it as an even/odd tracker. Expanded and added a symbolic constant since I couldn't remember the name to verify it.

DreamWorks: Over the Hedge performs D3D resets before playing FMVs at startup.
As a part of this process, an initialization is performed that checks to see if
a DirectX frame counter aligns with the counter reported by the TV encoder (at
least if Conexant is the encoder, it looks like it is bypassed for other cases).
If there is a mismatch, the DirectX frame counter is incremented.

This can cause an issue where the vblank interrupt handler misses the expected
frame counter, causing it to fail to update the nv2a buffer read index via the
NV_PGRAPH_INCREMENT PGRAPH register. In this particular title, this leads to a
livelock where the pushbuffer is waiting on a buffer flip that cannot be
triggered until the current buffer is marked as read via the register write.

This change populates the low nibble of the Conexant device with a simple frame
counter. See https://xboxdevwiki.net/Video_Encoder for a link to the datasheet.

Unfortunately testing this on HW has proved difficult, as some state is changed
during D3D Present calls that causes SMBus reads to stop providing accurate
data. Thus, this change is mostly a guess at the actual behavior along with
some light testing without using D3D that shows the field value incrementing
from 0-3 and wrapping around forever, presumably aligned with vsync.

Fixes xemu-project#1962
@abaire abaire force-pushed the fix_1962_update_conexant_field_count_on_vblank branch from f9350ff to 7b0937e Compare May 8, 2026 13:40
@MasonT8198

Copy link
Copy Markdown
Contributor

I triggered this then deleted the message bc I figured I wouldn't have perms to get it going lol, sorry if that was confusing as to why the AI randomly decided to start going

@abaire

abaire commented May 8, 2026

Copy link
Copy Markdown
Member Author

I'm happy you did, the suggestions were mostly good ones

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.

DreamWorks Over the Hedge: Doesn't make it past the intro FMV's

2 participants