RSP Task Queue
The rspq package manages submission and execution of tasks on the N64's RSP (Reality Signal Processor). The RSP is a MIPS-based coprocessor that runs microcode for graphics (display list processing) and audio (sample mixing). This package provides task queuing, microcode management, and the submit/wait protocol.
Microcode
The RSP has 4KB of instruction memory (IMEM) and 4KB of data memory (DMEM). Microcode is loaded into these memories before task execution.
MicrocodeType
Identifies which RSP microcode to use:
const (
Fast3D MicrocodeType = iota // original SM64 microcode
F3DEX // extended vertex buffer (32 slots)
F3DEX2 // most common N64 microcode
AspMain // standard audio microcode
)
Microcode struct
Holds the IMEM and DMEM blobs:
type Microcode struct {
Type MicrocodeType
Code []byte // IMEM content (up to 4KB)
Data []byte // DMEM content (up to 4KB)
}
Maximum sizes:
const MaxIMEMSize = 4096
const MaxDMEMSize = 4096
Load
Loads microcode into the RSP:
rspq.Load(ucode)
On N64, this wraps the rsp.Load function from the hardware driver. On other platforms, it is a no-op.
Start
Resumes RSP execution after loading microcode:
rspq.Start()
Task
A Task describes an RSP task to execute, matching the N64's OSTask structure:
type Task struct {
Type TaskType
Flags TaskFlags
// Microcode pointers
UcodeBootAddr uint32
UcodeBootSize uint32
UcodeAddr uint32
UcodeSize uint32
UcodeDataAddr uint32
UcodeDataSize uint32
// Task data (display list or audio command list)
DataAddr uint32
DataSize uint32
// Yield buffer for task preemption
YieldAddr uint32
YieldSize uint32
// Output buffer (for audio tasks)
OutputAddr uint32
OutputSize uint32
// DRAM stack for RSP microcode
DRAMStackAddr uint32
DRAMStackSize uint32
}
TaskType
const (
GfxTask TaskType = 1 // graphics (display list) task
AudTask TaskType = 2 // audio synthesis task
)
SubmitTask
Loads a task onto the RSP and starts execution. This replicates the osSpTaskLoad + osSpTaskStartGo protocol:
func SubmitTask(task *OSTask, bootCode []byte)
The submission sequence:
- Marshal the task struct to a 64-byte buffer
- Writeback the buffer to ensure cache coherency
- Clear RSP signal flags and enable interrupt-on-break
- DMA the task descriptor to DMEM at offset 0xFC0
- DMA the boot microcode to IMEM
- Resume RSP execution
All address fields in the task must be physical RDRAM addresses.
WaitTaskDone
Blocks until the RSP signals completion (break interrupt):
func WaitTaskDone()
The RSP sets the break flag when it finishes executing a task. This function waits for that interrupt.
Queue
The Queue manages task submission and interleaving. It handles the priority relationship between audio and graphics tasks.
q := rspq.NewQueue()
Task priority and preemption
Audio tasks have higher priority than graphics tasks. If an audio task is submitted while a graphics task is running, the graphics task is yielded:
q.Submit(gfxTask) // starts running (RSP was idle)
q.Submit(audTask) // graphics task is yielded, audio runs next
The yielded graphics task resumes after the audio task completes.
Queue status
status := q.Status()
// StatusIdle - RSP is idle
// StatusRunning - a task is executing
// StatusYielded - a graphics task was preempted
// StatusDone - last task completed
Submit
Adds a task to the queue:
q.Submit(task)
If the RSP is idle, the task starts immediately. If a graphics task is running and an audio task is submitted, the current task is yielded and the audio task runs first. Otherwise, the task is queued.
Complete
Marks the current task as done and dispatches the next pending task:
q.Complete()
Completion handler
Register a callback for task completion:
q.SetCompletionHandler(func(task *rspq.Task) {
if task.Type == rspq.GfxTask {
// frame rendering complete, swap buffers
}
})
Current
Returns the currently executing task:
task := q.Current()
if task != nil {
fmt.Println(task.Type)
}
Typical graphics frame
// Build display list
dl := gfx.NewDisplayList(256)
// ... add commands ...
dl.SPEndDisplayList()
// Create and submit task
task := &rspq.Task{
Type: rspq.GfxTask,
UcodeAddr: ucodeAddr,
UcodeSize: ucodeSize,
UcodeDataAddr: ucodeDataAddr,
UcodeDataSize: ucodeDataSize,
DataAddr: displayListAddr,
DataSize: uint32(dl.Len() * 8),
}
rspq.SubmitTask(task, bootCode)
rspq.WaitTaskDone()
Audio/graphics interleaving
The N64 runs audio and graphics on the same RSP. Audio tasks must complete within tight deadlines to avoid audio glitches, so they preempt graphics tasks:
Frame timeline:
[GFX running]──>[yield]──>[AUD runs]──>[AUD done]──>[GFX resumes]──>[GFX done]
The Queue handles this automatically. When you submit an audio task while graphics is running, the queue sets StatusYielded, puts the audio task at the front of the pending list, and dispatches it when the current task checkpoint allows.