Skip to content

GL: re-implement the RSP pipeline on top of magma - #906

Open
snacchus wants to merge 1 commit into
DragonMinded:previewfrom
snacchus:gl-magma-port
Open

snacchus wants to merge 1 commit into
DragonMinded:previewfrom
snacchus:gl-magma-port

Conversation

@snacchus

@snacchus snacchus commented Jun 13, 2026

Copy link
Copy Markdown
Contributor

This PR completely replaces the RSP pipeline of the OpenGL implementation with an improved version that is based on magma. This results in overall greatly improved performance (up to ~4,6x less RSP time required has been measured).

Overview

The rspq overlay rsp_gl_pipeline has been removed and replaced with a magma vertex shader of the same name. Like the old overlay, the shader expects vertices in a fixed format, but flexible layout. Since magma works most efficiently when loading large batches of vertices, the new implementation therefore has to convert all vertex data into internal buffers before handing them off to magma. The data is converted on demand when draw calls are dispatched.

In some cases, whenever GL is able to track changes to vertex layout and vertex data, converted data may be retained and reused across frames. This is the case when using vertex buffer objects and vertex array objects in conjunction. This is consequently the most optimal way to render geometry now, as opposed to display lists with the old implementation.

Because best practices for optimal performance are changing with this re-implementation, drawing models with model64 will not be optimal for now. The performance benefits from the new implementation still outweigh this pessimization, however (see measurements below). An improved version of model64 will follow in a later PR.

Performance comparison

The following measurements were made on a real PAL N64.

This is the baseline, using the old RSP pipeline:
Profiler results: baseline

Compare with the new, magma based implementation without further modifications to model64:
Profiler results: new implementation with unoptimized mesh

And finally, the new implementation using an optimized version of model64:
Profiler results: new implementation with optimized mesh

Breaking changes

  • The currently bound GL_ELEMENT_ARRAY_BUFFER_ARB is no longer a global state and instead now scoped to the current vertex array object. This now correctly implements the GL specification.
  • Texture coordinates with 3 or more components don't work correctly in the new implementation. They are effectively treated as 2 components. Depending on demand, this will be fixed in the future.
  • Tex gen with GL_OBJECT_PLANE and GL_EYE_PLANE is no longer supported by the RSP pipeline. The CPU pipeline will be used instead.

Other changes

  • The new packed format GL_SHORT_5_6_5_N64 has been added and is available for use in glNormalPointer.
  • The custom hashtable implementation in obj_map.h has been removed and usages migrated to hashtable_internal.h.

@snacchus
snacchus marked this pull request as ready for review June 14, 2026 13:41

@Dragorn421 Dragorn421 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

(this visually passes my triangle tests at https://github.com/Dragorn421/n64homebrew/tree/main/gltest (on ares))

@rasky rasky left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I took the time of going through the PR. I didn't have time to read it all or rather I don't understand the full architecture. So I just jumped here and there, studied a bit of code irrespective of the context, and just browsed it in the editor leaving comments. I think I might have spotted a few bugs from patterns that looked unconvincing but again, missing context, it's a bit hard for me to make sure a bug is a bug.

Comment thread src/GL/rsp_pipeline.c
mg_input_assembly_parms_t input_assembly_parms = array_object_get_input_assembly_parms(state->array_object, mode, range);
mg_ex_draw(&input_assembly_parms, count, first, mode);
mg_draw_end();
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

If you create the array object already offseted by first, why you need to pass first to mg_ex_draw? Is that a bug? Did you test glDrawArray with first != 0?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Well spotted! That was indeed a bug.

Comment thread src/GL/list.c
assertf(!state->begin_end_active, "glCallList between glBegin/glEnd is not supported!");

rspq_block_t *block = obj_map_get(&state->list_objects, n);
rspq_block_t *block = hashtable_lookup(&state->lists, n);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Switching from obj_map to hashtable seems to have removed the NULL check. I think both 0 and TOMBSTONE_KEY are reserved values that should return GL_INVALID_VALUE instead of crashing

Comment thread src/GL/rsp_pipeline.c
for (array_type_t i = 0; i < ARRAY_COUNT; i++)
{
parms->arrays[i] = &state->array_object->arrays[i];
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Shouldn't this be filtered by is_array_used? We have a similar filter in data_source.c. It would seem that arrays could easy have NULL pointers otherwise and lead to crashes?

VTEMP = vlcol:sfract +* vlpos:sfract.xxxxXXXX;
VTEMP = vlcol:sfract +* vlpos:sfract.yyyyYYYY;
vlcol = vlcol:sfract +* vlpos:sfract.zzzzZZZZ;
vlcol = max(vlcol, VZERO);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I think the N*L dot product should be clamped (like we do gl_clamped_dot in the CPU pipeline. Also I'm not sure about how the attenuation is handled. Should it be only applied to vlcol (which is the diffuse term if I understand correctly?)

Comment thread src/GL/rsp_pipeline.c

static void gl_rsp_draw_elements(GLenum mode, uint32_t count, const void* indices, GLenum type)
{
assertf(type == GL_UNSIGNED_SHORT, "Index type must be GL_UNSIGNED_SHORT");

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

We were handling different types before. I guess this is not a huge issue, but maybe it should be documented as a change.

Comment thread src/GL/data_source.h
typedef struct {
uint32_t offsets[ARRAY_COUNT];
uint32_t stride;
} data_layout_t;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It seems that you're then not using array_type_t to index offsets[], but rather a compact index. This maybe also warrants various comments as it can be tricky to get right.

Comment thread src/GL/array_convert.h
const data_layout_t *out_layout;
index_bounds_t range;
void *out_buffer;
} array_convert_parms_t;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I could use some documentation of this structure and how the various fields relate.

Comment thread src/GL/draw_call_cache.h
uint32_t offset;
uint32_t count;
GLenum mode;
} draw_call_parms_t;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Can you also document this structure? I think offset is a byte offset, while count is an element count. This is non-trivial.

#define SCRATCH_MAT_DIFFUSE 0x30
#define SCRATCH_MAT_AMBIENT 0x40
#define SCRATCH_MAT_EMISSIVE 0x50
#define SCRATCH_RGBA_IN 0x60

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

I guess these offsets are into a scratch area? But we don't have a RSPL include file from Magma that defines the base offset for it?

@@ -0,0 +1,573 @@
include "rsp_magma.inc"

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Maybe a top-level comment explaining what this ucode does, how it interacts with the others, etc.

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.

3 participants