Skip to content

Conversation

@KraftMacAndChee
Copy link

@KraftMacAndChee KraftMacAndChee commented Jan 16, 2026

Fixes #2671

@gemini-code-assist
Copy link

Summary of Changes

Hello @KraftMacAndChee, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly refactors the image blitting functionality within the NV2A PGRAPH component by extracting the core blit logic into a shared, renderer-agnostic implementation. This change eliminates code duplication between the OpenGL and Vulkan rendering backends, making the blitting process more maintainable and easier to extend in the future. The renderer-specific files now act as lightweight wrappers, providing their unique surface management functions to the unified blit routine.

Highlights

  • Shared Blit Implementation: A new common image blit function, pgraph_common_image_blit, has been introduced to centralize the core blitting logic for the NV2A PGRAPH renderer.
  • Renderer Agnostic Interface: A PGRAPHSurfaceOps struct was added, defining a set of function pointers for surface operations, allowing the common blit function to interact with renderer-specific surface management.
  • GL Blit Refactoring: The OpenGL-specific blit implementation (pgraph_gl_image_blit) has been refactored to delegate its operations to the new common blit function, passing its own GL-specific surface operation callbacks.
  • Vulkan Blit Refactoring: Similarly, the Vulkan-specific blit implementation (pgraph_vk_image_blit) has been refactored to utilize the common blit function, providing its Vulkan-specific surface operation callbacks.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

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

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 is a great refactoring that consolidates duplicated image blitting logic from the OpenGL and Vulkan backends into a shared implementation. This significantly improves maintainability. I've reviewed the new common implementation and found a few potential correctness issues that were likely present in the original code. My comments below provide suggestions to make the implementation more robust.

Comment on lines 30 to 37
PGRAPHState *pg = &d->pgraph;
ContextSurfaces2DState *context_surfaces = &pg->context_surfaces_2d;
ImageBlitState *image_blit = &pg->image_blit;
BetaState *beta = &pg->beta;

ops->surface_update(d, false, true, true);

assert(context_surfaces->object_instance == image_blit->context_surfaces);

Choose a reason for hiding this comment

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

high

Blits with zero width or height should be handled as no-ops. The current code proceeds with calculations that can lead to integer underflow when calculating buffer sizes (e.g., image_blit->height - 1 when height is 0). This can cause the bounds-checking assertions to fail or behave incorrectly.

It's best to add an early return at the beginning of the function if the width or height is zero.

    PGRAPHState *pg = &d->pgraph;
    ContextSurfaces2DState *context_surfaces = &pg->context_surfaces_2d;
    ImageBlitState *image_blit = &pg->image_blit;

    if (image_blit->width == 0 || image_blit->height == 0) {
        return;
    }

    BetaState *beta = &pg->beta;

    ops->surface_update(d, false, true, true);

    assert(context_surfaces->object_instance == image_blit->context_surfaces);

Copy link
Member

Choose a reason for hiding this comment

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

It probably does make sense to bail early on zero-sized draws. I don't think we perform any interesting side effects that wouldn't be covered elsewhere in the pipeline.

