I've been working on SoftRDP, a software implementation of the Nintendo 64 Reality Display Processor optimized for modern CPUs.
Performance
I did an end-to-end comparison using two games. Instead of showing raw emulator speed, every result is normalized against running with no video rendering at all, which is treated as the 100% maximum for that game.
The test machine is an Intel 8-core desktop CPU with a recent NVIDIA GPU. Those are good conditions for both approaches: SoftRDP has several strong CPU cores available, while paraLLEl-RDP is running on a mature NVIDIA Vulkan stack. GPU-compute results can vary with GPU architecture, Vulkan driver and shader compiler, so other driver stacks may perform differently or worse.
These are not isolated RDP microbenchmarks, so they also reflect how rendering interacts with the rest of emulation. The useful takeaway for me is that accurate native-resolution RDP rendering is a very manageable workload for a modern multicore CPU.
Most N64 rendering happens at relatively low native resolutions, commonly around 320×240. Pixel count is not the entire cost. Overdraw, texturing, blending and depth processing all matter, but it gives the CPU a favorable starting point.
The interesting part is making that workload map efficiently onto modern hardware.
Spread the work across cores
SoftRDP uses scanline-interleaved rendering.
Each worker processes the same ordered RDP commands with private RDP and TMEM state, but only renders its assigned scanlines. This gives the workers mostly independent output pixels and avoids fine-grained locking in the pixel pipeline.
The general scanline-interleaving approach was previously demonstrated by Angrylion RDP Plus.
SoftRDP also tracks pending framebuffer and depth writes. If later RDP work needs data that is still being produced, for example, a texture load reading from a recently rendered framebuffer, the dependency determines when that work has to be complete. This is derived from RDP memory behavior.
Process multiple pixels at once
Inside each worker, SoftRDP organizes rendering into 16-pixel packets.
This exposes independent arithmetic to SIMD and compiler vectorization. The color combiner is a particularly good fit: its configuration stays constant while its pixel inputs vary, allowing the AVX2 path to evaluate the complete combiner equation for eight pixels at once.
Other branch-free interpolation, perspective and texture calculations can also benefit from vectorization.
Not every part of the RDP maps cleanly to SIMD. Texture accesses, depth tests, blending and framebuffer operations can diverge or involve irregular memory access, so SoftRDP deliberately mixes vector and scalar processing instead of forcing the entire pipeline into vectors.
Build fast paths from RDP state
SoftRDP determines what work a primitive actually needs before processing its pixels.
The current RDP and primitive state is compiled into a processing plan. That determines which pipeline stages are required and selects specialized paths where possible.
An opaque write, for example, should not have to pass through all of the machinery needed by a completely general blending configuration.
These optimizations are not game-specific. The fast path is derived from RDP state. Any game producing the same state naturally gets the same optimized path.
Start rendering before FullSync
There is another useful level of parallelism outside the pixel pipeline itself.
RDP processing can run asynchronously with the rest of the emulator.
For accurate emulation, when FullSync is reached, all preceding RDP work has to be complete before the corresponding DP interrupt is signaled.
But that only defines when the work has to be finished. It does not mean rendering has to wait until FullSync before it starts.
SoftRDP can begin processing submitted work while emulation continues. At FullSync it waits for whatever preceding work is still outstanding, then signals completion.
FullSync is where previous work must be finished, not where it has to begin.
This lets RDP rendering overlap with CPU and RSP emulation. By the time FullSync arrives, some or all of the rendering may already be complete.
Memory dependencies can still require earlier synchronization. The point is simply to wait when correctness requires the result.
CPU rendering and paraLLEl-RDP
paraLLEl-RDP is an interesting comparison because it maps essentially the same low-level RDP problem onto Vulkan compute.
A GPU has far more parallel execution width, so it can manage the additional pixel work from higher internal resolutions more easily. This is one of the main strengths of the Vulkan approach.
SoftRDP also supports 2× internal rendering, which means roughly four times the pixel workload. On a modern multicore CPU, 2× can remain practical, but this is where the CPU's smaller amount of parallel execution starts to show more clearly.
paraLLEl-RDP still implements the RDP as a pure compute workload rather than conventional GPU rasterization. SoftRDP's RDP pipeline instead executes as native CPU code. A graphics API is still used to present the completed framebuffer, but rasterization, texture processing, combining, depth testing and blending are not being compiled and scheduled by a GPU driver.
At native resolution, the relatively small N64 workload gives a modern CPU a particularly favorable case. At 2×, CPU rendering can remain practical while the difference in available parallel execution becomes more visible.
Closing
Accurate N64 software rendering does not have to mean a slow single-threaded reference path.
The native workload maps well to modern CPUs when the implementation exposes enough parallelism: distribute scanlines across cores, vectorize suitable pixel arithmetic, derive fast paths directly from RDP state, and synchronize only when the emulated hardware actually requires the result.


Top comments (0)