Something went wrong. Try again.
A Playstation One (PS1) emulator fully written in Jam
Something went wrong. Try again.
jamstation mdec.jam
19 kB · 537 lines
at main
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538// SPDX-License-Identifier: MIT// Copyright (c) 2026 Raphael Amorim
// MDEC is Motion Decoder.// basically:// 1. CdlReadS streams compressed sectors into RAM.// 2. DMA channel 0 copies input -> MDEC's input FIFO (0x1F801820).// 3. MDEC runs RLE-decode -> inverse-quantisation -> iDCT -> YUV->RGB.// 4. DMA channel 1 reads decoded RGB -> RAM.// 5. GPU display VRAM blit of the decoded frame.
const { Vec } = import("std/collections");
const MDEC_INPUT_SIZE: u32 = 0x40000;const MDEC_OUTPUT_SIZE: u32 = 0x100000;const MDEC_CMD_NOP: u32 = 0;const MDEC_CMD_DECODE: u32 = 1;const MDEC_CMD_SET_QT: u32 = 2;const MDEC_CMD_SET_ST: u32 = 3;const DEPTH_4BIT: u32 = 0;const DEPTH_15BIT: u32 = 3;const ZAGZIG_INIT: [64]u8 = [ 0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20, 13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52, 45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,];
pub const Mdec = struct { cmd: u32, wordsRem: u32, curBlock: u32, outputBit15: u32, outputSigned: u32, outputDepth: u32, inputReq: u32, outputReq: u32, busy: u32, inputFull: u32, outputEmpty: u32, enableDma0: u32, enableDma1: u32, recvColor: u32, inputIndex: u32, inputSizeB: u32, outputIndex: u32, outputWords: u32, yQuant: [64]u8, uvQuant: [64]u8, scale: [64]i16, yBlk: [128]i16, crBlk: [128]i16, cbBlk: [128]i16, zagzig: [64]u8,
pub fn init() Self { return Self { cmd: 0, wordsRem: 0, curBlock: 0, outputBit15: 0, outputSigned: 0, outputDepth: 0, inputReq: 0, outputReq: 0, busy: 0, inputFull: 0, outputEmpty: 0, enableDma0: 0, enableDma1: 0, recvColor: 0, inputIndex: 0, inputSizeB: 0, outputIndex: 0, outputWords: 0, yQuant: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], uvQuant: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], scale: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], yBlk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], crBlk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], cbBlk: [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], zagzig: ZAGZIG_INIT, }; }
pub fn realIdct(self: Self, blk: *mut[] i16) { idctPassPtr(blk, blk, 64, self.scale.asMutPtr()); idctPassPtr(blk, blk, 64, self.scale.asMutPtr()); }
pub fn rlDecodeBlock(self: Self, input: *mut[] u8, inIdx: u32, blk: *mut[] i16, quant: *mut[] u8) u32 { var i: u32 = 0; while (i < 64) { blk[i] = 0; i = i + 1; }
var idx: u32 = inIdx; // Skip 0xFE00 run markers at start of block. var n: u32 = readU16(input, idx * 2); idx = idx + 1; while (n == 0xFE00) { n = readU16(input, idx * 2); idx = idx + 1; }
const qScale: u32 = (n >> 10) & 0x3F; var val: i32 = mdecTrunc16(exts10(n & 0x3FF) * (quant[0] as i32));
var k: u32 = 0; while (k < 64) { if (qScale == 0) { val = mdecTrunc16(exts10(n & 0x3FF) * 2); } val = clampS10(val); if (qScale > 0) { const zk: u32 = self.zagzig[k] as u32; blk[zk] = val as i16; } else { blk[k] = val as i16; } n = readU16(input, idx * 2); if (k == 63) { break; } idx = idx + 1; k = k + ((n >> 10) & 0x3F) + 1; if (k < 64) { const q: i32 = quant[k] as i32; val = mdecTrunc16((exts10(n & 0x3FF) * q * (qScale as i32) + 4) / 8); } }
self.realIdct(blk); return idx; }
pub fn yuvToRgb(self: Self, output: *mut[] u8, outBase: u32, xx: u32, yy: u32) { const depth: u32 = self.outputDepth; const signed_: u32 = self.outputSigned; const bit15: u32 = self.outputBit15;
var y: u32 = 0; while (y < 8) { var x: u32 = 0; while (x < 8) { const cri: u32 = ((x + xx) >> 1) + ((y + yy) >> 1) * 8; const rRaw: i32 = self.crBlk[cri] as i32; const bRaw: i32 = self.cbBlk[cri] as i32;
const rf: f64 = rRaw as f64; const bf: f64 = bRaw as f64; const gFloat: f64 = (0.0 - 0.3437) * bf + (0.0 - 0.7143) * rf; const rFloat: f64 = 1.402 * rf; const bFloat: f64 = 1.772 * bf;
const l: i32 = self.yBlk[x + y * 8] as i32;
var r: i32 = clampSByte(l + (rFloat as i32)); var g: i32 = clampSByte(l + (gFloat as i32)); var b: i32 = clampSByte(l + (bFloat as i32));
if (signed_ == 0) { r = r ^ 0x80; g = g ^ 0x80; b = b ^ 0x80; }
const px: u32 = (x + xx); const py: u32 = (y + yy); if (depth == DEPTH_15BIT) { const r5: u32 = ((r as u32) & 0xFF) >> 3; const g5: u32 = ((g as u32) & 0xFF) >> 3; const b5: u32 = ((b as u32) & 0xFF) >> 3; var rgb: u32 = (b5 << 10) | (g5 << 5) | r5; if (bit15 != 0) { rgb = rgb | 0x8000; } const pxOff: u32 = outBase + (px + py * 16) * 2; output[pxOff] = (rgb & 0xFF) as u8; output[pxOff + 1] = ((rgb >> 8) & 0xFF) as u8; } else { const pxOff: u32 = outBase + (px + py * 16) * 3; output[pxOff] = (r & 0xFF) as u8; output[pxOff + 1] = (g & 0xFF) as u8; output[pxOff + 2] = (b & 0xFF) as u8; } x = x + 1; } y = y + 1; } }
pub fn decodeMacroblock(self: mut Self, input: *mut[] u8, output: *mut[] u8) { const depth: u32 = self.outputDepth; const inputBytes: u32 = self.inputSizeB; if (depth < 2) { self.rlDecodeBlock(input, 0, self.yBlk.asMutPtr(), self.yQuant.asMutPtr()); var i: u32 = 0; while (i < 64) { const yv: i32 = self.yBlk[i] as i32; output[i] = (yv & 0xFF) as u8; i = i + 1; } var words: u32 = 16; if (depth == DEPTH_4BIT) { words = 8; } self.outputWords = words; self.outputEmpty = 0; self.outputIndex = 0; return; } var blockSize: u32 = 768; if (depth == DEPTH_15BIT) { blockSize = 512; }
var idx: u32 = 0; var outBytes: u32 = 0; while (idx * 2 < inputBytes) { const outBase: u32 = outBytes; idx = self.rlDecodeBlock(input, idx, self.crBlk.asMutPtr(), self.uvQuant.asMutPtr()); idx = self.rlDecodeBlock(input, idx, self.cbBlk.asMutPtr(), self.uvQuant.asMutPtr()); idx = self.rlDecodeBlock(input, idx, self.yBlk.asMutPtr(), self.yQuant.asMutPtr()); self.yuvToRgb(output, outBase, 0, 0); idx = self.rlDecodeBlock(input, idx, self.yBlk.asMutPtr(), self.yQuant.asMutPtr()); self.yuvToRgb(output, outBase, 8, 0); idx = self.rlDecodeBlock(input, idx, self.yBlk.asMutPtr(), self.yQuant.asMutPtr()); self.yuvToRgb(output, outBase, 0, 8); idx = self.rlDecodeBlock(input, idx, self.yBlk.asMutPtr(), self.yQuant.asMutPtr()); self.yuvToRgb(output, outBase, 8, 8); outBytes = outBytes + blockSize; if (outBytes + blockSize > MDEC_OUTPUT_SIZE) { break; } }
self.outputWords = outBytes / 4; self.outputEmpty = 0; self.outputIndex = 0; }
pub fn setQt(self: mut Self, input: *mut[] u8) { var i: u32 = 0; while (i < 64) { self.yQuant[i] = input[i]; i = i + 1; } if (self.recvColor != 0) { var j: u32 = 0; while (j < 64) { self.uvQuant[j] = input[64 + j]; j = j + 1; } } }
pub fn setSt(self: mut Self, input: *mut[] u8) { var i: u32 = 0; while (i < 64) { const lo: u32 = input[i * 2] as u32; const hi: u32 = input[i * 2 + 1] as u32; var v: u32 = (hi << 8) | lo; if ((v & 0x8000) != 0) { v = v | 0xFFFF0000; } self.scale[i] = v as i16; i = i + 1; } }
pub fn dispatch(self: mut Self, input: *mut[] u8, output: *mut[] u8) { const cmd: u32 = self.cmd; const op: u32 = (cmd >> 29) & 7; if (op == MDEC_CMD_NOP) { return; } if (op == MDEC_CMD_DECODE) { self.decodeMacroblock(input, output); return; } if (op == MDEC_CMD_SET_QT) { self.setQt(input); return; } if (op == MDEC_CMD_SET_ST) { self.setSt(input); return; } }
pub fn read32(self: mut Self, output: *mut[] u8, off: u32) u32 { if (off == 0) { const wordsRem: u32 = self.outputWords; if (wordsRem > 0) { const idx: u32 = self.outputIndex; const v: u32 = readU32(output, idx * 4); self.outputIndex = idx + 1; self.outputWords = wordsRem - 1; return v; } self.outputEmpty = 0; self.outputIndex = 0; self.outputReq = 0; return 0xAAAAAAAA; } if (off == 4) { // Bits 0-15 = "number of parameter words remaining MINUS 1" // (nocash: FFFFh = none). DuckStation `(remaining/2)-1`, Avocado // `(paramCount-1)&0xffff`, mednafen's 0xFFFF sentinel all agree var st: u32 = (self.wordsRem - 1) & 0xFFFF; st = st | ((self.curBlock & 0x7) << 16); if (self.outputBit15 != 0) { st = st | (1 << 23); } if (self.outputSigned != 0) { st = st | (1 << 24); } st = st | ((self.outputDepth & 3) << 25); if (self.enableDma1 != 0 && self.outputWords > 0) { st = st | (1 << 27); } if (self.enableDma0 != 0 && self.wordsRem > 0) { st = st | (1 << 28); } if (self.busy != 0) { st = st | (1 << 29); } if (self.inputFull != 0) { st = st | (1 << 30); } if (self.outputWords == 0) { st = st | (1 << 31); } return st; } return 0; }
pub fn read16(self: mut Self, output: *mut[] u8, off: u32) u32 { return self.read32(output, off & 0xFFFFFFFC) & 0xFFFF; }
pub fn read8(self: mut Self, output: *mut[] u8, off: u32) u32 { return self.read32(output, off & 0xFFFFFFFC) & 0xFF; }
pub fn write32(self: mut Self, input: *mut[] u8, output: *mut[] u8, off: u32, val: u32) { if (off == 0) { const wordsRem: u32 = self.wordsRem; if (wordsRem > 0) { const idx: u32 = self.inputIndex; writeU32(input, idx * 4, val); self.inputIndex = idx + 1; self.wordsRem = wordsRem - 1; if (wordsRem - 1 == 0) { self.outputEmpty = 0; self.inputFull = 0; self.inputReq = 0; self.busy = 0; self.outputReq = self.enableDma1; self.dispatch(input, output); } return; } // Start a new command. self.cmd = val; self.outputReq = 0; self.outputEmpty = 1; self.outputWords = 0; self.outputBit15 = (val >> 25) & 1; self.outputSigned = (val >> 26) & 1; self.outputDepth = (val >> 27) & 3; self.inputIndex = 0; self.inputFull = 0; self.busy = 1; const op: u32 = (val >> 29) & 7; var newWords: u32 = 0; if (op == MDEC_CMD_NOP) { self.busy = 0; } else if (op == MDEC_CMD_DECODE) { newWords = val & 0xFFFF; } else if (op == MDEC_CMD_SET_QT) { self.recvColor = val & 1; if ((val & 1) != 0) { newWords = 32; } else { newWords = 16; } } else if (op == MDEC_CMD_SET_ST) { newWords = 32; } self.wordsRem = newWords; if (newWords > 0) { self.inputReq = 1; self.inputSizeB = newWords * 4; self.inputFull = 0; self.inputIndex = 0; } return; } if (off == 4) { // Control register. if ((val & 0x40000000) != 0) { self.enableDma0 = 1; } else { self.enableDma0 = 0; }
if ((val & 0x20000000) != 0) { self.enableDma1 = 1; } else { self.enableDma1 = 0; } if ((val & 0x80000000) != 0) { // Reset - status = 0x80040000. self.busy = 0; self.wordsRem = 0; self.outputBit15 = 0; self.outputSigned = 0; self.outputDepth = 0; self.inputReq = 0; self.outputReq = 0; self.inputFull = 0; self.outputEmpty = 1; self.outputWords = 0; self.outputIndex = 0; self.curBlock = 4; } return; } }
pub fn write16(self: mut Self, input: *mut[] u8, output: *mut[] u8, off: u32, val: u32) { self.write32(input, output, off & 0xFFFFFFFC, val & 0xFFFF); }
pub fn write8(self: mut Self, input: *mut[] u8, output: *mut[] u8, off: u32, val: u32) { self.write32(input, output, off & 0xFFFFFFFC, val & 0xFF); }};
pub fn createMdecInput() Vec(u8) { return Vec(u8).filled(0, MDEC_INPUT_SIZE);}
pub fn createMdecOutput() Vec(u8) { return Vec(u8).filled(0, MDEC_OUTPUT_SIZE);}
pub fn exts10(v: u32) i32 { const t: u32 = (v << 22) & 0xFFFFFFFF; return (t as i32) >> 22;}
pub fn clampS10(v: i32) i32 { if (v < -0x400) { return -0x400; } if (v > 0x3FF) { return 0x3FF; } return v;}
pub fn mdecTrunc16(v: i32) i32 { return ((v & 0xFFFF) ^ 0x8000) - 0x8000;}
pub fn clampSByte(v: i32) i32 { if (v < -128) { return -128; } if (v > 127) { return 127; } return v;}
pub fn readU16(buf: *mut[] u8, off: u32) u32 { return (buf[off] as u32) | ((buf[off + 1] as u32) << 8);}
pub fn readU32(buf: *mut[] u8, off: u32) u32 { return (buf[off] as u32) | ((buf[off + 1] as u32) << 8) | ((buf[off + 2] as u32) << 16) | ((buf[off + 3] as u32) << 24);}
pub fn writeU32(buf: *mut[] u8, off: u32, v: u32) { buf[off] = (v & 0xFF) as u8; buf[off + 1] = ((v >> 8) & 0xFF) as u8; buf[off + 2] = ((v >> 16) & 0xFF) as u8; buf[off + 3] = ((v >> 24) & 0xFF) as u8;}
pub fn idctPassPtr(blk: *mut[] i16, _dst: *mut[] i16, _scratchOff: u32, scale: *mut[] i16) { var scratch: [64]i16 = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]; var x: u32 = 0; while (x < 8) { var y: u32 = 0; while (y < 8) { var sum: i32 = 0; var z: u32 = 0; while (z < 8) { const a: i32 = blk[y + z * 8] as i32; const b: i32 = scale[x + z * 8] as i32; sum = sum + a * (b / 8); z = z + 1; } scratch[x + y * 8] = ((sum + 0xFFF) / 0x2000) as i16; y = y + 1; } x = x + 1; } var i: u32 = 0; while (i < 64) { blk[i] = scratch[i]; i = i + 1; }}