dest_row += context_surfaces->dest_pitch;
}
} else if (image_blit->operation == NV09F_SET_OPERATION_BLEND_AND) {
// NV2A_GL_DPRINTF(false, "NV09F_SET_OPERATION_BLEND_AND");

Choose a reason for hiding this comment

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

high

The BLEND_AND operation implementation assumes a 32bpp format by using x * 4 for pixel addressing. This will cause incorrect memory access if used with formats that have a different number of bytes per pixel (e.g., 1 or 2).

An assertion should be added to ensure this operation is only used with 32bpp formats.

        assert(bytes_per_pixel == 4 && "BLEND_AND is only implemented for 32bpp formats");

Comment on lines 124 to 125
uint32_t beta_mult = beta->beta >> 16;
uint32_t inv_beta_mult = max_beta_mult - beta_mult;

Choose a reason for hiding this comment

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

high

If beta_mult is greater than max_beta_mult, inv_beta_mult will wrap around due to unsigned integer arithmetic, resulting in a very large positive value. This will lead to incorrect blending calculations.

beta_mult should be clamped to max_beta_mult to prevent this.

        uint32_t beta_mult = MIN(beta->beta >> 16, max_beta_mult);
        uint32_t inv_beta_mult = max_beta_mult - beta_mult;

Copy link
Member

@abaire abaire left a comment

Choose a reason for hiding this comment

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

Mostly nitpicky comments

#include "renderer.h"
#include "hw/xbox/nv2a/pgraph/blit.h"

// TODO: Optimize.
Copy link
Member

Choose a reason for hiding this comment

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

I think we can remove this comment now. There's not much that could be done to optimize the one function call and the comment doesn't give any detail on how to do renderer-specific optimization.

#include "hw/xbox/nv2a/pgraph/blit.h"


// TODO: Optimize. Ideally this should all be done via OpenGL.
Copy link
Member

Choose a reason for hiding this comment

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

nit: Remove, now that this is a shared implementation it doesn't make sense to suggest GL.

Comment on lines 30 to 37
PGRAPHState *pg = &d->pgraph;
ContextSurfaces2DState *context_surfaces = &pg->context_surfaces_2d;
ImageBlitState *image_blit = &pg->image_blit;
BetaState *beta = &pg->beta;

ops->surface_update(d, false, true, true);

assert(context_surfaces->object_instance == image_blit->context_surfaces);
Copy link
Member

Choose a reason for hiding this comment

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

It probably does make sense to bail early on zero-sized draws. I don't think we perform any interesting side effects that wouldn't be covered elsewhere in the pipeline.

uint8_t *dest_row = dest + dest_offset;

if (image_blit->operation == NV09F_SET_OPERATION_SRCCOPY) {
// NV2A_GL_DPRINTF(false, "NV09F_SET_OPERATION_SRCCOPY");
Copy link
Member

Choose a reason for hiding this comment

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

nit: While you're in here, we can probably lose these commented-out DPRINTF's as well.

@@ -0,0 +1,13 @@
// Header for blit.c
Copy link
Member

Choose a reason for hiding this comment

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

I think we should bring over the comment from the original GL impl.

@@ -0,0 +1,13 @@
// Header for blit.c

#pragma once
Copy link
Member

Choose a reason for hiding this comment

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

I don't think we use pragma once outside of C++ files. Would be good to use old C-style guards like in

#ifndef HW_NV2A_REGS_H


#pragma once

#include "hw/xbox/nv2a/nv2a_int.h" // NV2AState, hwaddr, SurfaceBinding
Copy link
Member

Choose a reason for hiding this comment

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

nit: I don't think we declare the types used from headers elsewhere and these can easily go stale without automation. I think we should remove them.


#include "hw/xbox/nv2a/nv2a_int.h" // NV2AState, hwaddr, SurfaceBinding

typedef struct PGRAPHSurfaceOps {
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if we should put this in the PGRAPHRenderer so they're available everywhere?

typedef struct PGRAPHRenderer {

@mborgerson
Copy link
Member

mborgerson commented Jan 16, 2026

Looks like this change was made on top of be882c0 and not 24087af (which fixed a bug). Please rebase the patch

void pgraph_gl_image_blit(NV2AState *d)
{
static const PGRAPHSurfaceOps ops = {
.surface_update = pgraph_gl_surface_update,
Copy link
Member

Choose a reason for hiding this comment

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

Let's make this even simpler and drop the extra indirection here.

  • surface_update is already a renderer op
  • Just add surface_get, add surface_download_if_dirty and remove image_blit in PGRAPHRenderer::ops
  • Drop gl/blit.c, vk/blit.c, blit.h

Copy link
Member

@mborgerson mborgerson left a comment

Choose a reason for hiding this comment

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

Thanks for the patch. See comments about rebase and structure inline. Also don't worry about trying to fix pre-existing bugs with this patch. I've not evaluated the feedback of Gemini, but we should focus only on the unification in this patch. The delta between pgraph/blit.c, gl/blit.c, and vk/blit.c should be near 0. We can fix any bugs in the current implementation afterwards.

Used a shared implementation and dropped gl/blit.c. vk/blit.c, and blit.h
@KraftMacAndChee
Copy link
Author

Thanks for the patch. See comments about rebase and structure inline. Also don't worry about trying to fix pre-existing bugs with this patch. I've not evaluated the feedback of Gemini, but we should focus only on the unification in this patch. The delta between pgraph/blit.c, gl/blit.c, and vk/blit.c should be near 0. We can fix any bugs in the current implementation afterwards.

I rebased it as you requested, I then worked to unify everything following your instruction and also removed gl/blit.c, vk/blit.c, and blit.h as requested. I spent a long time looking everything over and trying to double check myself and make sure I did everything correctly. However, I would appreciate you looking over it as I'm still getting my feet wet on this kind of stuff again.

Copy link
Member

@abaire abaire left a comment

Choose a reason for hiding this comment

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

This still looks like it has dropped all of the changes from 24087af

You can use https://github.com/abaire/xemu-dev_pgraph_test_results to look for other regressions (I haven't had a chance to update the goldens for the latest xemu yet, so it wouldn't catch the blit regression that this PR will cause).

@KraftMacAndChee
Copy link
Author

This still looks like it has dropped all of the changes from 24087af

You can use https://github.com/abaire/xemu-dev_pgraph_test_results to look for other regressions (I haven't had a chance to update the goldens for the latest xemu yet, so it wouldn't catch the blit regression that this PR will cause).

Ah yes, you’re right. I wasn’t thinking and just rebased it without modifying anything. So it would be a regression. I’ll have to fix that when I get time.

#include "hw/xbox/nv2a/nv2a_int.h"
#include "renderer.h"

#include "hw/xbox/nv2a/pgraph/surface.h"
Copy link
Member

Choose a reason for hiding this comment

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

Already included (via nv2a_int.h, which includes pgraph.h, which includes surface.h)

dest += dest_pitch;
}
}
void pgraph_common_image_blit(NV2AState *d)
Copy link
Member

Choose a reason for hiding this comment

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

  • Fix whitespace
  • Use name pgraph_image_blit (this was the former name, and since there is only one 'common' is redundant)


if (image_blit->width && image_blit->height) {
d->pgraph.renderer->ops.image_blit(d);
pgraph_common_image_blit(d);
Copy link
Member

Choose a reason for hiding this comment

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

Should've pointed it out before, but "common" seems unnecessary if there are no renderer-spec. Maybe just "pgraph_image_blit"?

}
}
void pgraph_common_image_blit(NV2AState *d)
Copy link
Member

Choose a reason for hiding this comment

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

Remove newline

break;
}
hwaddr source_dma_len, dest_dma_len;
Copy link
Member

Choose a reason for hiding this comment

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

This is (I assume inadvertently) reverted from the split version in the current head, see line 104 and 111 in the diff.

leftover_bytes / bytes_per_pixel, 1, leftover_bytes,
context_surfaces->source_pitch,
context_surfaces->dest_pitch, beta);
}
Copy link
Member

Choose a reason for hiding this comment

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

This indentation is wrong.

Since this is a new file you can probably just run clang-format on it to avoid having to waste time with whitespace changes. I believe I clang-formatted the original blit file when I fixed the clipping bug.

SurfaceBinding *surf_dest = pg->renderer->ops.surface_get(d, dest_addr);
if (surf_dest) {
if (adjusted_height < surf_dest->height ||
Copy link
Member

Choose a reason for hiding this comment

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

This indentation looks wrong

dest += dest_pitch;
}
}
void pgraph_common_image_blit(NV2AState *d)
Copy link
Member

Choose a reason for hiding this comment

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

Add the newline above this back

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.

Refactor GL and VK blit.c code to a shared implementation

3 participants