├── .gitattributes ├── src ├── common │ ├── file.hpp │ ├── file.cpp │ └── types.hpp ├── core │ ├── cpu │ │ ├── cpu.hpp │ │ ├── gte.hpp │ │ ├── cop0.hpp │ │ ├── cop0.cpp │ │ └── gte.cpp │ ├── gpu │ │ ├── gpu.hpp │ │ └── gpu.cpp │ ├── Mari.hpp │ ├── mdec │ │ ├── mdec.hpp │ │ └── mdec.cpp │ ├── cdrom │ │ ├── cdrom.hpp │ │ └── cdrom.cpp │ ├── spu │ │ ├── spu.hpp │ │ ├── gauss.hpp │ │ └── spu.cpp │ ├── timer │ │ ├── timer.hpp │ │ └── timer.cpp │ ├── sio │ │ ├── sio.hpp │ │ └── sio.cpp │ ├── scheduler.hpp │ ├── bus │ │ ├── bus.hpp │ │ └── bus.cpp │ ├── dmac │ │ ├── dmac.hpp │ │ └── dmac.cpp │ ├── intc.hpp │ ├── intc.cpp │ ├── scheduler.cpp │ └── Mari.cpp └── main.cpp ├── .gitignore ├── README.md ├── LICENSE └── CMakeLists.txt /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /src/common/file.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "types.hpp" 11 | 12 | /* Reads a binary file into a std::vector */ 13 | std::vector loadBinary(const char *path); 14 | -------------------------------------------------------------------------------- /src/core/cpu/cpu.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::cpu { 11 | 12 | void init(); 13 | void step(i64 c); 14 | 15 | void doInterrupt(); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/core/gpu/gpu.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "../../common/types.hpp" 7 | 8 | namespace ps::gpu { 9 | 10 | void init(); 11 | 12 | void writeGP0(u32 data); 13 | void writeGP1(u32 data); 14 | 15 | u32 readGPUREAD(); 16 | u32 readStatus(); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/core/Mari.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../common/types.hpp" 9 | 10 | namespace ps { 11 | 12 | void init(const char *biosPath, const char *isoPath, const char *exePath); 13 | void run(); 14 | 15 | void update(const u8 *fb); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/core/mdec/mdec.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::mdec { 11 | 12 | void init(); 13 | 14 | u32 readData(); 15 | u32 readStat(); 16 | 17 | void writeCmd(u32 data); 18 | void writeCtrl(u32 data); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/core/cdrom/cdrom.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::cdrom { 11 | 12 | void init(const char *isoPath); 13 | 14 | u8 read(u32 addr); 15 | 16 | void write(u32 addr, u8 data); 17 | 18 | u32 getData32(); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/core/spu/spu.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::spu { 11 | 12 | void init(); 13 | void save(); 14 | 15 | void writeRAM(u16 data); 16 | 17 | u16 read(u32 addr); 18 | 19 | void write(u32 addr, u16 data); 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/core/cpu/gte.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::cpu::gte { 11 | 12 | u32 get(u32 idx); 13 | u32 getControl(u32 idx); 14 | 15 | void set(u32 idx, u32 data); 16 | void setControl(u32 idx, u32 data); 17 | 18 | void doCmd(u32 cmd); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/common/file.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "file.hpp" 7 | 8 | #include 9 | #include 10 | 11 | std::vector loadBinary(const char *path) { 12 | std::ifstream file{path, std::ios::binary}; 13 | 14 | file.unsetf(std::ios::skipws); 15 | 16 | return {std::istream_iterator{file}, {}}; 17 | } 18 | -------------------------------------------------------------------------------- /src/common/types.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | using u8 = std::uint8_t; 11 | using u16 = std::uint16_t; 12 | using u32 = std::uint32_t; 13 | using u64 = std::uint64_t; 14 | 15 | using i8 = std::int8_t; 16 | using i16 = std::int16_t; 17 | using i32 = std::int32_t; 18 | using i64 = std::int64_t; 19 | -------------------------------------------------------------------------------- /src/core/timer/timer.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::timer { 11 | 12 | void init(); 13 | 14 | u16 read(u32 addr); 15 | 16 | void write(u32 addr, u16 data); 17 | 18 | void step(i64 c); 19 | void stepHBLANK(); 20 | 21 | void gateVBLANKStart(); 22 | void gateVBLANKEnd(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | # Dev 35 | .vscode 36 | build 37 | MariFiles 38 | -------------------------------------------------------------------------------- /src/core/sio/sio.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::sio { 11 | 12 | void init(); 13 | 14 | u8 read8(u32 addr); 15 | u16 read16(u32 addr); 16 | u32 read32(u32 addr); 17 | 18 | void write8(u32 addr, u8 data); 19 | void write16(u32 addr, u16 data); 20 | void write32(u32 addr, u32 data); 21 | 22 | void setInput(u16 input); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mari 2 | PlayStation emulator written in C++. Just a fun little side project, nothing serious. 3 | 4 | # Screenshots 5 | Mari1 6 | Mari2 7 | Mari3 8 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include 7 | 8 | #include "core/Mari.hpp" 9 | 10 | int main(int argc, char **argv) { 11 | std::printf("[Mari ] PlayStation emulator\n"); 12 | 13 | if (argc < 3) { 14 | std::printf("Usage: Mari /path/to/bios /path/to/iso\n"); 15 | 16 | return -1; 17 | } 18 | 19 | ps::init(argv[1], argv[2], (argc == 4) ? argv[3] : NULL); 20 | ps::run(); 21 | 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /src/core/scheduler.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include 9 | 10 | #include "../common/types.hpp" 11 | 12 | namespace ps::scheduler { 13 | 14 | void init(); 15 | 16 | void flush(); 17 | 18 | u64 registerEvent(std::function func); 19 | 20 | void addEvent(u64 id, int param, i64 cyclesUntilEvent); 21 | void removeEvent(u64 id); 22 | void processEvents(i64 elapsedCycles); 23 | 24 | i64 getRunCycles(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/core/bus/bus.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::bus { 11 | 12 | void init(const char *biosPath, const char *exePath); 13 | 14 | u8 read8(u32 addr); 15 | u16 read16(u32 addr); 16 | u32 read32(u32 addr); 17 | 18 | void write8(u32 addr, u8 data); 19 | void write16(u32 addr, u16 data); 20 | void write32(u32 addr, u32 data); 21 | 22 | u32 loadEXE(); 23 | 24 | bool isEXEEnabled(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/core/dmac/dmac.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::dmac { 11 | 12 | /* DMA channels */ 13 | enum class Channel { 14 | MDECIN, 15 | MDECOUT, 16 | GPU, 17 | CDROM, 18 | SPU, 19 | PIO, 20 | OTC, 21 | }; 22 | 23 | void init(); 24 | 25 | u32 read(u32 addr); 26 | 27 | void write8(u32 addr, u8 data); 28 | void write32(u32 addr, u32 data); 29 | 30 | void setDRQ(Channel chn, bool drq); 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/core/intc.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../common/types.hpp" 9 | 10 | namespace ps::intc { 11 | 12 | /* Interrupt sources */ 13 | enum class Interrupt { 14 | VBLANK, 15 | GPU, 16 | CDROM, 17 | DMA, 18 | Timer0, Timer1, Timer2, 19 | SIORecieve, 20 | SIO, 21 | SPU, 22 | PIO, 23 | }; 24 | 25 | u16 readMask(); 26 | u16 readStat(); 27 | 28 | void writeMask(u16 data); 29 | void writeStat(u16 data); 30 | 31 | void sendInterrupt(Interrupt i); 32 | 33 | } 34 | -------------------------------------------------------------------------------- /src/core/cpu/cop0.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::cpu::cop0 { 11 | 12 | /* Exception codes */ 13 | enum Exception { 14 | Interrupt = 0x0, 15 | LoadError = 0x4, 16 | StoreError = 0x5, 17 | SystemCall = 0x8, 18 | Breakpoint = 0x9, 19 | Instruction = 0xA, 20 | Overflow = 0xC, 21 | }; 22 | 23 | /* Exception names */ 24 | static const char *eNames[32] = { 25 | "INT", "MOD", "TLBL", "TLBS", "AdEL", "AdES", "IBE", "DBE", "Syscall", "BP", "RI", "CpU", "Ov", 26 | }; 27 | 28 | void init(); 29 | 30 | u32 get(u32 idx); 31 | 32 | void set(u32 idx, u32 data); 33 | 34 | void enterException(Exception e); 35 | void leaveException(); 36 | 37 | void setInterruptPending(bool irq); 38 | 39 | bool isBEV(); 40 | bool isCacheIsolated(); 41 | 42 | void setBD(bool bd); 43 | void setEPC(u32 pc); 44 | void setBadVAddr(u32 addr); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Michelle-Marie Schiller 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.2) 2 | project(Mari CXX) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | 7 | add_compile_options(-O1 -Wall -Wextra) 8 | 9 | set(SOURCES 10 | src/main.cpp 11 | src/common/file.cpp 12 | src/core/intc.cpp 13 | src/core/Mari.cpp 14 | src/core/scheduler.cpp 15 | src/core/bus/bus.cpp 16 | src/core/cdrom/cdrom.cpp 17 | src/core/cpu/cop0.cpp 18 | src/core/cpu/cpu.cpp 19 | src/core/cpu/gte.cpp 20 | src/core/dmac/dmac.cpp 21 | src/core/gpu/gpu.cpp 22 | src/core/mdec/mdec.cpp 23 | src/core/sio/sio.cpp 24 | src/core/spu/spu.cpp 25 | src/core/timer/timer.cpp 26 | ) 27 | 28 | set(HEADERS 29 | src/common/file.hpp 30 | src/common/types.hpp 31 | src/core/intc.hpp 32 | src/core/Mari.hpp 33 | src/core/scheduler.hpp 34 | src/core/bus/bus.hpp 35 | src/core/cdrom/cdrom.hpp 36 | src/core/cpu/cop0.hpp 37 | src/core/cpu/cpu.hpp 38 | src/core/cpu/gte.hpp 39 | src/core/dmac/dmac.hpp 40 | src/core/gpu/gpu.hpp 41 | src/core/mdec/mdec.hpp 42 | src/core/sio/sio.hpp 43 | src/core/spu/gauss.hpp 44 | src/core/spu/spu.hpp 45 | src/core/timer/timer.hpp 46 | ) 47 | 48 | find_package(SDL2 REQUIRED) 49 | include_directories(Mari ${SDL2_INCLUDE_DIRS}) 50 | 51 | add_executable(Mari ${SOURCES} ${HEADERS}) 52 | target_link_libraries(Mari ${SDL2_LIBRARIES}) 53 | -------------------------------------------------------------------------------- /src/core/intc.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "intc.hpp" 7 | 8 | #include 9 | #include 10 | 11 | #include "cpu/cop0.hpp" 12 | 13 | namespace ps::intc { 14 | 15 | /* Interrupt sources */ 16 | const char *intNames[] = { 17 | "VBLANK", 18 | "GPU", 19 | "CDROM", 20 | "DMA", 21 | "Timer 0", "Timer 1", "Timer 2", 22 | "SIO Receive", 23 | "SIO", 24 | "SPU", 25 | "PIO", 26 | }; 27 | 28 | /* --- INTC registers --- */ 29 | 30 | u16 iMASK = 0, iSTAT = 0; 31 | 32 | void checkInterrupt(); 33 | 34 | /* Returns I_MASK */ 35 | u16 readMask() { 36 | return iMASK; 37 | } 38 | 39 | /* Returns I_STAT */ 40 | u16 readStat() { 41 | return iSTAT; 42 | } 43 | 44 | /* Writes I_MASK */ 45 | void writeMask(u16 data) { 46 | iMASK = (data & 0x7FF); 47 | 48 | assert(!(iMASK & 0x702)); 49 | 50 | checkInterrupt(); 51 | } 52 | 53 | /* Writes I_STAT */ 54 | void writeStat(u16 data) { 55 | iSTAT &= (data & 0x7FF); 56 | 57 | checkInterrupt(); 58 | } 59 | 60 | void sendInterrupt(Interrupt i) { 61 | //std::printf("[INTC ] %s interrupt request\n", intNames[static_cast(i)]); 62 | 63 | iSTAT |= 1 << static_cast(i); 64 | 65 | checkInterrupt(); 66 | } 67 | 68 | void checkInterrupt() { 69 | //std::printf("[INTC ] I_STAT = 0x%04X, I_MASK = 0x%04X\n", iSTAT, iMASK); 70 | 71 | cpu::cop0::setInterruptPending(iSTAT & iMASK); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /src/core/scheduler.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "scheduler.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | namespace ps::scheduler { 16 | 17 | /* --- Scheduler constants --- */ 18 | 19 | constexpr i64 MAX_RUN_CYCLES = 64; 20 | 21 | /* Scheduler event */ 22 | struct Event { 23 | u64 id; 24 | 25 | int param; 26 | i64 cyclesUntilEvent; 27 | }; 28 | 29 | std::deque events; // Event queue 30 | std::queue nextEvents; 31 | 32 | std::vector> registeredFuncs; 33 | 34 | i64 cycleCount, cyclesUntilNextEvent; 35 | 36 | /* Finds the next event */ 37 | void reschedule() { 38 | auto nextEvent = INT64_MAX; 39 | 40 | for (auto &event : events) { 41 | if (event.cyclesUntilEvent < nextEvent) nextEvent = event.cyclesUntilEvent; 42 | } 43 | 44 | cyclesUntilNextEvent = nextEvent; 45 | } 46 | 47 | void init() { 48 | cycleCount = 0; 49 | 50 | cyclesUntilNextEvent = INT64_MAX; 51 | } 52 | 53 | void flush() { 54 | if (nextEvents.empty()) return reschedule(); 55 | 56 | while (!nextEvents.empty()) { events.push_back(nextEvents.front()); nextEvents.pop(); } 57 | 58 | reschedule(); 59 | } 60 | 61 | /* Registers an event, returns event ID */ 62 | u64 registerEvent(std::function func) { 63 | static u64 idPool; 64 | 65 | registeredFuncs.push_back(func); 66 | 67 | return idPool++; 68 | } 69 | 70 | /* Adds a scheduler event */ 71 | void addEvent(u64 id, int param, i64 cyclesUntilEvent) { 72 | assert(cyclesUntilEvent >= 0); 73 | 74 | //std::printf("[Scheduler ] Adding event %llu, cycles until event: %lld\n", id, cyclesUntilEvent); 75 | 76 | nextEvents.emplace(Event{id, param, cyclesUntilEvent}); 77 | } 78 | 79 | /* Removes all scheduler events of a certain ID */ 80 | void removeEvent(u64 id) { 81 | for (auto event = events.begin(); event != events.end();) { 82 | if (event->id == id) { 83 | event = events.erase(event); 84 | } else { 85 | event++; 86 | } 87 | } 88 | } 89 | 90 | void processEvents(i64 elapsedCycles) { 91 | assert(!events.empty()); 92 | 93 | cyclesUntilNextEvent -= elapsedCycles; 94 | 95 | for (auto event = events.begin(); event != events.end();) { 96 | event->cyclesUntilEvent -= elapsedCycles; 97 | 98 | assert(event->cyclesUntilEvent >= 0); 99 | 100 | if (!event->cyclesUntilEvent) { 101 | const auto id = event->id; 102 | const auto param = event->param; 103 | //const auto cyclesUntilEvent = event->cyclesUntilEvent; 104 | 105 | event = events.erase(event); 106 | 107 | registeredFuncs[id](param, 0); 108 | } else { 109 | event++; 110 | } 111 | } 112 | } 113 | 114 | i64 getRunCycles() { 115 | return std::min((i64)MAX_RUN_CYCLES, cyclesUntilNextEvent); 116 | } 117 | 118 | } 119 | 120 | -------------------------------------------------------------------------------- /src/core/Mari.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "Mari.hpp" 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | 13 | #include "scheduler.hpp" 14 | #include "bus/bus.hpp" 15 | #include "cdrom/cdrom.hpp" 16 | #include "cpu/cpu.hpp" 17 | #include "dmac/dmac.hpp" 18 | #include "gpu/gpu.hpp" 19 | #include "sio/sio.hpp" 20 | #include "spu/spu.hpp" 21 | #include "timer/timer.hpp" 22 | 23 | #include 24 | 25 | #undef main 26 | 27 | namespace ps { 28 | 29 | /* SDL2 */ 30 | SDL_Renderer *renderer; 31 | SDL_Window *window; 32 | SDL_Texture *texture; 33 | 34 | SDL_Event e; 35 | 36 | bool isRunning = true; 37 | 38 | /* Initializes SDL */ 39 | void initSDL() { 40 | SDL_Init(SDL_INIT_VIDEO); 41 | SDL_SetHint(SDL_HINT_RENDER_VSYNC, "1"); 42 | 43 | SDL_CreateWindowAndRenderer(1024, 512, 0, &window, &renderer); 44 | SDL_SetWindowSize(window, 1024, 512); 45 | SDL_RenderSetLogicalSize(renderer, 1024, 512); 46 | SDL_SetWindowResizable(window, SDL_FALSE); 47 | SDL_SetWindowTitle(window, "Mari"); 48 | 49 | texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_XBGR1555, SDL_TEXTUREACCESS_STREAMING, 1024, 512); 50 | } 51 | 52 | void init(const char *biosPath, const char *isoPath, const char *exePath) { 53 | std::printf("BIOS path: \"%s\"\nISO path: \"%s\"\n", biosPath, isoPath); 54 | 55 | scheduler::init(); 56 | 57 | bus::init(biosPath, exePath); 58 | cdrom::init(isoPath); 59 | cpu::init(); 60 | dmac::init(); 61 | gpu::init(); 62 | sio::init(); 63 | spu::init(); 64 | timer::init(); 65 | 66 | scheduler::flush(); 67 | 68 | initSDL(); 69 | } 70 | 71 | void run() { 72 | while (isRunning) { 73 | const auto runCycles = scheduler::getRunCycles(); 74 | 75 | scheduler::processEvents(runCycles); 76 | 77 | cpu::step(runCycles / 2); // 2 cycles per instruction 78 | 79 | timer::step(runCycles); 80 | 81 | scheduler::flush(); 82 | } 83 | 84 | SDL_Quit(); 85 | } 86 | 87 | void update(const u8 *fb) { 88 | const u8 *keyState = SDL_GetKeyboardState(NULL); 89 | 90 | u16 input = 0; 91 | 92 | SDL_PollEvent(&e); 93 | 94 | switch (e.type) { 95 | case SDL_QUIT : isRunning = false; break; 96 | case SDL_KEYDOWN: 97 | if (keyState[SDL_GetScancodeFromKey(SDLK_c)]) input |= 1 << 0; // SELECT 98 | if (keyState[SDL_GetScancodeFromKey(SDLK_v)]) input |= 1 << 3; // START 99 | if (keyState[SDL_GetScancodeFromKey(SDLK_w)]) input |= 1 << 4; // UP 100 | if (keyState[SDL_GetScancodeFromKey(SDLK_d)]) input |= 1 << 5; // RIGHT 101 | if (keyState[SDL_GetScancodeFromKey(SDLK_s)]) input |= 1 << 6; // DOWN 102 | if (keyState[SDL_GetScancodeFromKey(SDLK_a)]) input |= 1 << 7; // LEFT 103 | if (keyState[SDL_GetScancodeFromKey(SDLK_1)]) input |= 1 << 8; // L2 104 | if (keyState[SDL_GetScancodeFromKey(SDLK_3)]) input |= 1 << 9; // R2 105 | if (keyState[SDL_GetScancodeFromKey(SDLK_q)]) input |= 1 << 10; // L2 106 | if (keyState[SDL_GetScancodeFromKey(SDLK_e)]) input |= 1 << 11; // R2 107 | if (keyState[SDL_GetScancodeFromKey(SDLK_t)]) input |= 1 << 12; // TRIANGLE 108 | if (keyState[SDL_GetScancodeFromKey(SDLK_h)]) input |= 1 << 13; // CIRCLE 109 | if (keyState[SDL_GetScancodeFromKey(SDLK_g)]) input |= 1 << 14; // CROSS 110 | if (keyState[SDL_GetScancodeFromKey(SDLK_f)]) input |= 1 << 15; // SQUARE 111 | break; 112 | default: break; 113 | } 114 | 115 | sio::setInput(~input); 116 | 117 | SDL_UpdateTexture(texture, nullptr, fb, 2 * 1024); 118 | SDL_RenderCopy(renderer, texture, nullptr, nullptr); 119 | SDL_RenderPresent(renderer); 120 | } 121 | 122 | } 123 | -------------------------------------------------------------------------------- /src/core/spu/gauss.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #pragma once 7 | 8 | #include "../../common/types.hpp" 9 | 10 | namespace ps::spu::gauss { 11 | 12 | static const i32 gaussTable[] = { 13 | -0x0001, -0x0001, -0x0001, -0x0001, -0x0001, -0x0001, -0x0001, -0x0001, 14 | -0x0001, -0x0001, -0x0001, -0x0001, -0x0001, -0x0001, -0x0001, -0x0001, 15 | 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0x0001, 16 | 0x0001, 0x0001, 0x0001, 0x0002, 0x0002, 0x0002, 0x0003, 0x0003, 17 | 0x0003, 0x0004, 0x0004, 0x0005, 0x0005, 0x0006, 0x0007, 0x0007, 18 | 0x0008, 0x0009, 0x0009, 0x000A, 0x000B, 0x000C, 0x000D, 0x000E, 19 | 0x000F, 0x0010, 0x0011, 0x0012, 0x0013, 0x0015, 0x0016, 0x0018, 20 | 0x0019, 0x001B, 0x001C, 0x001E, 0x0020, 0x0021, 0x0023, 0x0025, 21 | 0x0027, 0x0029, 0x002C, 0x002E, 0x0030, 0x0033, 0x0035, 0x0038, 22 | 0x003A, 0x003D, 0x0040, 0x0043, 0x0046, 0x0049, 0x004D, 0x0050, 23 | 0x0054, 0x0057, 0x005B, 0x005F, 0x0063, 0x0067, 0x006B, 0x006F, 24 | 0x0074, 0x0078, 0x007D, 0x0082, 0x0087, 0x008C, 0x0091, 0x0096, 25 | 0x009C, 0x00A1, 0x00A7, 0x00AD, 0x00B3, 0x00BA, 0x00C0, 0x00C7, 26 | 0x00CD, 0x00D4, 0x00DB, 0x00E3, 0x00EA, 0x00F2, 0x00FA, 0x0101, 27 | 0x010A, 0x0112, 0x011B, 0x0123, 0x012C, 0x0135, 0x013F, 0x0148, 28 | 0x0152, 0x015C, 0x0166, 0x0171, 0x017B, 0x0186, 0x0191, 0x019C, 29 | 0x01A8, 0x01B4, 0x01C0, 0x01CC, 0x01D9, 0x01E5, 0x01F2, 0x0200, 30 | 0x020D, 0x021B, 0x0229, 0x0237, 0x0246, 0x0255, 0x0264, 0x0273, 31 | 0x0283, 0x0293, 0x02A3, 0x02B4, 0x02C4, 0x02D6, 0x02E7, 0x02F9, 32 | 0x030B, 0x031D, 0x0330, 0x0343, 0x0356, 0x036A, 0x037E, 0x0392, 33 | 0x03A7, 0x03BC, 0x03D1, 0x03E7, 0x03FC, 0x0413, 0x042A, 0x0441, 34 | 0x0458, 0x0470, 0x0488, 0x04A0, 0x04B9, 0x04D2, 0x04EC, 0x0506, 35 | 0x0520, 0x053B, 0x0556, 0x0572, 0x058E, 0x05AA, 0x05C7, 0x05E4, 36 | 0x0601, 0x061F, 0x063E, 0x065C, 0x067C, 0x069B, 0x06BB, 0x06DC, 37 | 0x06FD, 0x071E, 0x0740, 0x0762, 0x0784, 0x07A7, 0x07CB, 0x07EF, 38 | 0x0813, 0x0838, 0x085D, 0x0883, 0x08A9, 0x08D0, 0x08F7, 0x091E, 39 | 0x0946, 0x096F, 0x0998, 0x09C1, 0x09EB, 0x0A16, 0x0A40, 0x0A6C, 40 | 0x0A98, 0x0AC4, 0x0AF1, 0x0B1E, 0x0B4C, 0x0B7A, 0x0BA9, 0x0BD8, 41 | 0x0C07, 0x0C38, 0x0C68, 0x0C99, 0x0CCB, 0x0CFD, 0x0D30, 0x0D63, 42 | 0x0D97, 0x0DCB, 0x0E00, 0x0E35, 0x0E6B, 0x0EA1, 0x0ED7, 0x0F0F, 43 | 0x0F46, 0x0F7F, 0x0FB7, 0x0FF1, 0x102A, 0x1065, 0x109F, 0x10DB, 44 | 0x1116, 0x1153, 0x118F, 0x11CD, 0x120B, 0x1249, 0x1288, 0x12C7, 45 | 0x1307, 0x1347, 0x1388, 0x13C9, 0x140B, 0x144D, 0x1490, 0x14D4, 46 | 0x1517, 0x155C, 0x15A0, 0x15E6, 0x162C, 0x1672, 0x16B9, 0x1700, 47 | 0x1747, 0x1790, 0x17D8, 0x1821, 0x186B, 0x18B5, 0x1900, 0x194B, 48 | 0x1996, 0x19E2, 0x1A2E, 0x1A7B, 0x1AC8, 0x1B16, 0x1B64, 0x1BB3, 49 | 0x1C02, 0x1C51, 0x1CA1, 0x1CF1, 0x1D42, 0x1D93, 0x1DE5, 0x1E37, 50 | 0x1E89, 0x1EDC, 0x1F2F, 0x1F82, 0x1FD6, 0x202A, 0x207F, 0x20D4, 51 | 0x2129, 0x217F, 0x21D5, 0x222C, 0x2282, 0x22DA, 0x2331, 0x2389, 52 | 0x23E1, 0x2439, 0x2492, 0x24EB, 0x2545, 0x259E, 0x25F8, 0x2653, 53 | 0x26AD, 0x2708, 0x2763, 0x27BE, 0x281A, 0x2876, 0x28D2, 0x292E, 54 | 0x298B, 0x29E7, 0x2A44, 0x2AA1, 0x2AFF, 0x2B5C, 0x2BBA, 0x2C18, 55 | 0x2C76, 0x2CD4, 0x2D33, 0x2D91, 0x2DF0, 0x2E4F, 0x2EAE, 0x2F0D, 56 | 0x2F6C, 0x2FCC, 0x302B, 0x308B, 0x30EA, 0x314A, 0x31AA, 0x3209, 57 | 0x3269, 0x32C9, 0x3329, 0x3389, 0x33E9, 0x3449, 0x34A9, 0x3509, 58 | 0x3569, 0x35C9, 0x3629, 0x3689, 0x36E8, 0x3748, 0x37A8, 0x3807, 59 | 0x3867, 0x38C6, 0x3926, 0x3985, 0x39E4, 0x3A43, 0x3AA2, 0x3B00, 60 | 0x3B5F, 0x3BBD, 0x3C1B, 0x3C79, 0x3CD7, 0x3D35, 0x3D92, 0x3DEF, 61 | 0x3E4C, 0x3EA9, 0x3F05, 0x3F62, 0x3FBD, 0x4019, 0x4074, 0x40D0, 62 | 0x412A, 0x4185, 0x41DF, 0x4239, 0x4292, 0x42EB, 0x4344, 0x439C, 63 | 0x43F4, 0x444C, 0x44A3, 0x44FA, 0x4550, 0x45A6, 0x45FC, 0x4651, 64 | 0x46A6, 0x46FA, 0x474E, 0x47A1, 0x47F4, 0x4846, 0x4898, 0x48E9, 65 | 0x493A, 0x498A, 0x49D9, 0x4A29, 0x4A77, 0x4AC5, 0x4B13, 0x4B5F, 66 | 0x4BAC, 0x4BF7, 0x4C42, 0x4C8D, 0x4CD7, 0x4D20, 0x4D68, 0x4DB0, 67 | 0x4DF7, 0x4E3E, 0x4E84, 0x4EC9, 0x4F0E, 0x4F52, 0x4F95, 0x4FD7, 68 | 0x5019, 0x505A, 0x509A, 0x50DA, 0x5118, 0x5156, 0x5194, 0x51D0, 69 | 0x520C, 0x5247, 0x5281, 0x52BA, 0x52F3, 0x532A, 0x5361, 0x5397, 70 | 0x53CC, 0x5401, 0x5434, 0x5467, 0x5499, 0x54CA, 0x54FA, 0x5529, 71 | 0x5558, 0x5585, 0x55B2, 0x55DE, 0x5609, 0x5632, 0x565B, 0x5684, 72 | 0x56AB, 0x56D1, 0x56F6, 0x571B, 0x573E, 0x5761, 0x5782, 0x57A3, 73 | 0x57C3, 0x57E2, 0x57FF, 0x581C, 0x5838, 0x5853, 0x586D, 0x5886, 74 | 0x589E, 0x58B5, 0x58CB, 0x58E0, 0x58F4, 0x5907, 0x5919, 0x592A, 75 | 0x593A, 0x5949, 0x5958, 0x5965, 0x5971, 0x597C, 0x5986, 0x598F, 76 | 0x5997, 0x599E, 0x59A4, 0x59A9, 0x59AD, 0x59B0, 0x59B2, 0x59B3, 77 | }; 78 | 79 | i16 interpolate(u8 idx, i16 s0, i16 s1, i16 s2, i16 s3) { 80 | i32 out; 81 | 82 | out = gaussTable[0x0FF - idx] * s0; 83 | out += gaussTable[0x1FF - idx] * s1; 84 | out += gaussTable[0x100 + idx] * s2; 85 | out += gaussTable[0x000 + idx] * s3; 86 | 87 | return out >> 16; 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /src/core/mdec/mdec.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "mdec.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../dmac/dmac.hpp" 13 | 14 | namespace ps::mdec { 15 | 16 | using Channel = dmac::Channel; 17 | 18 | /* --- MDEC constants --- */ 19 | 20 | constexpr int LUM_TABLE = 0; 21 | constexpr int COL_TABLE = 64; 22 | 23 | enum Command { 24 | NOP, 25 | DecodeMacroblock, 26 | SetQuantTables, 27 | SetScaleTable, 28 | }; 29 | 30 | enum MDECState { 31 | Idle, 32 | ReceiveMacroblock, 33 | ReceiveQuantTables, 34 | ReceiveScaleTable, 35 | }; 36 | 37 | /* --- MDEC registers --- */ 38 | 39 | struct MDECStatus { 40 | u16 rem; // Words remaining 41 | u8 blk; // Current block 42 | bool b15; // Bit 15 set/clear (15-bit depth only) 43 | bool sign; // Signed 44 | u8 dep; // Output depth 45 | bool oreq; // Output request 46 | bool ireq; // Input request 47 | bool busy; 48 | bool empty; // Out FIFO empty 49 | bool full; // In FIFO full/last word received 50 | }; 51 | 52 | MDECStatus stat; 53 | 54 | /* Quant tables (0-63 = lum, 64-127 = col) */ 55 | u8 quantTable[128]; 56 | int quantIdx = 0; 57 | 58 | /* Scale table */ 59 | i16 scaleTable[64]; 60 | int scaleIdx = 0; 61 | 62 | int cmdLen; 63 | 64 | MDECState state = MDECState::Idle; 65 | 66 | u32 readData() { 67 | //std::printf("[MDEC ] 32-bit read @ MDEC1\n"); 68 | 69 | return 0; 70 | } 71 | 72 | u32 readStat() { 73 | //std::printf("[MDEC ] 32-bit read @ MDEC0\n"); 74 | 75 | u32 data; 76 | 77 | data = stat.rem; 78 | data |= stat.blk << 16; 79 | data |= stat.b15 << 23; 80 | data |= stat.sign << 24; 81 | data |= stat.dep << 25; 82 | data |= stat.oreq << 27; 83 | data |= stat.ireq << 28; 84 | data |= stat.busy << 29; 85 | data |= stat.full << 30; 86 | data |= stat.empty << 31; 87 | 88 | return data; 89 | } 90 | 91 | void writeCmd(u32 data) { 92 | std::printf("[MDEC ] 32-bit write @ MDEC0 = 0x%08X\n", data); 93 | 94 | switch (state) { 95 | case MDECState::Idle: 96 | { 97 | const auto cmd = data >> 29; 98 | 99 | /* Always copied */ 100 | stat.b15 = data & (1 << 25); 101 | stat.sign = data & (1 << 26); 102 | stat.dep = (data >> 27) & 3; 103 | 104 | switch (cmd) { 105 | case Command::NOP: 106 | std::printf("[MDEC ] NOP\n"); 107 | 108 | stat.rem = data; 109 | return; 110 | case Command::DecodeMacroblock: // TODO: handle this 111 | std::printf("[MDEC ] Decode Macroblock\n"); 112 | 113 | cmdLen = data & 0xFFFF; 114 | 115 | state = MDECState::ReceiveMacroblock; 116 | break; 117 | case Command::SetQuantTables: 118 | std::printf("[MDEC ] Set Quant Tables\n"); 119 | 120 | quantIdx = 0; 121 | 122 | cmdLen = (data & 1) ? 32 : 16; // cmd[0] == 1 sets lum and col 123 | 124 | state = MDECState::ReceiveQuantTables; 125 | break; 126 | case Command::SetScaleTable: 127 | std::printf("[MDEC ] Set Scale Table\n"); 128 | 129 | scaleIdx = 0; 130 | 131 | cmdLen = 32; 132 | 133 | state = MDECState::ReceiveScaleTable; 134 | break; 135 | default: 136 | std::printf("[MDEC ] Unhandled command %u\n", cmd); 137 | 138 | exit(0); 139 | } 140 | 141 | stat.busy = true; 142 | 143 | //dmac::setDRQ(Channel::MDECIN, true); 144 | } 145 | break; 146 | case MDECState::ReceiveMacroblock: 147 | if (!--cmdLen) { 148 | stat.rem = 0xFFFF; 149 | stat.busy = false; 150 | 151 | /* Clear MDEC_IN request, set MDEC_OUT request */ 152 | 153 | stat.full = true; 154 | stat.ireq = false; 155 | 156 | stat.empty = false; 157 | stat.oreq = true; 158 | 159 | dmac::setDRQ(Channel::MDECOUT, true); 160 | 161 | state = MDECState::Idle; 162 | } 163 | break; 164 | case MDECState::ReceiveQuantTables: 165 | assert(quantIdx < 128); 166 | 167 | //std::printf("%d, %d\n", quantIdx, cmdLen); 168 | 169 | std::memcpy(&quantTable[quantIdx], &data, 4); 170 | 171 | quantIdx += 4; 172 | 173 | if (!--cmdLen) { 174 | //std::printf("Done\n"); 175 | 176 | stat.rem = 0xFFFF; 177 | stat.busy = false; 178 | 179 | state = MDECState::Idle; 180 | } 181 | break; 182 | case MDECState::ReceiveScaleTable: 183 | assert(scaleIdx < 64); 184 | 185 | std::memcpy(&scaleTable[scaleIdx], &data, 4); 186 | 187 | scaleIdx += 2; 188 | 189 | if (!--cmdLen) { 190 | stat.rem = 0xFFFF; 191 | stat.busy = false; 192 | 193 | state = MDECState::Idle; 194 | } 195 | break; 196 | } 197 | } 198 | 199 | void writeCtrl(u32 data) { 200 | std::printf("[MDEC ] 32-bit write @ MDEC1 = 0x%08X\n", data); 201 | 202 | if (data & (1 << 31)) { 203 | std::printf("[MDEC ] MDEC reset\n"); 204 | 205 | stat.rem = 0; 206 | stat.blk = 0; 207 | stat.b15 = false; 208 | stat.sign = false; 209 | stat.dep = 0; 210 | stat.oreq = false; 211 | stat.ireq = true; 212 | stat.busy = false; 213 | 214 | state = MDECState::Idle; 215 | } 216 | } 217 | 218 | } 219 | -------------------------------------------------------------------------------- /src/core/cpu/cop0.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "cop0.hpp" 7 | 8 | #include 9 | #include 10 | 11 | #include "cpu.hpp" 12 | 13 | namespace ps::cpu::cop0 { 14 | 15 | /* --- COP0 register definitions --- */ 16 | 17 | enum class COP0Reg { 18 | BPC = 0x03, 19 | BDA = 0x05, 20 | JumpDest = 0x06, 21 | DCIC = 0x07, 22 | BadVAddr = 0x08, 23 | BDAM = 0x09, 24 | BPCM = 0x0B, 25 | Status = 0x0C, 26 | Cause = 0x0D, 27 | EPC = 0x0E, 28 | PRId = 0x0F, 29 | }; 30 | 31 | /* COP0 Cause register */ 32 | struct Cause { 33 | u8 excode; // EXception CODE 34 | u8 ip; // Interrupt Pending 35 | u8 ce; // Coprocessor Error 36 | bool bd; // Branch Delay 37 | }; 38 | 39 | /* COP0 Status register */ 40 | struct Status { 41 | bool cie; // Current Interrupt Enable 42 | bool cku; // Current Kernel/User mode 43 | bool pie; // Previous Interrupt Enable 44 | bool pku; // Previous Kernel/User mode 45 | bool oie; // Old Interrupt Enable 46 | bool oku; // Old Kernel/User mode 47 | u8 im; // Interrupt Mask 48 | bool isc; // ISolate Cache 49 | bool swc; // SWap Caches 50 | bool pz; // cache Parity Zero 51 | bool ch; // Cache Hit 52 | bool pe; // cache Parity Error 53 | bool ts; // TLB Shutdown 54 | bool bev; // Boot Exception Vectors 55 | bool re; // Reverse Endianness 56 | u8 cu; // Coprocessor Usable 57 | }; 58 | 59 | Cause cause; 60 | Status status; 61 | 62 | u32 badvaddr; 63 | u32 epc; // Exception program counter 64 | 65 | void checkInterrupt() { 66 | if (status.cie && (status.im & cause.ip)) cpu::doInterrupt(); 67 | } 68 | 69 | void init() { 70 | status.bev = true; 71 | } 72 | 73 | /* Returns a COP0 register */ 74 | u32 get(u32 idx) { 75 | assert(idx < 32); 76 | 77 | u32 data; 78 | 79 | switch (idx) { 80 | case static_cast(COP0Reg::JumpDest): 81 | case static_cast(COP0Reg::DCIC ): 82 | return 0; 83 | case static_cast(COP0Reg::BadVAddr): 84 | return badvaddr; 85 | case static_cast(COP0Reg::Status): 86 | data = status.cie; 87 | data |= status.cku << 1; 88 | data |= status.pie << 2; 89 | data |= status.pku << 3; 90 | data |= status.oie << 4; 91 | data |= status.oku << 5; 92 | data |= status.im << 8; 93 | data |= status.isc << 16; 94 | data |= status.swc << 17; 95 | data |= status.pz << 18; 96 | data |= status.ch << 19; 97 | data |= status.pe << 20; 98 | data |= status.ts << 21; 99 | data |= status.bev << 22; 100 | data |= status.re << 25; 101 | data |= status.cu << 28; 102 | break; 103 | case static_cast(COP0Reg::Cause): 104 | data = cause.excode << 2; 105 | data |= cause.ip << 8; 106 | data |= cause.ce << 28; 107 | data |= cause.bd << 31; 108 | break; 109 | case static_cast(COP0Reg::EPC ): return epc; 110 | case static_cast(COP0Reg::PRId): return 1; // Value for CXD8530BQ CPU 111 | default: 112 | std::printf("[COP0:IOP ] Unhandled register read @ %u\n", idx); 113 | 114 | exit(0); 115 | } 116 | 117 | return data; 118 | } 119 | 120 | /* Sets a COP0 register */ 121 | void set(u32 idx, u32 data) { 122 | switch (idx) { 123 | case static_cast(COP0Reg::BPC ): break; 124 | case static_cast(COP0Reg::BDA ): break; 125 | case static_cast(COP0Reg::JumpDest): break; 126 | case static_cast(COP0Reg::DCIC ): break; 127 | case static_cast(COP0Reg::BDAM ): break; 128 | case static_cast(COP0Reg::BPCM ): break; 129 | case static_cast(COP0Reg::Status ): 130 | status.cie = data & (1 << 0); 131 | status.cku = data & (1 << 1); 132 | status.pie = data & (1 << 2); 133 | status.pku = data & (1 << 3); 134 | status.oie = data & (1 << 4); 135 | status.oku = data & (1 << 5); 136 | status.im = (data >> 8) & 0xFF; 137 | status.isc = data & (1 << 16); 138 | status.swc = data & (1 << 17); 139 | status.pz = data & (1 << 18); 140 | status.ch = data & (1 << 19); 141 | status.pe = data & (1 << 20); 142 | status.ts = data & (1 << 21); 143 | status.bev = data & (1 << 22); 144 | status.re = data & (1 << 25); 145 | status.cu = (data >> 28) & 0xF; 146 | 147 | checkInterrupt(); 148 | break; 149 | case static_cast(COP0Reg::Cause): 150 | cause.ip = (cause.ip & 0xFC) | ((data >> 8) & 3); // IP[1:0] are R/W 151 | 152 | checkInterrupt(); 153 | break; 154 | default: 155 | std::printf("[COP0:IOP ] Unhandled register write @ %u = 0x%08X\n", idx, data); 156 | 157 | exit(0); 158 | } 159 | } 160 | 161 | /* Sets exception code, saves privilege level and interrupt enable bit */ 162 | void enterException(Exception e) { 163 | cause.excode = e; 164 | 165 | /* Push interrupt enable */ 166 | status.oie = status.pie; 167 | status.pie = status.cie; 168 | status.cie = false; 169 | 170 | /* Push privilege level */ 171 | status.oku = status.pku; 172 | status.pku = status.cku; 173 | status.cku = true; 174 | } 175 | 176 | /* Restores privilege level and interrupt enable bit */ 177 | void leaveException() { 178 | /* Pop interrupt enable */ 179 | status.cie = status.pie; 180 | status.pie = status.oie; 181 | 182 | /* Pop privilege level */ 183 | status.cku = status.pku; 184 | status.pku = status.oku; 185 | 186 | checkInterrupt(); 187 | } 188 | 189 | /* Sets IP bit in Cause, optionally triggers an interrupt */ 190 | void setInterruptPending(bool irq) { 191 | cause.ip &= ~(1 << 2); 192 | cause.ip |= irq << 2; 193 | 194 | checkInterrupt(); 195 | } 196 | 197 | /* Returns true if BEV is set */ 198 | bool isBEV() { 199 | return status.bev; 200 | } 201 | 202 | /* Returns true if IsC bit is set */ 203 | bool isCacheIsolated() { 204 | return status.isc; 205 | } 206 | 207 | /* Sets the BD bit in Cause */ 208 | void setBD(bool bd) { 209 | cause.bd = bd; 210 | } 211 | 212 | /* Sets the exception program counter */ 213 | void setEPC(u32 pc) { 214 | epc = pc; 215 | } 216 | 217 | void setBadVAddr(u32 addr) { 218 | badvaddr = addr; 219 | } 220 | 221 | } 222 | -------------------------------------------------------------------------------- /src/core/sio/sio.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "sio.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../intc.hpp" 14 | #include "../scheduler.hpp" 15 | 16 | namespace ps::sio { 17 | 18 | using Interrupt = intc::Interrupt; 19 | 20 | /* --- SIO constants --- */ 21 | 22 | constexpr i64 ACK_TIME = 8 * 0x88; 23 | 24 | /* --- SIO registers --- */ 25 | 26 | enum class SIOReg { 27 | JOYFIFO = 0x1F801040, 28 | JOYSTAT = 0x1F801044, 29 | JOYMODE = 0x1F801048, 30 | JOYCTRL = 0x1F80104A, 31 | JOYBAUD = 0x1F80104E, 32 | }; 33 | 34 | enum JOYState { // Only for controllers 35 | Idle, 36 | SendID, 37 | SendButtons, 38 | }; 39 | 40 | struct JOYCTRL { 41 | bool txen; // TX enable 42 | //bool joyn; // /JOYn 43 | bool rxen; // RX enable 44 | u8 irqm; // RX interrupt mode 45 | bool tirq; // TX interrupt enable 46 | bool rirq; // RX interrupt enable 47 | bool airq; // ACK interrupt enable 48 | bool slot; // /JOYn select 49 | 50 | u16 raw; 51 | }; 52 | 53 | struct JOYSTAT { 54 | bool rdy0; // TX ready 0 (?) 55 | bool rdy1; // TX ready 1 (?) 56 | bool ack; // /ACK 57 | bool irq; // IRQ7 pending 58 | }; 59 | 60 | JOYCTRL joyctrl; 61 | JOYSTAT joystat; 62 | 63 | JOYState state = JOYState::Idle; 64 | 65 | u16 keyState = ~1; 66 | 67 | int cmdLen = 0; 68 | 69 | std::queue rxFIFO; 70 | 71 | u64 idSendACK; 72 | 73 | void sendACKEvent(int data) { 74 | //std::printf("[SIO ] ACK\n"); 75 | 76 | rxFIFO.push(data); 77 | 78 | joystat.rdy0 = true; 79 | joystat.rdy1 = true; 80 | joystat.ack = true; 81 | 82 | if (joyctrl.airq) { 83 | joystat.irq = true; 84 | 85 | intc::sendInterrupt(Interrupt::SIORecieve); 86 | } 87 | } 88 | 89 | void init() { 90 | /* Register scheduler events */ 91 | idSendACK = scheduler::registerEvent([](int data, i64) { sendACKEvent(data); }); 92 | } 93 | 94 | u8 read8(u32 addr) { 95 | u8 data; 96 | 97 | if (addr >= 0x1F801050) return 0; 98 | 99 | switch (addr) { 100 | case static_cast(SIOReg::JOYFIFO): 101 | //std::printf("[SIO ] 8-bit read @ JOY_RX_FIFO\n"); 102 | 103 | if (rxFIFO.empty()) { 104 | //std::printf("RX FIFO is empty!\n"); 105 | 106 | return 0xFF; 107 | } 108 | 109 | data = rxFIFO.front(); rxFIFO.pop(); 110 | break; 111 | default: 112 | std::printf("[SIO ] Unhandled 8-bit read @ 0x%08X\n", addr); 113 | 114 | exit(0); 115 | } 116 | 117 | return data; 118 | } 119 | 120 | u16 read16(u32 addr) { 121 | u16 data; 122 | 123 | if (addr >= 0x1F801050) return 0; 124 | 125 | switch (addr) { 126 | case static_cast(SIOReg::JOYSTAT): 127 | ////std::printf("[SIO ] 16-bit read @ JOY_STAT\n"); 128 | 129 | data = joystat.rdy0; 130 | data |= joystat.rdy1 << 2; 131 | data |= joystat.ack << 7; 132 | data |= joystat.irq << 9; 133 | 134 | if (rxFIFO.size()) data |= 1 << 1; 135 | 136 | joystat.ack = false; 137 | break; 138 | case static_cast(SIOReg::JOYCTRL): 139 | //std::printf("[SIO ] 16-bit read @ JOY_CTRL\n"); 140 | 141 | data = joyctrl.txen; 142 | data |= joyctrl.rxen << 1; 143 | data |= joyctrl.irqm << 8; 144 | data |= joyctrl.tirq << 10; 145 | data |= joyctrl.rirq << 11; 146 | data |= joyctrl.airq << 12; 147 | data |= joyctrl.slot << 13; 148 | break; 149 | case static_cast(SIOReg::JOYBAUD): 150 | //std::printf("[SIO ] 16-bit read @ JOY_BAUD\n"); 151 | 152 | data = 0x0088; 153 | break; 154 | default: 155 | std::printf("[SIO ] Unhandled 16-bit read @ 0x%08X\n", addr); 156 | 157 | exit(0); 158 | } 159 | 160 | return data; 161 | } 162 | 163 | void write8(u32 addr, u8 data) { 164 | if (addr >= 0x1F801050) return; 165 | 166 | switch (addr) { 167 | case static_cast(SIOReg::JOYFIFO): 168 | //std::printf("[SIO ] 8-bit write @ JOY_TX_FIFO = 0x%02X\n", data); 169 | 170 | joystat.rdy0 = false; 171 | joystat.rdy1 = false; 172 | 173 | switch (state) { 174 | case JOYState::Idle: 175 | if ((data == 0x01) && ((joyctrl.raw & 0x2002) == 2)) { // Controller, slot 1 176 | /* Send ACK */ 177 | scheduler::addEvent(idSendACK, 0xFF, ACK_TIME); 178 | 179 | state = JOYState::SendID; 180 | 181 | cmdLen = 2; 182 | } else { 183 | rxFIFO.push(0xFF); 184 | 185 | joystat.rdy0 = true; 186 | joystat.rdy1 = true; 187 | } 188 | break; 189 | case JOYState::SendID: 190 | /* Send ACK */ 191 | scheduler::addEvent(idSendACK, (cmdLen == 2) ? 0x41 : 0x5A, ACK_TIME); 192 | 193 | if (!--cmdLen) { 194 | assert(!data); 195 | 196 | state = JOYState::SendButtons; 197 | 198 | cmdLen = 2; 199 | } else { 200 | if (data != 0x42) { // Read command 201 | //std::printf("[SIO ] Unhandled JOY command 0x%02X\n", data); 202 | 203 | state = JOYState::Idle; 204 | 205 | return; 206 | } 207 | } 208 | break; 209 | case JOYState::SendButtons: 210 | assert(!data); 211 | 212 | scheduler::addEvent(idSendACK, (cmdLen == 2) ? keyState : (keyState >> 8), ACK_TIME); 213 | 214 | if (!--cmdLen) { 215 | state = JOYState::Idle; 216 | } 217 | break; 218 | default: 219 | std::printf("[SIO ] Unhandled JOY state\n"); 220 | 221 | exit(0); 222 | } 223 | break; 224 | default: 225 | std::printf("[SIO ] Unhandled 8-bit write @ 0x%08X = 0x%02X\n", addr, data); 226 | 227 | exit(0); 228 | } 229 | } 230 | 231 | void write16(u32 addr, u16 data) { 232 | if (addr >= 0x1F801050) return; 233 | 234 | switch (addr) { 235 | case static_cast(SIOReg::JOYMODE): 236 | //std::printf("[SIO ] 16-bit write @ JOY_MODE = 0x%04X\n", data); 237 | 238 | assert(data == 0x000D); 239 | break; 240 | case static_cast(SIOReg::JOYCTRL): 241 | //std::printf("[SIO ] 16-bit write @ JOY_CTRL = 0x%04X\n", data); 242 | 243 | joyctrl.raw = data; 244 | 245 | joyctrl.txen = data & (1 << 0); 246 | joyctrl.irqm = (data >> 8) & 3; 247 | joyctrl.tirq = data & (1 << 10); 248 | joyctrl.rirq = data & (1 << 11); 249 | joyctrl.airq = data & (1 << 12); 250 | 251 | if (data & (1 << 4)) { 252 | /* Ack JOY_STAT bits */ 253 | //std::printf("[SIO ] Acknowledge IRQ\n"); 254 | 255 | joystat.irq = false; 256 | } 257 | 258 | if (data & (1 << 6)) { 259 | //std::printf("[SIO ] JOY reset\n"); 260 | 261 | joystat.rdy0 = true; 262 | joystat.rdy1 = true; 263 | joystat.irq = false; 264 | 265 | state = JOYState::Idle; 266 | 267 | while (rxFIFO.size()) rxFIFO.pop(); 268 | } 269 | 270 | joyctrl.slot = data & (1 << 13); 271 | 272 | if (!data) { 273 | //std::printf("[SIO ] JOY time-out\n"); 274 | 275 | state = JOYState::Idle; 276 | 277 | while (rxFIFO.size()) rxFIFO.pop(); 278 | } 279 | break; 280 | case static_cast(SIOReg::JOYBAUD): 281 | //std::printf("[SIO ] 16-bit write @ JOY_BAUD = 0x%04X\n", data); 282 | 283 | assert(data == 0x0088); 284 | break; 285 | default: 286 | std::printf("[SIO ] Unhandled 16-bit write @ 0x%08X = 0x%04X\n", addr, data); 287 | 288 | exit(0); 289 | } 290 | } 291 | 292 | void setInput(u16 input) { 293 | keyState = input; 294 | } 295 | 296 | } 297 | -------------------------------------------------------------------------------- /src/core/timer/timer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "timer.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../intc.hpp" 13 | 14 | namespace ps::timer { 15 | 16 | using Interrupt = intc::Interrupt; 17 | 18 | /* --- Timer registers --- */ 19 | 20 | enum TimerReg { 21 | COUNT = 0x1F801100, 22 | MODE = 0x1F801104, 23 | COMP = 0x1F801108, 24 | }; 25 | 26 | /* Timer mode register */ 27 | struct Mode { 28 | bool gate; // GATE enable 29 | u8 gats; // GATe Select 30 | bool zret; // Zero RETurn 31 | bool cmpe; // CoMPare Enable 32 | bool ovfe; // OVerFlow Enable 33 | bool rept; // REPeaT interrupt 34 | bool levl; // LEVL 35 | u8 clks; // CLocK Select 36 | bool intf; // INTerrupt Flag 37 | bool equf; // EQUal Flag 38 | bool ovff; // OVerFlow Flag 39 | }; 40 | 41 | /* Timer */ 42 | struct Timer { 43 | Mode mode; // T_MODE 44 | 45 | u32 count; // T_COUNT 46 | u16 comp; // T_COMP 47 | 48 | // Prescaler 49 | u16 subcount; 50 | u16 prescaler; 51 | 52 | bool isPaused; 53 | }; 54 | 55 | Timer timers[3]; 56 | 57 | /* Returns timer ID from address */ 58 | int getTimer(u32 addr) { 59 | switch ((addr >> 4) & 0xFF) { 60 | case 0x10: return 0; 61 | case 0x11: return 1; 62 | case 0x12: return 2; 63 | default: 64 | std::printf("[Timer ] Invalid timer\n"); 65 | 66 | exit(0); 67 | } 68 | } 69 | 70 | void sendInterrupt(int tmID) { 71 | auto &mode = timers[tmID].mode; 72 | 73 | if (mode.intf) intc::sendInterrupt(static_cast(tmID + 4)); 74 | 75 | if (mode.rept && mode.levl) { 76 | mode.intf = !mode.intf; // Toggle interrupt flag 77 | } else { 78 | mode.intf = false; 79 | } 80 | } 81 | 82 | void init() { 83 | memset(&timers, 0, 3 * sizeof(Timer)); 84 | 85 | for (auto &i : timers) i.prescaler = 1; 86 | 87 | std::printf("[Timer ] Init OK\n"); 88 | } 89 | 90 | u16 read(u32 addr) { 91 | u16 data; 92 | 93 | // Get channel ID 94 | const auto chn = getTimer(addr); 95 | 96 | auto &timer = timers[chn]; 97 | 98 | switch ((addr & ~0xFF0) | (1 << 8)) { 99 | case TimerReg::COUNT: 100 | //std::printf("[Timer ] 16-bit read @ T%d_COUNT\n", chn); 101 | return timer.count; 102 | case TimerReg::MODE: 103 | { 104 | //std::printf("[Timer ] 16-bit read @ T%d_MODE\n", chn); 105 | 106 | auto &mode = timer.mode; 107 | 108 | data = mode.gate; 109 | data |= mode.gats << 1; 110 | data |= mode.zret << 3; 111 | data |= mode.cmpe << 4; 112 | data |= mode.ovfe << 5; 113 | data |= mode.rept << 6; 114 | data |= mode.levl << 7; 115 | data |= mode.clks << 8; 116 | data |= mode.intf << 10; 117 | data |= mode.equf << 11; 118 | data |= mode.ovff << 12; 119 | 120 | /* Clear interrupt flags */ 121 | 122 | mode.equf = false; 123 | mode.ovff = false; 124 | } 125 | break; 126 | case TimerReg::COMP: 127 | //std::printf("[Timer ] 16-bit read @ T%d_COMP\n", chn); 128 | return timer.comp; 129 | default: 130 | std::printf("[Timer ] Unhandled 16-bit read @ 0x%08X\n", addr); 131 | 132 | exit(0); 133 | } 134 | 135 | return data; 136 | } 137 | 138 | void write(u32 addr, u16 data) { 139 | // Get channel ID 140 | const auto chn = getTimer(addr); 141 | 142 | auto &timer = timers[chn]; 143 | 144 | switch ((addr & ~0xFF0) | (1 << 8)) { 145 | case TimerReg::COUNT: 146 | std::printf("[Timer ] 16-bit write @ T%d_COUNT = 0x%04X\n", chn, data); 147 | 148 | timer.count = data; 149 | break; 150 | case TimerReg::MODE: 151 | { 152 | auto &mode = timer.mode; 153 | 154 | std::printf("[Timer ] 16-bit write @ T%d_MODE = 0x%04X\n", chn, data); 155 | 156 | mode.gate = data & 1; 157 | mode.gats = (data >> 1) & 3; 158 | mode.zret = data & (1 << 3); 159 | mode.cmpe = data & (1 << 4); 160 | mode.ovfe = data & (1 << 5); 161 | mode.rept = data & (1 << 6); 162 | mode.levl = data & (1 << 7); 163 | mode.clks = (data >> 8) & 3; 164 | 165 | mode.intf = true; // Always reset to 1 166 | 167 | timer.isPaused = false; 168 | 169 | if (mode.gate) { 170 | switch (chn) { 171 | case 0: // HBLANK gate 172 | std::printf("[Timer ] Unhandled timer 0 gate\n"); 173 | 174 | exit(0); 175 | case 1: // VBLANK gate 176 | switch (mode.gats) { 177 | case 0: break; // Pause during VBLANK 178 | case 1: break; // Reset counter at VBLANK start 179 | case 2: // Reset counter at VBLANK start, pause outside of VBLANK 180 | timer.isPaused = true; 181 | break; 182 | case 3: // Pause ONCE until VBLANK start 183 | timer.isPaused = true; 184 | break; 185 | } 186 | break; 187 | case 2: 188 | switch (mode.gats) { 189 | case 0: case 3: // Pause forever 190 | timer.isPaused = true; 191 | break; 192 | case 1: case 2: // Nothing 193 | break; 194 | } 195 | break; 196 | } 197 | } 198 | 199 | if (mode.clks) { 200 | switch (chn) { 201 | case 1: break; 202 | case 2: timer.prescaler = 1 + 7 * (u16)(mode.clks > 1); break; 203 | default: 204 | std::printf("[Timer ] Unhandled clock source\n"); 205 | 206 | exit(0); 207 | } 208 | } 209 | 210 | timer.subcount = 0; 211 | timer.count = 0; // Always cleared 212 | } 213 | break; 214 | case TimerReg::COMP: 215 | std::printf("[Timer ] 16-bit write @ T%d_COMP = 0x%04X\n", chn, data); 216 | 217 | timer.comp = data; 218 | 219 | if (!timer.mode.levl) timer.mode.intf = true; // Set INTF if in toggle mode 220 | break; 221 | default: 222 | std::printf("[Timer ] Unhandled 16-bit write @ 0x%08X = 0x%04X\n", addr, data); 223 | 224 | exit(0); 225 | } 226 | } 227 | 228 | /* Steps timers */ 229 | void step(i64 c) { 230 | for (int i = 0; i < 3; i++) { 231 | auto &timer = timers[i]; 232 | 233 | /* Timers 0 and 1 have a different clock source if CLKS is odd */ 234 | if ((timer.mode.clks & 1) && ((i == 0) || (i == 1))) continue; 235 | 236 | if (timer.isPaused) continue; 237 | 238 | timer.subcount += c; 239 | 240 | while (timer.subcount > timer.prescaler) { 241 | timer.count++; 242 | 243 | if (timer.count & (1 << 16)) { 244 | if (timer.mode.ovfe && !timer.mode.ovff) { 245 | // Checking OVFF is necessary because timer IRQs are edge-triggered 246 | timer.mode.ovff = true; 247 | 248 | sendInterrupt(i); 249 | } 250 | } 251 | 252 | timer.count &= 0xFFFF; 253 | 254 | if (timer.count == timer.comp) { 255 | if (timer.mode.cmpe && !timer.mode.equf) { 256 | // Checking EQUF is necessary because timer IRQs are edge-triggered 257 | timer.mode.equf = true; 258 | 259 | sendInterrupt(i); 260 | } 261 | 262 | if (timer.mode.zret) timer.count = 0; 263 | } 264 | 265 | timer.subcount -= timer.prescaler; 266 | } 267 | } 268 | } 269 | 270 | /* Steps HBLANK timer */ 271 | void stepHBLANK() { 272 | auto &timer = timers[1]; 273 | 274 | /* Not in HBLANK mode */ 275 | if (!(timer.mode.clks & 1)) return; 276 | 277 | if (timer.isPaused) return; 278 | 279 | timer.count++; 280 | 281 | if (timer.count & (1 << 16)) { 282 | if (timer.mode.ovfe && !timer.mode.ovff) { 283 | // Checking OVFF is necessary because timer IRQs are edge-triggered 284 | timer.mode.ovff = true; 285 | 286 | sendInterrupt(1); 287 | } 288 | } 289 | 290 | timer.count &= 0xFFFF; 291 | 292 | if (timer.count == timer.comp) { 293 | if (timer.mode.cmpe && !timer.mode.equf) { 294 | // Checking EQUF is necessary because timer IRQs are edge-triggered 295 | timer.mode.equf = true; 296 | 297 | sendInterrupt(1); 298 | } 299 | 300 | if (timer.mode.zret) timer.count = 0; 301 | } 302 | } 303 | 304 | /* Handle VBLANK start gate events */ 305 | void gateVBLANKStart() { 306 | auto &timer = timers[1]; 307 | 308 | auto &mode = timer.mode; 309 | 310 | if (!mode.gate) return; 311 | 312 | switch (mode.gats) { 313 | case 0: // Pause during VBLANK 314 | timer.isPaused = true; 315 | break; 316 | case 1: // Reset counter at VBLANK start 317 | timer.count = 0; 318 | break; 319 | case 2: // Reset counter at VBLANK start, pause outside of VBLANK 320 | timer.count = 0; 321 | 322 | timer.isPaused = false; 323 | break; 324 | case 3: // Pause ONCE until VBLANK start 325 | timer.isPaused = false; 326 | break; 327 | } 328 | } 329 | 330 | /* Handle VBLANK end gate events */ 331 | void gateVBLANKEnd() { 332 | auto &timer = timers[1]; 333 | 334 | auto &mode = timer.mode; 335 | 336 | if (!mode.gate) return; 337 | 338 | switch (mode.gats) { 339 | case 0: // Pause during VBLANK 340 | timer.isPaused = false; 341 | break; 342 | case 1: break; // Reset counter at VBLANK start 343 | case 2: // Reset counter at VBLANK start, pause outside of VBLANK 344 | timer.isPaused = true; 345 | break; 346 | case 3: break; // Pause ONCE until VBLANK start 347 | } 348 | } 349 | 350 | } 351 | -------------------------------------------------------------------------------- /src/core/bus/bus.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "bus.hpp" 7 | 8 | #include 9 | 10 | #include "../intc.hpp" 11 | #include "../cdrom/cdrom.hpp" 12 | #include "../dmac/dmac.hpp" 13 | #include "../gpu/gpu.hpp" 14 | #include "../mdec/mdec.hpp" 15 | #include "../sio/sio.hpp" 16 | #include "../spu/spu.hpp" 17 | #include "../timer/timer.hpp" 18 | 19 | #include "../../common/file.hpp" 20 | 21 | namespace ps::bus { 22 | 23 | /* --- PS memory regions --- */ 24 | 25 | /* Base addresses */ 26 | enum class MemoryBase { 27 | RAM = 0x00000000, 28 | SPRAM = 0x1F800000, // Scratchpad RAM 29 | SIO = 0x1F801040, // Serial I/O 30 | DMA = 0x1F801080, // DMA controller 31 | Timer = 0x1F801100, 32 | SPU = 0x1F801C00, // Sound processing unit 33 | BIOS = 0x1FC00000, 34 | }; 35 | 36 | /* Memory sizes */ 37 | enum class MemorySize { 38 | RAM = 0x200000, 39 | SPRAM = 0x000400, 40 | SIO = 0x000020, 41 | DMA = 0x000080, 42 | Timer = 0x000030, 43 | SPU = 0x000280, 44 | BIOS = 0x080000, 45 | }; 46 | 47 | /* --- PlayStation memory --- */ 48 | std::vector ram; 49 | std::vector bios; 50 | 51 | u8 spram[static_cast(MemorySize::SPRAM)]; 52 | 53 | /* Expansion region bases/sizes */ 54 | u32 exp1Base = 0x1F000000, exp1Size; 55 | u32 exp2Base = 0x1F000000, exp2Size; 56 | u32 exp3Base = 0x1FA00000, exp3Size; // Base is fixed!! 57 | 58 | /* PS-EXE loading */ 59 | char path[256]; 60 | bool enableEXE = false; 61 | 62 | /* Returns true if address is in range [base;size] */ 63 | bool inRange(u64 addr, u64 base, u64 size) { 64 | return (addr >= base) && (addr < (base + size)); 65 | } 66 | 67 | void init(const char *biosPath, const char *exePath) { 68 | ram.resize(static_cast(MemorySize::RAM)); 69 | 70 | if (exePath) { 71 | std::strncpy(path, exePath, 256); 72 | 73 | enableEXE = true; 74 | } 75 | 76 | bios = loadBinary(biosPath); 77 | 78 | assert(bios.size() == static_cast(MemorySize::BIOS)); 79 | 80 | //std::printf("[Bus ] Init OK\n"); 81 | } 82 | 83 | /* Reads a byte from the system bus */ 84 | u8 read8(u32 addr) { 85 | if (inRange(addr, exp1Base, exp1Size)) { 86 | //std::printf("[Bus ] 8-bit read @ 0x%08X (EXP1)\n", addr); 87 | 88 | return 0; 89 | } 90 | 91 | if (inRange(addr, static_cast(MemoryBase::RAM), static_cast(MemorySize::RAM))) { 92 | return ram[addr]; 93 | } else if (inRange(addr, static_cast(MemoryBase::SPRAM), static_cast(MemorySize::SPRAM))) { 94 | return spram[addr & 0x3FF]; 95 | } else if (inRange(addr, static_cast(MemoryBase::SIO), static_cast(MemorySize::SIO))) { 96 | return sio::read8(addr); 97 | } else if (inRange(addr, static_cast(MemoryBase::DMA), static_cast(MemorySize::DMA))) { 98 | return dmac::read(addr & ~3) >> (8 * (addr & 3)); 99 | } else if (inRange(addr, static_cast(MemoryBase::BIOS), static_cast(MemorySize::BIOS))) { 100 | return bios[addr - static_cast(MemoryBase::BIOS)]; 101 | } else { 102 | switch (addr) { 103 | case 0x1F801800: case 0x1F801801: case 0x1F801802: case 0x1F801803: 104 | return cdrom::read(addr); 105 | default: 106 | std::printf("[Bus ] Unhandled 8-bit read @ 0x%08X\n", addr); 107 | 108 | exit(0); 109 | } 110 | } 111 | } 112 | 113 | /* Reads a halfword from the system bus */ 114 | u16 read16(u32 addr) { 115 | u16 data; 116 | 117 | if (inRange(addr, static_cast(MemoryBase::RAM), static_cast(MemorySize::RAM))) { 118 | std::memcpy(&data, &ram[addr], sizeof(u16)); 119 | } else if (inRange(addr, static_cast(MemoryBase::SPRAM), static_cast(MemorySize::SPRAM))) { 120 | std::memcpy(&data, &spram[addr & 0x3FE], sizeof(u16)); 121 | } else if (inRange(addr, static_cast(MemoryBase::SIO), static_cast(MemorySize::SIO))) { 122 | return sio::read16(addr); 123 | } else if (inRange(addr, static_cast(MemoryBase::Timer), static_cast(MemorySize::Timer))) { 124 | return timer::read(addr); 125 | } else if (inRange(addr, static_cast(MemoryBase::SPU), static_cast(MemorySize::SPU))) { 126 | return spu::read(addr); 127 | } else if (inRange(addr, static_cast(MemoryBase::BIOS), static_cast(MemorySize::BIOS))) { 128 | std::memcpy(&data, &bios[addr - static_cast(MemoryBase::BIOS)], sizeof(u16)); 129 | } else { 130 | switch (addr) { 131 | case 0x1F801014: 132 | //std::printf("[Bus ] 16-bit read @ SPU_DELAY\n"); 133 | return 0x31E1; 134 | case 0x1F801016: 135 | //std::printf("[Bus ] 16-bit read @ SPU_DELAY\n"); 136 | return 0x2009; 137 | case 0x1F801070: 138 | ////std::printf("[Bus ] 16-bit read @ I_STAT\n"); 139 | return intc::readStat(); 140 | case 0x1F801074: 141 | ////std::printf("[Bus ] 16-bit read @ I_MASK\n"); 142 | return intc::readMask(); 143 | default: 144 | std::printf("[Bus ] Unhandled 16-bit read @ 0x%08X\n", addr); 145 | 146 | exit(0); 147 | } 148 | } 149 | 150 | return data; 151 | } 152 | 153 | /* Reads a word from the system bus */ 154 | u32 read32(u32 addr) { 155 | u32 data; 156 | 157 | if (inRange(addr, static_cast(MemoryBase::RAM), static_cast(MemorySize::RAM))) { 158 | std::memcpy(&data, &ram[addr], sizeof(u32)); 159 | } else if (inRange(addr, static_cast(MemoryBase::SPRAM), static_cast(MemorySize::SPRAM))) { 160 | std::memcpy(&data, &spram[addr & 0x3FC], sizeof(u32)); 161 | } else if (inRange(addr, static_cast(MemoryBase::DMA), static_cast(MemorySize::DMA))) { 162 | return dmac::read(addr); 163 | } else if (inRange(addr, static_cast(MemoryBase::Timer), static_cast(MemorySize::Timer))) { 164 | return timer::read(addr); 165 | } else if (inRange(addr, static_cast(MemoryBase::BIOS), static_cast(MemorySize::BIOS))) { 166 | std::memcpy(&data, &bios[addr - static_cast(MemoryBase::BIOS)], sizeof(u32)); 167 | } else { 168 | switch (addr) { 169 | case 0x1F801014: 170 | //std::printf("[Bus ] 32-bit read @ SPU_DELAY\n"); 171 | return 0x200931E1; 172 | case 0x1F80101C: 173 | //std::printf("[Bus ] 32-bit read @ EXP2_SIZE\n"); 174 | return exp2Size; 175 | case 0x1F801060: 176 | //std::printf("[Bus ] 32-bit read @ RAM_SIZE\n"); 177 | return 0x00000B88; 178 | case 0x1F801070: 179 | ////std::printf("[Bus ] 32-bit read @ I_STAT\n"); 180 | return intc::readStat(); 181 | case 0x1F801074: 182 | ////std::printf("[Bus ] 32-bit read @ I_MASK\n"); 183 | return intc::readMask(); 184 | case 0x1F801810: 185 | //std::printf("[Bus ] 32-bit read @ GPUREAD\n"); 186 | return gpu::readGPUREAD(); 187 | case 0x1F801814: 188 | ////std::printf("[Bus ] Unhandled 32-bit read @ GP1\n"); 189 | return gpu::readStatus(); 190 | case 0x1F801824: 191 | return mdec::readStat(); 192 | default: 193 | std::printf("[Bus ] Unhandled 32-bit read @ 0x%08X\n", addr); 194 | 195 | exit(0); 196 | } 197 | } 198 | 199 | return data; 200 | } 201 | 202 | /* Writes a byte to the system bus */ 203 | void write8(u32 addr, u8 data) { 204 | if (inRange(addr, exp2Base, exp2Size)) { 205 | if (addr == (exp2Base + 0x41)) { 206 | //std::printf("[PS ] POST = 0x%02X\n", data); 207 | } else { 208 | //std::printf("[Bus ] 8-bit write @ 0x%08X (EXP2) = 0x%02X\n", addr, data); 209 | } 210 | 211 | return; 212 | } 213 | 214 | if (inRange(addr, static_cast(MemoryBase::RAM), static_cast(MemorySize::RAM))) { 215 | ram[addr] = data; 216 | } else if (inRange(addr, static_cast(MemoryBase::SPRAM), static_cast(MemorySize::SPRAM))) { 217 | spram[addr & 0x3FF] = data; 218 | } else if (inRange(addr, static_cast(MemoryBase::SIO), static_cast(MemorySize::SIO))) { 219 | return sio::write8(addr, data); 220 | } else if (inRange(addr, static_cast(MemoryBase::DMA), static_cast(MemorySize::DMA))) { 221 | return dmac::write8(addr, data); 222 | } else { 223 | switch (addr) { 224 | case 0x1F801800: case 0x1F801801: case 0x1F801802: case 0x1F801803: 225 | return cdrom::write(addr, data); 226 | default: 227 | std::printf("[Bus ] Unhandled 8-bit write @ 0x%08X = 0x%02X\n", addr, data); 228 | 229 | exit(0); 230 | } 231 | } 232 | } 233 | 234 | /* Writes a halfword to the system bus */ 235 | void write16(u32 addr, u16 data) { 236 | if (inRange(addr, static_cast(MemoryBase::RAM), static_cast(MemorySize::RAM))) { 237 | std::memcpy(&ram[addr], &data, sizeof(u16)); 238 | } else if (inRange(addr, static_cast(MemoryBase::SPRAM), static_cast(MemorySize::SPRAM))) { 239 | std::memcpy(&spram[addr & 0x3FE], &data, sizeof(u16)); 240 | } else if (inRange(addr, static_cast(MemoryBase::SIO), static_cast(MemorySize::SIO))) { 241 | return sio::write16(addr, data); 242 | } else if (inRange(addr, static_cast(MemoryBase::Timer), static_cast(MemorySize::Timer))) { 243 | return timer::write(addr, data); 244 | } else if (inRange(addr, static_cast(MemoryBase::SPU), static_cast(MemorySize::SPU))) { 245 | return spu::write(addr, data); 246 | } else { 247 | switch (addr) { 248 | case 0x1F801014: 249 | //std::printf("[Bus ] 16-bit write @ SPU_DELAY = 0x%04X\n", data); 250 | break; 251 | case 0x1F801016: 252 | //std::printf("[Bus ] 16-bit write @ SPU_DELAY = 0x%04X\n", data); 253 | break; 254 | case 0x1F801070: 255 | ////std::printf("[Bus ] 16-bit write @ I_STAT = 0x%04X\n", data); 256 | return intc::writeStat(data); 257 | case 0x1F801074: 258 | //std::printf("[Bus ] 16-bit write @ I_MASK = 0x%04X\n", data); 259 | return intc::writeMask(data); 260 | default: 261 | std::printf("[Bus ] Unhandled 16-bit write @ 0x%08X = 0x%04X\n", addr, data); 262 | 263 | exit(0); 264 | } 265 | } 266 | } 267 | 268 | /* Writes a word to the system bus */ 269 | void write32(u32 addr, u32 data) { 270 | if (inRange(addr, static_cast(MemoryBase::RAM), static_cast(MemorySize::RAM))) { 271 | std::memcpy(&ram[addr], &data, sizeof(u32)); 272 | } else if (inRange(addr, static_cast(MemoryBase::SPRAM), static_cast(MemorySize::SPRAM))) { 273 | std::memcpy(&spram[addr & 0x3FC], &data, sizeof(u32)); 274 | } else if (inRange(addr, static_cast(MemoryBase::DMA), static_cast(MemorySize::DMA))) { 275 | return dmac::write32(addr, data); 276 | } else if (inRange(addr, static_cast(MemoryBase::Timer), static_cast(MemorySize::Timer))) { 277 | return timer::write(addr, data); 278 | } else { 279 | switch (addr) { 280 | case 0x1F801000: 281 | //std::printf("[Bus ] 32-bit write @ EXP1_BASE = 0x%08X\n", data); 282 | 283 | exp1Base = (exp1Base & 0xFF000000) | (data & 0xFFFFFF); 284 | break; 285 | case 0x1F801004: 286 | //std::printf("[Bus ] 32-bit write @ EXP2_BASE = 0x%08X\n", data); 287 | 288 | exp2Base = (exp2Base & 0xFF000000) | (data & 0xFFFFFF); 289 | break; 290 | case 0x1F801008: 291 | //std::printf("[Bus ] 32-bit write @ EXP1_SIZE = 0x%08X\n", data); 292 | 293 | exp1Size = 1 << ((data >> 16) & 0x1F); 294 | break; 295 | case 0x1F80100C: 296 | //std::printf("[Bus ] 32-bit write @ EXP3_SIZE = 0x%08X\n", data); 297 | 298 | exp3Size = 1 << ((data >> 16) & 0x1F); 299 | break; 300 | case 0x1F801010: 301 | //std::printf("[Bus ] 32-bit write @ BIOS_DELAY = 0x%08X\n", data); 302 | break; 303 | case 0x1F801014: 304 | //std::printf("[Bus ] 32-bit write @ SPU_DELAY = 0x%08X\n", data); 305 | break; 306 | case 0x1F801018: 307 | //std::printf("[Bus ] 32-bit write @ CDROM_DELAY = 0x%08X\n", data); 308 | break; 309 | case 0x1F80101C: 310 | //std::printf("[Bus ] 32-bit write @ EXP2_SIZE = 0x%08X\n", data); 311 | 312 | exp2Size = 1 << ((data >> 16) & 0x1F); 313 | break; 314 | case 0x1F801020: 315 | //std::printf("[Bus ] 32-bit write @ COM_DELAY = 0x%08X\n", data); 316 | break; 317 | case 0x1F801060: 318 | //std::printf("[Bus ] 32-bit write @ RAM_SIZE = 0x%08X\n", data); 319 | break; 320 | case 0x1F801070: 321 | ////std::printf("[Bus ] 32-bit write @ I_STAT = 0x%08X\n", data); 322 | return intc::writeStat(data); 323 | case 0x1F801074: 324 | //std::printf("[Bus ] 32-bit write @ I_MASK = 0x%08X\n", data); 325 | return intc::writeMask(data); 326 | case 0x1F801810: 327 | //std::printf("[Bus ] 32-bit write @ GP0 = 0x%08X\n", data); 328 | 329 | gpu::writeGP0(data); 330 | break; 331 | case 0x1F801814: 332 | //std::printf("[Bus ] 32-bit write @ GP1 = 0x%08X\n", data); 333 | 334 | gpu::writeGP1(data); 335 | break; 336 | case 0x1F801820: 337 | return mdec::writeCmd(data); 338 | case 0x1F801824: 339 | return mdec::writeCtrl(data); 340 | case 0x1FFE0130: 341 | //std::printf("[Bus ] 32-bit write @ CACHE_CONTROL = 0x%08X\n", data); 342 | break; 343 | default: 344 | std::printf("[Bus ] Unhandled 32-bit write @ 0x%08X = 0x%08X\n", addr, data); 345 | 346 | exit(0); 347 | } 348 | } 349 | } 350 | 351 | /* Loads a PS-EXE, returns entry point */ 352 | u32 loadEXE() { 353 | std::printf("Loading PS-EXE...\n"); 354 | 355 | const auto exe = loadBinary(path); 356 | 357 | if (std::strncmp((char *)exe.data(), "PS-X EXE", 8) != 0) { 358 | std::printf("Invalid PS-EXE\n"); 359 | 360 | exit(0); 361 | } 362 | 363 | /* Get CPU register values */ 364 | 365 | u32 entry, gp, sp; 366 | 367 | std::memcpy(&entry, &exe[0x10], 4); 368 | 369 | std::memcpy(&gp, &exe[0x14], 4); 370 | std::memcpy(&sp, &exe[0x30], 4); 371 | 372 | if (sp != 0x801FFF00) { 373 | std::printf("GP = 0x%08X, SP = 0x%08X\n", gp, sp); 374 | 375 | exit(0); 376 | } 377 | 378 | /* Copy code to RAM */ 379 | 380 | u32 addr, size; 381 | 382 | std::memcpy(&addr, &exe[0x18], 4); 383 | std::memcpy(&size, &exe[0x1C], 4); 384 | 385 | addr &= 0x1FFFFC; 386 | 387 | std::memcpy(&ram[addr], &exe[0x800], size); 388 | 389 | enableEXE = false; 390 | 391 | return entry; 392 | } 393 | 394 | bool isEXEEnabled() { 395 | return enableEXE; 396 | } 397 | 398 | } 399 | -------------------------------------------------------------------------------- /src/core/dmac/dmac.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "dmac.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "../intc.hpp" 13 | #include "../scheduler.hpp" 14 | #include "../bus/bus.hpp" 15 | #include "../cdrom/cdrom.hpp" 16 | #include "../gpu/gpu.hpp" 17 | #include "../mdec/mdec.hpp" 18 | #include "../spu/spu.hpp" 19 | 20 | namespace ps::dmac { 21 | 22 | using Interrupt = intc::Interrupt; 23 | 24 | const char *chnNames[7] = { 25 | "MDEC_IN", "MDEC_OUT", "GPU", "CDROM", "SPU", "PIO", "OTC", 26 | }; 27 | 28 | enum Mode { 29 | Burst, 30 | Slice, 31 | LinkedList, 32 | }; 33 | 34 | /* --- DMA registers --- */ 35 | 36 | /* DMA channel registers */ 37 | enum class ChannelReg { 38 | MADR = 0x1F801000, // Memory address 39 | BCR = 0x1F801004, // Block count 40 | CHCR = 0x1F801008, // Channel control 41 | }; 42 | 43 | /* DMA control registers */ 44 | enum class ControlReg { 45 | DPCR = 0x1F8010F0, 46 | DICR = 0x1F8010F4, 47 | }; 48 | 49 | /* DMA Interrupt Control */ 50 | struct DICR { 51 | bool fi; // Force interrupt 52 | u8 im; // Interrupt mask 53 | bool mie; // Master interrupt enable 54 | u8 ip; // Interrupt pending 55 | bool mif; // Master interrupt flag 56 | }; 57 | 58 | /* D_CHCR */ 59 | struct ChannelControl { 60 | bool dir; // Direction 61 | bool dec; // Decrementing address 62 | bool cpe; // Chopping enable 63 | u8 mod; // Mode 64 | u8 cpd; // Chopping window (DMA) 65 | u8 cpc; // Chopping window (CPU) 66 | bool str; // Start 67 | bool fst; // Forced start (don't wait for DRQ) 68 | }; 69 | 70 | /* DMA channel */ 71 | struct DMAChannel { 72 | ChannelControl chcr; 73 | 74 | u16 size, count; // Block count 75 | u32 madr; // Memory address 76 | 77 | u32 len; 78 | 79 | bool drq; 80 | }; 81 | 82 | DMAChannel channels[7]; // DMA channels 83 | 84 | /* DMA interrupt control */ 85 | DICR dicr; 86 | 87 | u32 dpcr; // Priority control 88 | 89 | u64 idTransferEnd; // Scheduler 90 | 91 | void checkInterrupt(); 92 | 93 | void transferEndEvent(int chnID) { 94 | auto &chcr = channels[chnID].chcr; 95 | 96 | //std::printf("[DMAC ] Channel %d (%s) transfer end\n", chnID, chnNames[chnID]); 97 | 98 | chcr.str = false; 99 | 100 | /* Set interrupt pending flag, check for interrupts */ 101 | if (dicr.im & (1 << chnID)) { 102 | dicr.ip |= 1 << chnID; 103 | 104 | checkInterrupt(); 105 | } 106 | } 107 | 108 | /* Returns DMA channel from address */ 109 | Channel getChannel(u32 addr) { 110 | switch ((addr >> 4) & 0xFF) { 111 | case 0x08: return Channel::MDECIN; 112 | case 0x09: return Channel::MDECOUT; 113 | case 0x0A: return Channel::GPU; 114 | case 0x0B: return Channel::CDROM; 115 | case 0x0C: return Channel::SPU; 116 | case 0x0D: return Channel::PIO; 117 | case 0x0E: return Channel::OTC; 118 | default: 119 | //std::printf("[DMAC ] Unknown channel\n"); 120 | 121 | exit(0); 122 | } 123 | } 124 | 125 | /* Handles CDROM DMA */ 126 | void doCDROM() { 127 | const auto chnID = Channel::CDROM; 128 | 129 | auto &chn = channels[static_cast(chnID)]; 130 | auto &chcr = chn.chcr; 131 | 132 | //std::printf("[DMAC ] CDROM transfer\n"); 133 | 134 | assert(!chcr.dir); // Always to RAM 135 | assert(chcr.mod == Mode::Burst); // Always burst? 136 | assert(!chcr.dec); // Always incrementing? 137 | assert(chn.size); 138 | 139 | for (int i = 0; i < chn.size; i++) { 140 | bus::write32(chn.madr, cdrom::getData32()); 141 | 142 | chn.madr += 4; 143 | } 144 | 145 | scheduler::addEvent(idTransferEnd, static_cast(chnID), 24 * chn.size); 146 | 147 | /* Clear BCR */ 148 | chn.count = 0; 149 | chn.size = 0; 150 | } 151 | 152 | /* Handles GPU DMA */ 153 | void doGPU() { 154 | const auto chnID = Channel::GPU; 155 | 156 | auto &chn = channels[static_cast(chnID)]; 157 | auto &chcr = chn.chcr; 158 | 159 | //std::printf("[DMAC ] GPU transfer\n"); 160 | 161 | assert((chcr.mod == Mode::Slice) || (chcr.mod == Mode::LinkedList)); 162 | assert(!chcr.dec); // Always incrementing? 163 | 164 | i64 len = 0; 165 | 166 | if (chcr.mod == Mode::Slice) { 167 | assert(chn.len); 168 | 169 | len += chn.len; 170 | 171 | if (chcr.dir) { // To GPU 172 | for (int i = 0; i < len; i++) { 173 | gpu::writeGP0(bus::read32(chn.madr)); 174 | 175 | chn.madr += 4; 176 | } 177 | } else { // To RAM 178 | for (int i = 0; i < len; i++) { 179 | bus::write32(chn.madr, gpu::readGPUREAD()); 180 | 181 | chn.madr += 4; 182 | } 183 | } 184 | } else { 185 | assert(chcr.dir); 186 | 187 | /* Linked list DMA */ 188 | while (true) { 189 | /* Get header */ 190 | const auto header = bus::read32(chn.madr); 191 | 192 | chn.madr += 4; 193 | 194 | const auto size = (int)(header >> 24); 195 | 196 | len += size; 197 | 198 | /* Transfer size words */ 199 | for (int i = 0; i < size; i++) { 200 | gpu::writeGP0(bus::read32(chn.madr)); 201 | 202 | chn.madr += 4; 203 | } 204 | 205 | if (header & (1 << 23)) break; // We're done 206 | 207 | chn.madr = header & 0x1FFFFC; 208 | } 209 | } 210 | 211 | scheduler::addEvent(idTransferEnd, static_cast(chnID), len); 212 | 213 | /* Clear DMA request */ 214 | //chn.drq = false; 215 | 216 | /* Clear BCR */ 217 | chn.count = 0; 218 | chn.size = 0; 219 | } 220 | 221 | /* Handles MDEC_IN DMA */ 222 | void doMDECIN() { 223 | const auto chnID = Channel::MDECIN; 224 | 225 | auto &chn = channels[static_cast(chnID)]; 226 | auto &chcr = chn.chcr; 227 | 228 | //std::printf("[DMAC ] MDEC_IN transfer\n"); 229 | 230 | //assert(chcr.dir); // Always from RAM 231 | assert(chcr.mod == Mode::Slice); // Always slice? 232 | assert(!chcr.dec); // Always incrementing? 233 | assert(chn.len); 234 | 235 | for (int i = 0; i < (int)chn.len; i++) { 236 | mdec::writeCmd(bus::read32(chn.madr)); 237 | 238 | chn.madr += 4; 239 | } 240 | 241 | scheduler::addEvent(idTransferEnd, static_cast(chnID), chn.len); 242 | 243 | /* Clear DRQ */ 244 | chn.drq = false; 245 | 246 | /* Clear BCR */ 247 | chn.count = 0; 248 | chn.size = 0; 249 | chn.len = 0; 250 | } 251 | 252 | /* Handles MDEC_OUT DMA */ 253 | void doMDECOUT() { 254 | const auto chnID = Channel::MDECOUT; 255 | 256 | auto &chn = channels[static_cast(chnID)]; 257 | auto &chcr = chn.chcr; 258 | 259 | //std::printf("[DMAC ] MDEC_OUT transfer\n"); 260 | 261 | //assert(chcr.dir); // Always from RAM 262 | assert(chcr.mod == Mode::Slice); // Always slice? 263 | assert(!chcr.dec); // Always incrementing? 264 | assert(chn.len); 265 | 266 | for (int i = 0; i < (int)chn.len; i++) { 267 | //bus::write32(chn.madr, mdec::readData()); 268 | 269 | chn.madr += 4; 270 | } 271 | 272 | scheduler::addEvent(idTransferEnd, static_cast(chnID), chn.len); 273 | 274 | /* Clear DRQ */ 275 | chn.drq = false; 276 | 277 | /* Clear BCR */ 278 | chn.count = 0; 279 | chn.size = 0; 280 | chn.len = 0; 281 | } 282 | 283 | /* Handles OTC DMA */ 284 | void doOTC() { 285 | const auto chnID = Channel::OTC; 286 | 287 | auto &chn = channels[static_cast(chnID)]; 288 | auto &chcr = chn.chcr; 289 | 290 | //std::printf("[DMAC ] OTC transfer\n"); 291 | 292 | assert(!chcr.dir); // Always to RAM 293 | assert(chcr.mod == Mode::Burst); // Always burst? 294 | assert(chcr.dec); // Always decrementing? 295 | assert(chn.size); 296 | 297 | for (int i = chn.size; i > 0; i--) { 298 | u32 data; 299 | if (i != 1) { data = chn.madr - 4; } else { data = 0xFFFFFF; } 300 | 301 | bus::write32(chn.madr, data); 302 | 303 | chn.madr -= 4; 304 | } 305 | 306 | scheduler::addEvent(idTransferEnd, static_cast(chnID), chn.size); 307 | 308 | /* Clear BCR */ 309 | chn.count = 0; 310 | chn.size = 0; 311 | } 312 | 313 | /* Handles SPU DMA */ 314 | void doSPU() { 315 | const auto chnID = Channel::SPU; 316 | 317 | auto &chn = channels[static_cast(chnID)]; 318 | auto &chcr = chn.chcr; 319 | 320 | //std::printf("[DMAC ] SPU transfer\n"); 321 | 322 | assert(chcr.mod == Mode::Slice); // Always slice? 323 | assert(!chcr.dec); // Always incrementing? 324 | assert(chn.len); 325 | 326 | if (chcr.dir) { 327 | for (int i = 0; i < (int)chn.len; i++) { 328 | const auto data = bus::read32(chn.madr); 329 | 330 | spu::writeRAM(data); 331 | spu::writeRAM(data >> 16); 332 | 333 | chn.madr += 4; 334 | } 335 | } else { 336 | chn.madr += 4 * chn.len; 337 | } 338 | 339 | scheduler::addEvent(idTransferEnd, static_cast(chnID), 4 * chn.len); 340 | 341 | /* Clear BCR */ 342 | chn.count = 0; 343 | chn.size = 0; 344 | chn.len = 0; 345 | } 346 | 347 | void startDMA(Channel chn) { 348 | switch (chn) { 349 | case Channel::MDECIN : doMDECIN(); break; 350 | case Channel::MDECOUT: doMDECOUT(); break; 351 | case Channel::GPU : doGPU(); break; 352 | case Channel::CDROM : doCDROM(); break; 353 | case Channel::SPU : doSPU(); break; 354 | case Channel::OTC : doOTC(); break; 355 | default: 356 | //std::printf("[DMAC ] Unhandled channel %d (%s) transfer\n", chn, chnNames[static_cast(chn)]); 357 | 358 | exit(0); 359 | } 360 | } 361 | 362 | /* Sets master interrupt flag, sends interrupt */ 363 | void checkInterrupt() { 364 | const auto oldMIF = dicr.mif; 365 | 366 | dicr.mif = dicr.fi || (dicr.mie && (dicr.im & dicr.ip)); 367 | 368 | //std::printf("[DMAC ] MIF = %d\n", dicr.mif); 369 | 370 | if (!oldMIF && dicr.mif) intc::sendInterrupt(Interrupt::DMA); 371 | } 372 | 373 | void checkRunning(Channel chn) { 374 | const auto chnID = static_cast(chn); 375 | 376 | //std::printf("[DMAC ] Channel %d check\n", chnID); 377 | 378 | const bool cde = dpcr & (1 << (4 * chnID + 3)); 379 | 380 | //std::printf("[DMAC ] D%d.DRQ = %d, DPCR.CDE%d = %d, D%d_CHCR.STR = %d, D%d_CHCR.FST = %d\n", chnID, channels[chnID].drq, chnID, cde, chnID, channels[chnID].chcr.str, chnID, channels[chnID].chcr.fst); 381 | 382 | if ((channels[chnID].drq || channels[chnID].chcr.fst) && cde && channels[chnID].chcr.str) startDMA(static_cast(chnID)); 383 | } 384 | 385 | void checkRunningAll() { 386 | for (int i = 0; i < 7; i++) { 387 | const bool cde = dpcr & (1 << (4 * i + 3)); 388 | 389 | //std::printf("[DMAC ] D%d.DRQ = %d, DPCR.CDE%d = %d, D%d_CHCR.STR = %d, D%d_CHCR.FST = %d\n", i, channels[i].drq, i, cde, i, channels[i].chcr.str, i, channels[i].chcr.fst); 390 | 391 | if ((channels[i].drq || channels[i].chcr.fst) && cde && channels[i].chcr.str) return startDMA(static_cast(i)); 392 | } 393 | } 394 | 395 | void init() { 396 | std::memset(&channels, 0, 7 * sizeof(DMAChannel)); 397 | 398 | /* Set initial DRQs */ 399 | channels[static_cast(Channel::MDECIN)].drq = true; 400 | channels[static_cast(Channel::GPU )].drq = true; // Hack 401 | channels[static_cast(Channel::SPU )].drq = true; 402 | channels[static_cast(Channel::OTC )].drq = true; 403 | 404 | /* TODO: register scheduler events */ 405 | idTransferEnd = scheduler::registerEvent([](int chnID, i64) { transferEndEvent(chnID); }); 406 | } 407 | 408 | u32 read(u32 addr) { 409 | u32 data; 410 | 411 | if (addr < static_cast(ControlReg::DPCR)) { 412 | const auto chnID = static_cast(getChannel(addr)); 413 | 414 | auto &chn = channels[chnID]; 415 | 416 | switch (addr & ~(0xFF0)) { 417 | case static_cast(ChannelReg::MADR): 418 | //std::printf("[DMAC ] 32-bit read @ D%d_MADR\n", chnID); 419 | 420 | return chn.madr; 421 | case static_cast(ChannelReg::CHCR): 422 | { 423 | auto &chcr = chn.chcr; 424 | 425 | //std::printf("[DMAC ] 32-bit read @ D%d_CHCR\n", chnID); 426 | 427 | data = chcr.dir; 428 | data |= chcr.dec << 1; 429 | data |= chcr.mod << 9; 430 | data |= chcr.cpd << 16; 431 | data |= chcr.cpc << 20; 432 | data |= chcr.str << 24; 433 | data |= chcr.fst << 28; 434 | } 435 | break; 436 | default: 437 | std::printf("[DMAC ] Unhandled 32-bit channel read @ 0x%08X\n", addr); 438 | 439 | exit(0); 440 | } 441 | } else { 442 | switch (addr) { 443 | case static_cast(ControlReg::DPCR): 444 | //std::printf("[DMAC ] 32-bit read @ DPCR\n"); 445 | return dpcr; 446 | case static_cast(ControlReg::DICR): 447 | //std::printf("[DMAC ] 32-bit read @ DICR\n"); 448 | 449 | data = dicr.fi << 15; 450 | data |= dicr.im << 16; 451 | data |= dicr.mie << 23; 452 | data |= dicr.ip << 24; 453 | data |= dicr.mif << 31; 454 | break; 455 | default: 456 | std::printf("[DMAC ] Unhandled 32-bit control read @ 0x%08X\n", addr); 457 | 458 | exit(0); 459 | } 460 | } 461 | 462 | return data; 463 | } 464 | 465 | void write8(u32 addr, u8 data) { 466 | if (addr < static_cast(ControlReg::DPCR)) { 467 | switch (addr & ~(0xFF3)) { 468 | default: 469 | //std::printf("[DMAC ] Unhandled 8-bit channel write @ 0x%08X = 0x%02X\n", addr, data); 470 | 471 | exit(0); 472 | } 473 | } else { 474 | switch (addr & ~3) { 475 | case static_cast(ControlReg::DICR): 476 | { 477 | const auto offset = addr & 3; 478 | 479 | //std::printf("[DMAC ] 8-bit write @ DICR[%u] = 0x%08X\n", offset, data); 480 | 481 | switch (offset) { 482 | case 1: 483 | dicr.fi = data & (1 << 7); 484 | break; 485 | case 2: 486 | dicr.im = data & 0x7F; 487 | dicr.mie = data & (1 << 7); 488 | break; 489 | case 3: 490 | dicr.ip = (dicr.ip & ~data) & 0x7F; 491 | break; 492 | } 493 | 494 | checkInterrupt(); 495 | } 496 | break; 497 | default: 498 | std::printf("[DMAC ] Unhandled 8-bit control write @ 0x%08X = 0x%02X\n", addr, data); 499 | 500 | exit(0); 501 | } 502 | } 503 | } 504 | 505 | void write32(u32 addr, u32 data) { 506 | if (addr < static_cast(ControlReg::DPCR)) { 507 | const auto chnID = static_cast(getChannel(addr)); 508 | 509 | auto &chn = channels[chnID]; 510 | 511 | switch (addr & ~(0xFF0)) { 512 | case static_cast(ChannelReg::MADR): 513 | //std::printf("[DMAC ] 32-bit write @ D%u_MADR = 0x%08X\n", chnID, data); 514 | 515 | chn.madr = data & 0xFFFFFC; 516 | break; 517 | case static_cast(ChannelReg::BCR): 518 | //std::printf("[DMAC ] 32-bit write @ D%u_BCR = 0x%08X\n", chnID, data); 519 | 520 | chn.size = data; 521 | chn.count = data >> 16; 522 | 523 | chn.len = (u32)chn.count * (u32)chn.size; 524 | break; 525 | case static_cast(ChannelReg::CHCR): 526 | { 527 | auto &chcr = chn.chcr; 528 | 529 | //std::printf("[DMAC ] 32-bit write @ D%u_CHCR = 0x%08X\n", chnID, data); 530 | 531 | chcr.dir = data & (1 << 0); 532 | chcr.dec = data & (1 << 1); 533 | chcr.mod = (data >> 9) & 3; 534 | chcr.cpd = (data >> 16) & 7; 535 | chcr.cpc = (data >> 20) & 7; 536 | chcr.str = data & (1 << 24); 537 | chcr.fst = data & (1 << 28); 538 | } 539 | 540 | checkRunning(static_cast(chnID)); 541 | break; 542 | default: 543 | std::printf("[DMAC ] Unhandled 32-bit channel write @ 0x%08X = 0x%08X\n", addr, data); 544 | 545 | exit(0); 546 | } 547 | } else { 548 | switch (addr) { 549 | case static_cast(ControlReg::DPCR): 550 | //std::printf("[DMAC ] 32-bit write @ DPCR = 0x%08X\n", data); 551 | 552 | dpcr = data; 553 | 554 | //checkRunningAll(); 555 | break; 556 | case static_cast(ControlReg::DICR): 557 | //std::printf("[DMAC ] 32-bit write @ DICR = 0x%08X\n", data); 558 | 559 | //std::printf("[DMAC ] 0x%02X, 0x%02X\n", (data >> 16) & 0x7F, (data >> 24) & 0x7F); 560 | 561 | dicr.fi = data & (1 << 15); 562 | dicr.im = (data >> 16) & 0x7F; 563 | dicr.mie = data & (1 << 23); 564 | dicr.ip = (dicr.ip & ~(data >> 24)) & 0x7F; 565 | 566 | //std::printf("[DMAC ] IM = 0x%02X, IP = 0x%02X\n", dicr.im, dicr.ip); 567 | 568 | checkInterrupt(); 569 | break; 570 | default: 571 | std::printf("[DMAC ] Unhandled 32-bit control write @ 0x%08X = 0x%08X\n", addr, data); 572 | 573 | exit(0); 574 | } 575 | } 576 | } 577 | 578 | /* Sets DRQ, runs channel if enabled */ 579 | void setDRQ(Channel chn, bool drq) { 580 | channels[static_cast(chn)].drq = drq; 581 | 582 | checkRunning(chn); 583 | } 584 | 585 | } 586 | -------------------------------------------------------------------------------- /src/core/cdrom/cdrom.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "cdrom.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "../intc.hpp" 14 | #include "../scheduler.hpp" 15 | 16 | namespace ps::cdrom { 17 | 18 | using Interrupt = intc::Interrupt; 19 | 20 | /* --- CDROM constants --- */ 21 | 22 | constexpr int SECTOR_SIZE = 2352; 23 | constexpr int READ_SIZE = 0x818; 24 | 25 | constexpr i64 CPU_SPEED = 44100 * 0x300; 26 | constexpr i64 READ_TIME_SINGLE = CPU_SPEED / 75; 27 | constexpr i64 READ_TIME_DOUBLE = CPU_SPEED / (2 * 75); 28 | 29 | constexpr u64 _1MS = CPU_SPEED / 1000; 30 | 31 | constexpr i64 INT3_TIME = _1MS; 32 | 33 | /* CDROM commands */ 34 | enum Command { 35 | GetStat = 0x01, 36 | SetLoc = 0x02, 37 | ReadN = 0x06, 38 | Stop = 0x08, 39 | Pause = 0x09, 40 | Init = 0x0A, 41 | Unmute = 0x0C, 42 | SetFilter = 0x0D, 43 | SetMode = 0x0E, 44 | GetLocL = 0x10, 45 | GetLocP = 0x11, 46 | GetTN = 0x13, 47 | GetTD = 0x14, 48 | SeekL = 0x15, 49 | SeekP = 0x16, 50 | Test = 0x19, 51 | GetID = 0x1A, 52 | ReadS = 0x1B, 53 | ReadTOC = 0x1E, 54 | }; 55 | 56 | /* Sub commands */ 57 | enum SubCommand { 58 | GetBIOSDate = 0x20, 59 | }; 60 | 61 | /* Seek parameters */ 62 | struct SeekParam { 63 | int mins, secs, sector; 64 | }; 65 | 66 | /* --- CDROM registers --- */ 67 | 68 | enum class Mode { 69 | CDDAOn = 1 << 0, 70 | AutoPause = 1 << 1, 71 | Report = 1 << 2, 72 | XAFilter = 1 << 3, 73 | Ignore = 1 << 4, 74 | FullSector = 1 << 5, 75 | XAADPCMOn = 1 << 6, 76 | Speed = 1 << 7, 77 | }; 78 | 79 | enum class Status { 80 | Error = 1 << 0, 81 | MotorOn = 1 << 1, 82 | SeekError = 1 << 2, 83 | IDError = 1 << 3, 84 | ShellOpen = 1 << 4, 85 | Read = 1 << 5, 86 | Seek = 1 << 6, 87 | Play = 1 << 7, 88 | }; 89 | 90 | std::ifstream file; 91 | 92 | u8 mode, stat; 93 | u8 iEnable, iFlags; // Interrupt registers 94 | 95 | u8 index; // CDROM register index 96 | 97 | u8 cmd; // Current CDROM command 98 | 99 | std::queue paramFIFO, responseFIFO; 100 | std::queue queuedResp, lateResp; 101 | 102 | int queuedIRQ = 0; 103 | bool oldCmdWasSeekL = true; 104 | 105 | SeekParam seekParam; 106 | 107 | u8 readBuf[SECTOR_SIZE]; 108 | int readIdx; 109 | 110 | u64 seekTarget; 111 | 112 | u64 idSendIRQ; // Scheduler 113 | 114 | void readSector(); 115 | void loadResponse(); 116 | void pushResponse(u8); 117 | 118 | u8 getData8(); 119 | 120 | /* BCD to char conversion */ 121 | inline u32 toChar(u8 bcd) { 122 | assert(((bcd & 0xF0) <= 0x90) && ((bcd & 0xF) <= 9)); 123 | 124 | return (bcd / 16) * 10 + (bcd % 16); 125 | } 126 | 127 | void sendIRQEvent(int irq) { 128 | if (iFlags) { 129 | assert(!queuedIRQ); 130 | 131 | //std::printf("[CDROM ] Queueing INT%d\n", irq); 132 | 133 | queuedIRQ = irq; 134 | 135 | return; 136 | } 137 | 138 | //std::printf("[CDROM ] INT%d\n", irq); 139 | 140 | iFlags = (u8)irq; 141 | 142 | if (iEnable & iFlags) intc::sendInterrupt(Interrupt::CDROM); 143 | 144 | loadResponse(); 145 | 146 | /* If this is an INT1, read sector and send new INT1 */ 147 | if (irq == 1) { 148 | // Send status 149 | pushResponse(stat | static_cast(Status::Read)); 150 | 151 | readSector(); 152 | 153 | if (mode & static_cast(Mode::Speed)) { 154 | scheduler::addEvent(idSendIRQ, 1, READ_TIME_DOUBLE); 155 | } else { 156 | scheduler::addEvent(idSendIRQ, 1, READ_TIME_SINGLE); 157 | } 158 | } 159 | } 160 | 161 | void readSector() { 162 | auto &s = seekParam; 163 | 164 | /* Calculate seek target (in sectors) */ 165 | const auto mm = toChar(s.mins) * 60 * 75; // 1min = 60sec 166 | const auto ss = toChar(s.secs) * 75; // 1min = 75 sectors 167 | const auto sect = toChar(s.sector); 168 | 169 | seekTarget = mm + ss + sect - 150; // Starts at 2s, subtract 150 sectors to get start 170 | 171 | //std::printf("[CDROM ] Seeking to [%02X:%02X:%02X] = %llu\n", s.mins, s.secs, s.sector, seekTarget); 172 | 173 | file.seekg(seekTarget * SECTOR_SIZE, std::ios_base::beg); 174 | 175 | file.read((char *)readBuf, SECTOR_SIZE); 176 | 177 | readIdx = (mode & static_cast(Mode::FullSector)) ? 0x0C : 0x18; 178 | 179 | s.sector++; 180 | 181 | /* Increment BCD values */ 182 | if ((s.sector & 0xF) == 10) { s.sector += 0x10; s.sector &= 0xF0; } 183 | 184 | if (s.sector == 0x75) { s.secs++; s.sector = 0; } 185 | 186 | if ((s.secs & 0xF) == 10) { s.secs += 0x10; s.secs &= 0xF0; } 187 | 188 | if (s.secs == 0x60) { s.mins++; s.secs = 0; } 189 | 190 | if ((s.mins & 0xF) == 10) { s.mins += 0x10; s.mins &= 0xF0; } 191 | 192 | //std::printf("[CDROM ] Next seek to [%02X:%02X:%02X]\n", s.mins, s.secs, s.sector); 193 | } 194 | 195 | u8 readResponse() { 196 | assert(!responseFIFO.empty()); 197 | 198 | const auto data = responseFIFO.front(); responseFIFO.pop(); 199 | 200 | return data; 201 | } 202 | 203 | void pushResponse(u8 data) { 204 | queuedResp.push(data); 205 | } 206 | 207 | void pushLateResponse(u8 data) { 208 | lateResp.push(data); 209 | } 210 | 211 | void clearParameters() { 212 | while (!paramFIFO.empty()) paramFIFO.pop(); 213 | } 214 | 215 | void clearResponse() { 216 | while (!responseFIFO.empty()) responseFIFO.pop(); 217 | 218 | while (!queuedResp.empty()) queuedResp.pop(); 219 | 220 | while (!lateResp.empty()) lateResp.pop(); 221 | } 222 | 223 | void clearParameters(); 224 | 225 | void loadResponse() { 226 | while (!queuedResp.empty()) { 227 | responseFIFO.push(queuedResp.front()); 228 | 229 | queuedResp.pop(); 230 | } 231 | 232 | while (!lateResp.empty()) { 233 | queuedResp.push(lateResp.front()); 234 | 235 | lateResp.pop(); 236 | } 237 | } 238 | 239 | /* Get BIOS Date */ 240 | void cmdGetBIOSDate() { 241 | //std::printf("[CDROM ] Get BIOS Date\n"); 242 | 243 | // Send date 244 | pushResponse(0x96); 245 | pushResponse(0x09); 246 | pushResponse(0x12); 247 | pushResponse(0xC2); 248 | 249 | // Send INT3 250 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 251 | } 252 | 253 | /* Get ID */ 254 | void cmdGetID() { 255 | //std::printf("[CDROM ] Get ID\n"); 256 | 257 | if (paramFIFO.size()) { 258 | /* Too few/many parameters, send error */ 259 | clearParameters(); 260 | 261 | pushResponse(stat | static_cast(Status::Error)); 262 | pushResponse(0x20); 263 | 264 | return scheduler::addEvent(idSendIRQ, 5, INT3_TIME); 265 | } 266 | 267 | // Send status 268 | pushResponse(stat); 269 | 270 | // Send INT3 271 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 272 | 273 | /* Licensed, Mode2 */ 274 | pushLateResponse(0x02); 275 | pushLateResponse(0x00); 276 | pushLateResponse(0x20); 277 | pushLateResponse(0x00); 278 | 279 | pushLateResponse('M'); 280 | pushLateResponse('A'); 281 | pushLateResponse('R'); 282 | pushLateResponse('I'); 283 | 284 | // Send INT2 285 | scheduler::addEvent(idSendIRQ, 2, INT3_TIME + 30000); 286 | } 287 | 288 | /* Get Loc L - Returns position from header */ 289 | void cmdGetLocL() { 290 | //std::printf("[CDROM ] Get Loc L\n"); 291 | 292 | if (!oldCmdWasSeekL) { 293 | /* Can't execute data mode GetLoc after audio seek, send error */ 294 | clearParameters(); 295 | 296 | pushResponse(stat | static_cast(Status::Error)); 297 | pushResponse(0x80); 298 | 299 | return scheduler::addEvent(idSendIRQ, 5, INT3_TIME); 300 | } 301 | 302 | oldCmdWasSeekL = false; 303 | 304 | char buf[8]; 305 | 306 | file.seekg(seekTarget * SECTOR_SIZE + 12, std::ios_base::beg); 307 | file.read(buf, 8); 308 | 309 | // Send information 310 | for (int i = 0; i < 8; i++) pushResponse(buf[i]); 311 | 312 | // Send INT3 313 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 314 | } 315 | 316 | /* Get Loc P - Returns position from subchannel Q */ 317 | void cmdGetLocP() { 318 | //std::printf("[CDROM ] Get Loc P\n"); 319 | 320 | // Send information 321 | pushResponse(0x01); 322 | pushResponse(0x01); 323 | pushResponse(seekParam.mins); 324 | pushResponse(seekParam.secs); 325 | pushResponse(seekParam.sector); 326 | pushResponse(seekParam.mins); 327 | pushResponse(seekParam.secs); 328 | pushResponse(seekParam.sector); 329 | 330 | // Send INT3 331 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 332 | } 333 | 334 | /* Get Stat - Activate motor, set mode = 0x20, abort all commands */ 335 | void cmdGetStat() { 336 | //std::printf("[CDROM ] Get Stat\n"); 337 | 338 | if (paramFIFO.size()) { 339 | /* Too many parameters, send error */ 340 | clearParameters(); 341 | 342 | pushResponse(stat | static_cast(Status::Error)); 343 | pushResponse(0x20); 344 | 345 | return scheduler::addEvent(idSendIRQ, 5, INT3_TIME); 346 | } 347 | 348 | // Send status 349 | pushResponse(stat); 350 | 351 | // Clear shell open flag 352 | stat &= ~static_cast(Status::ShellOpen); 353 | 354 | // Send INT3 355 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 356 | } 357 | 358 | /* Get TD - Returns track start */ 359 | void cmdGetTD() { 360 | //std::printf("[CDROM ] Get TD\n"); 361 | 362 | if (paramFIFO.size() != 1) { 363 | /* Too few/many parameters, send error */ 364 | clearParameters(); 365 | 366 | pushResponse(stat | static_cast(Status::Error)); 367 | pushResponse(0x20); 368 | 369 | return scheduler::addEvent(idSendIRQ, 5, INT3_TIME); 370 | } 371 | 372 | const auto track = paramFIFO.front(); paramFIFO.pop(); 373 | 374 | if (track > 0x26) { 375 | /* Too few/many parameters, send error */ 376 | clearParameters(); 377 | 378 | pushResponse(stat | static_cast(Status::Error)); 379 | pushResponse(0x10); 380 | 381 | return scheduler::addEvent(idSendIRQ, 5, INT3_TIME); 382 | } 383 | 384 | // Send status 385 | pushResponse(stat); 386 | pushResponse(0); 387 | pushResponse(0); 388 | 389 | // Send INT3 390 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 391 | } 392 | 393 | /* Get TN - Returns first and last track number */ 394 | void cmdGetTN() { 395 | //std::printf("[CDROM ] Get TN\n"); 396 | 397 | if (paramFIFO.size()) { 398 | /* Too many parameters, send error */ 399 | clearParameters(); 400 | 401 | pushResponse(stat | static_cast(Status::Error)); 402 | pushResponse(0x20); 403 | 404 | return scheduler::addEvent(idSendIRQ, 5, INT3_TIME); 405 | } 406 | 407 | // Send status 408 | pushResponse(stat); 409 | pushResponse(0x01); 410 | pushResponse(0x01); 411 | 412 | // Send INT3 413 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 414 | } 415 | 416 | /* Read TOC - Reread TOC */ 417 | void cmdReadTOC() { 418 | //std::printf("[CDROM ] Read TOC\n"); 419 | 420 | // Send status 421 | pushResponse(stat); 422 | 423 | // Send status 424 | pushLateResponse(stat | static_cast(Status::Read)); 425 | 426 | // Send INT3 427 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 428 | 429 | // Send INT2 430 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME + 20000); 431 | } 432 | 433 | /* Init - Activate motor, set mode = 0x20, abort all commands */ 434 | void cmdInit() { 435 | //std::printf("[CDROM ] Init\n"); 436 | 437 | if (paramFIFO.size()) { 438 | /* Too many parameters, send error */ 439 | clearParameters(); 440 | 441 | pushResponse(stat | static_cast(Status::Error)); 442 | pushResponse(0x20); 443 | 444 | return scheduler::addEvent(idSendIRQ, 5, INT3_TIME); 445 | } 446 | 447 | stat = static_cast(Status::MotorOn); 448 | 449 | // Send mode 450 | pushResponse(stat); 451 | 452 | // Send INT3 453 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 454 | 455 | mode = static_cast(Mode::FullSector); 456 | 457 | // Send mode 458 | pushLateResponse(stat); 459 | 460 | // Send INT2 461 | scheduler::addEvent(idSendIRQ, 2, INT3_TIME + 120 * _1MS); 462 | } 463 | 464 | /* Pause */ 465 | void cmdPause() { 466 | //std::printf("[CDROM ] Pause\n"); 467 | 468 | scheduler::removeEvent(idSendIRQ); // Kill all pending CDROM events 469 | 470 | /* Clear response buffer(s) */ 471 | clearResponse(); 472 | 473 | // Send status 474 | pushResponse(stat); 475 | 476 | // Send INT3 and INT2 477 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 478 | scheduler::addEvent(idSendIRQ, 2, INT3_TIME + 70 * _1MS - 35 * _1MS * !!(mode & static_cast(Mode::Speed))); 479 | 480 | stat &= ~static_cast(Status::Read); 481 | 482 | // Send status 483 | pushLateResponse(stat); 484 | } 485 | 486 | /* ReadN - Read sector */ 487 | void cmdReadN() { 488 | //std::printf("[CDROM ] ReadN\n"); 489 | 490 | // Send status 491 | pushResponse(stat); 492 | 493 | // Send INT3 494 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 495 | 496 | const auto int1Time = INT3_TIME + (mode & static_cast(Mode::Speed)) ? READ_TIME_DOUBLE : READ_TIME_SINGLE; 497 | 498 | scheduler::addEvent(idSendIRQ, 1, int1Time); 499 | 500 | stat |= static_cast(Status::Read); 501 | 502 | pushLateResponse(stat); 503 | } 504 | 505 | /* SeekL - Data mode seek */ 506 | void cmdSeekL() { 507 | //std::printf("[CDROM ] SeekL\n"); 508 | 509 | // Send status 510 | pushResponse(stat); 511 | 512 | // Send INT3 513 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 514 | 515 | // Send status 516 | pushLateResponse(stat | static_cast(Status::Seek)); 517 | 518 | // Send INT2 519 | scheduler::addEvent(idSendIRQ, 2, INT3_TIME + 2 * _1MS); 520 | } 521 | 522 | /* Set Filter - Sets XA filter */ 523 | void cmdSetFilter() { 524 | //std::printf("[CDROM ] Set Filter\n"); 525 | 526 | paramFIFO.pop(); paramFIFO.pop(); 527 | 528 | // Send status 529 | pushResponse(stat); 530 | 531 | // Send INT3 532 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 533 | } 534 | 535 | /* Set Loc - Sets seek parameters */ 536 | void cmdSetLoc() { 537 | //std::printf("[CDROM ] Set Loc\n"); 538 | 539 | // Send status 540 | pushResponse(stat); 541 | 542 | /* Set minutes, seconds, sector */ 543 | seekParam.mins = paramFIFO.front(); paramFIFO.pop(); 544 | seekParam.secs = paramFIFO.front(); paramFIFO.pop(); 545 | seekParam.sector = paramFIFO.front(); paramFIFO.pop(); 546 | 547 | // Send INT3 548 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 549 | } 550 | 551 | /* Set Mode - Sets CDROM mode */ 552 | void cmdSetMode() { 553 | //std::printf("[CDROM ] Set Mode\n"); 554 | 555 | // Send status 556 | pushResponse(stat); 557 | 558 | mode = paramFIFO.front(); paramFIFO.pop(); 559 | 560 | // Send INT3 561 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 562 | } 563 | 564 | /* Stop */ 565 | void cmdStop() { 566 | //std::printf("[CDROM ] Stop\n"); 567 | 568 | scheduler::removeEvent(idSendIRQ); // Kill all pending CDROM events 569 | 570 | /* Clear response buffer(s) */ 571 | clearResponse(); 572 | 573 | // Send status 574 | pushResponse(stat); 575 | 576 | // Send INT3 577 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 578 | scheduler::addEvent(idSendIRQ, 2, CPU_SPEED); 579 | 580 | stat &= ~static_cast(Status::MotorOn); 581 | 582 | stat &= ~static_cast(Status::Read); 583 | 584 | // Send status 585 | pushLateResponse(stat); 586 | } 587 | 588 | /* Unmute */ 589 | void cmdUnmute() { 590 | //std::printf("[CDROM ] Unmute\n"); 591 | 592 | // Send status 593 | pushResponse(stat); 594 | 595 | // Send INT3 596 | scheduler::addEvent(idSendIRQ, 3, INT3_TIME); 597 | } 598 | 599 | /* Handles sub commands */ 600 | void doSubCmd() { 601 | const auto cmd = paramFIFO.front(); paramFIFO.pop(); 602 | 603 | switch (cmd) { 604 | case SubCommand::GetBIOSDate: cmdGetBIOSDate(); break; 605 | default: 606 | //std::printf("[CDROM ] Unhandled sub command 0x%02X\n", cmd); 607 | 608 | exit(0); 609 | } 610 | } 611 | 612 | /* Handles CDROM commands */ 613 | void doCmd(u8 data) { 614 | cmd = data; 615 | 616 | switch (cmd) { 617 | case Command::GetStat : cmdGetStat(); break; 618 | case Command::SetLoc : cmdSetLoc(); break; 619 | case Command::ReadN : cmdReadN(); break; 620 | case Command::Stop : cmdStop(); break; 621 | case Command::Pause : cmdPause(); break; 622 | case Command::Init : cmdInit(); break; 623 | case Command::Unmute : cmdUnmute(); break; 624 | case Command::SetFilter: cmdSetFilter(); break; 625 | case Command::SetMode : cmdSetMode(); break; 626 | case Command::GetLocL : cmdGetLocL(); break; 627 | case Command::GetLocP : cmdGetLocP(); break; 628 | case Command::GetTN : cmdGetTN(); break; 629 | case Command::GetTD : cmdGetTD(); break; 630 | case Command::SeekL : oldCmdWasSeekL = true; cmdSeekL(); break; 631 | case Command::SeekP : oldCmdWasSeekL = false; cmdSeekL(); break; // Should be fine? 632 | case Command::Test : doSubCmd(); break; 633 | case Command::GetID : cmdGetID(); break; 634 | case Command::ReadS : cmdReadN(); break; // Should be fine? 635 | case Command::ReadTOC : cmdReadTOC(); break; 636 | default: 637 | //std::printf("[CDROM ] Unhandled command 0x%02X\n", cmd); 638 | 639 | exit(0); 640 | } 641 | } 642 | 643 | void init(const char *isoPath) { 644 | // Open file 645 | file.open(isoPath, std::ios::in | std::ios::binary); 646 | 647 | if (!file.is_open()) { 648 | //std::printf("[CDROM ] Unable to open file \"%s\"\n", isoPath); 649 | 650 | exit(0); 651 | } 652 | 653 | file.unsetf(std::ios::skipws); 654 | 655 | /* Register scheduler events */ 656 | idSendIRQ = scheduler::registerEvent([](int irq, i64) { sendIRQEvent(irq); }); 657 | } 658 | 659 | u8 read(u32 addr) { 660 | switch (addr) { 661 | case 0x1F801800: 662 | { 663 | ////std::printf("[CDROM ] 8-bit read @ STATUS\n"); 664 | 665 | u8 data = index; 666 | 667 | data |= paramFIFO.empty() << 3; // Parameter FIFO empty 668 | data |= (paramFIFO.size() != 16) << 4; // Parameter FIFO not full 669 | data |= !responseFIFO.empty() << 5; // Response FIFO not empty 670 | data |= (readIdx && (readIdx < READ_SIZE)) << 6; // Data FIFO not empty 671 | 672 | return data; 673 | } 674 | case 0x1F801801: 675 | { 676 | //std::printf("[CDROM ] 8-bit read @ RESPONSE\n"); 677 | 678 | return readResponse(); 679 | } 680 | case 0x1F801802: 681 | { 682 | const auto data = getData8(); 683 | 684 | //std::printf("[CDROM ] 8-bit read @ DATA = 0x%02X\n", data); 685 | 686 | return data; 687 | } 688 | case 0x1F801803: 689 | switch (index) { 690 | case 0: 691 | //std::printf("[CDROM ] 8-bit read @ IE\n"); 692 | return iEnable; 693 | case 1: 694 | ////std::printf("[CDROM ] 8-bit read @ IF = 0x%02X\n", iFlags); 695 | 696 | return iFlags | 0xE0; 697 | default: 698 | //std::printf("[CDROM ] Unhandled 8-bit read @ 0x%08X.%u\n", addr, index); 699 | 700 | exit(0); 701 | } 702 | break; 703 | default: 704 | //std::printf("[CDROM ] Unhandled 8-bit read @ 0x%08X\n", addr); 705 | 706 | exit(0); 707 | } 708 | } 709 | 710 | void write(u32 addr, u8 data) { 711 | switch (addr) { 712 | case 0x1F801800: 713 | ////std::printf("[CDROM ] 8-bit write @ INDEX = 0x%02X\n", data); 714 | 715 | index = data & 3; 716 | break; 717 | case 0x1F801801: 718 | switch (index) { 719 | case 0: 720 | //std::printf("[CDROM ] 8-bit write @ CMD = 0x%02X\n", data); 721 | 722 | doCmd(data); 723 | break; 724 | case 3: 725 | //std::printf("[CDROM ] 8-bit write @ VOLR->L = 0x%02X\n", data); 726 | break; 727 | default: 728 | //std::printf("[CDROM ] Unhandled 8-bit write @ 0x%08X.%u = 0x%02X\n", addr, index, data); 729 | 730 | exit(0); 731 | } 732 | break; 733 | case 0x1F801802: 734 | switch (index) { 735 | case 0: 736 | //std::printf("[CDROM ] 8-bit write @ PARAM = 0x%02X\n", data); 737 | 738 | assert(paramFIFO.size() < 16); 739 | 740 | paramFIFO.push(data); 741 | break; 742 | case 1: 743 | //std::printf("[CDROM ] 8-bit write @ IE = 0x%02X\n", data); 744 | 745 | iEnable = data & 0x1F; 746 | break; 747 | case 2: 748 | //std::printf("[CDROM ] 8-bit write @ VOLL->L = 0x%02X\n", data); 749 | break; 750 | case 3: 751 | //std::printf("[CDROM ] 8-bit write @ VOLR->R = 0x%02X\n", data); 752 | break; 753 | default: 754 | //std::printf("[CDROM ] Unhandled 8-bit write @ 0x%08X.%u = 0x%02X\n", addr, index, data); 755 | 756 | exit(0); 757 | } 758 | break; 759 | case 0x1F801803: 760 | switch (index) { 761 | case 0: 762 | //std::printf("[CDROM ] 8-bit write @ REQUEST = 0x%02X\n", data); 763 | 764 | assert(!(data & (1 << 5))); 765 | break; 766 | case 1: 767 | //std::printf("[CDROM ] 8-bit write @ IF = 0x%02X\n", data); 768 | 769 | iFlags &= (~data & 0x1F); 770 | 771 | if (!iFlags && queuedIRQ) { 772 | // Send queued INT 773 | scheduler::addEvent(idSendIRQ, queuedIRQ, _1MS); 774 | 775 | queuedIRQ = 0; 776 | } 777 | break; 778 | case 2: 779 | //std::printf("[CDROM ] 8-bit write @ VOLL->R = 0x%02X\n", data); 780 | break; 781 | case 3: 782 | //std::printf("[CDROM ] 8-bit write @ APPLYVOL = 0x%02X\n", data); 783 | break; 784 | default: 785 | //std::printf("[CDROM ] Unhandled 8-bit write @ 0x%08X.%u = 0x%02X\n", addr, index, data); 786 | 787 | exit(0); 788 | } 789 | break; 790 | default: 791 | //std::printf("[CDROM ] Unhandled 8-bit write @ 0x%08X = 0x%02X\n", addr, data); 792 | 793 | exit(0); 794 | } 795 | } 796 | 797 | u8 getData8() { 798 | assert(readIdx && (readIdx < READ_SIZE)); 799 | 800 | const auto data = readBuf[readIdx++]; 801 | 802 | if (readIdx == READ_SIZE) readIdx = 0; 803 | 804 | return data; 805 | } 806 | 807 | u32 getData32() { 808 | assert(readIdx && (readIdx < READ_SIZE)); 809 | 810 | u32 data; 811 | 812 | std::memcpy(&data, &readBuf[readIdx], 4); 813 | 814 | readIdx += 4; 815 | 816 | if (readIdx == READ_SIZE) readIdx = 0; 817 | 818 | return data; 819 | } 820 | 821 | } 822 | -------------------------------------------------------------------------------- /src/core/spu/spu.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "spu.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "gauss.hpp" 16 | 17 | #include "../intc.hpp" 18 | #include "../scheduler.hpp" 19 | 20 | namespace ps::spu { 21 | 22 | /* --- SPU constants --- */ 23 | 24 | constexpr i64 SPU_RATE = 0x300; 25 | 26 | constexpr u32 SPU_BASE = 0x1F801C00; 27 | constexpr u32 RAM_SIZE = 0x80000; 28 | 29 | static const i32 POS_XA_ADPCM_TABLE[] = { 0, 60, 115, 98, 112 }; 30 | static const i32 NEG_XA_ADPCM_TABLE[] = { 0, 0, -52, -55, -60 }; 31 | 32 | static const i32 INC_TABLE[] = { 7, 6, 5, 4 }; 33 | static const i32 DEC_TABLE[] = { -8, -7, -6, -5 }; 34 | 35 | /* --- SPU registers --- */ 36 | 37 | enum class SPUReg { 38 | VOLL = 0x1F801C00, 39 | VOLR = 0x1F801C02, 40 | PITCH = 0x1F801C04, 41 | ADDR = 0x1F801C06, 42 | ADSR = 0x1F801C08, 43 | ADSRVOL = 0x1F801C0C, 44 | LOOP = 0x1F801C0E, 45 | MVOLL = 0x1F801D80, 46 | MVOLR = 0x1F801D82, 47 | VLOUT = 0x1F801D84, 48 | VROUT = 0x1F801D86, 49 | KON = 0x1F801D88, 50 | KOFF = 0x1F801D8C, 51 | PMON = 0x1F801D90, 52 | NON = 0x1F801D94, 53 | REVON = 0x1F801D98, 54 | VON = 0x1F801D9C, 55 | REVADDR = 0x1F801DA2, 56 | SPUADDR = 0x1F801DA6, 57 | SPUDATA = 0x1F801DA8, 58 | SPUCNT = 0x1F801DAA, 59 | FIFOCNT = 0x1F801DAC, 60 | SPUSTAT = 0x1F801DAE, 61 | CDVOLL = 0x1F801DB0, 62 | CDVOLR = 0x1F801DB2, 63 | EVOLL = 0x1F801DB4, 64 | EVOLR = 0x1F801DB6, 65 | CVOLL = 0x1F801DB8, 66 | CVOLR = 0x1F801DBA, 67 | }; 68 | 69 | /* SPU control */ 70 | struct SPUCNT { 71 | bool cden; // CD audio enable 72 | bool exten; // External audio enable 73 | bool cdrev; // CD reverb enable 74 | bool extrev; // External reverb enable 75 | u8 trxmod; // Transfer mode 76 | bool irqen; // IRQ enable 77 | bool reven; // Reverb master enable 78 | u8 nstep; // Noise step 79 | u8 nshift; // Noise shift 80 | bool unmute; // (un)mute SPU 81 | bool spuen; // SPU enable 82 | }; 83 | 84 | /* SPU status */ 85 | struct SPUSTAT { 86 | u8 spumod; // SPU mode 87 | bool irq9; // IRQ flag 88 | bool dmard; // DMA read (0 = write) 89 | bool wrreq; // DMA write request 90 | bool rdreq; // DMA read request 91 | bool busy; // SPU busy 92 | bool cbuf; // Current capture buffer 93 | }; 94 | 95 | enum ADSR { 96 | Off, 97 | Attack, 98 | Decay, 99 | Sustain, 100 | Release, 101 | }; 102 | 103 | struct Voice { 104 | bool on; // Set by KON 105 | 106 | ADSR adsr; 107 | 108 | bool amode, dmode, smode, rmode; 109 | 110 | bool adir, ddir, sdir, rdir; 111 | 112 | i32 ashift, dshift, sshift, rshift; 113 | i32 astep, dstep, sstep, rstep; 114 | 115 | i32 slevel; 116 | 117 | i32 adsrvol; 118 | 119 | int adsrCounter, adsrStep; 120 | 121 | i16 voll, volr; 122 | 123 | u16 pitch; 124 | u32 pitchCounter; 125 | 126 | u32 addr, loopaddr, caddr; 127 | 128 | u8 adpcmBlock[16]; 129 | bool hasBlock; 130 | 131 | int shift, filter; 132 | 133 | i16 s[4]; // Most recent samples 134 | }; 135 | 136 | std::vector ram; 137 | 138 | i16 sound[2 * 2048]; 139 | int soundIdx = 0; 140 | 141 | SPUCNT spucnt; 142 | SPUSTAT spustat; 143 | 144 | u32 kon, koff; 145 | 146 | u32 spuaddr, caddr; // SPU address, current address 147 | 148 | Voice voices[24]; 149 | 150 | i16 mvoll, mvolr; 151 | 152 | u64 idStep; 153 | 154 | /* Returns true if address is in range [base;size] */ 155 | bool inRange(u64 addr, u64 base, u64 size) { 156 | return (addr >= base) && (addr < (base + size)); 157 | } 158 | 159 | i32 clamp16(i32 a) { 160 | if (a > 0x7FFF) return 0x7FFF; 161 | 162 | return (a < 0) ? 0 : a; 163 | } 164 | 165 | i32 clamp16S(i32 a) { 166 | if (a > 0x7FFF) return 0x7FFF; 167 | 168 | return (a < -0x8000) ? -0x8000 : a; 169 | } 170 | 171 | /* Start ADSR in attack phase */ 172 | void startADSR(int vID) { 173 | auto &v = voices[vID]; 174 | 175 | v.adsr = ADSR::Attack; 176 | 177 | v.adsrCounter = 1 << std::max(0, v.ashift - 11); 178 | v.adsrStep = INC_TABLE[v.astep] << std::max(0, 11 - v.ashift); 179 | 180 | if (v.amode && (v.adsrvol > 0x6000)) { 181 | v.adsrCounter *= 4; 182 | } 183 | } 184 | 185 | void doRelease(int vID) { 186 | auto &v = voices[vID]; 187 | 188 | v.adsr = ADSR::Release; 189 | 190 | v.adsrCounter = 1 << std::max(0, v.rshift - 11); 191 | v.adsrStep = -8 << std::max(0, 11 - v.rshift); 192 | 193 | if (v.rmode) v.adsrStep = (v.adsrStep * v.adsrvol) / 0x8000; 194 | } 195 | 196 | void stepADSR(int vID) { 197 | auto &v = voices[vID]; 198 | 199 | assert(v.adsr != ADSR::Off); 200 | 201 | if (--v.adsrCounter) return; 202 | 203 | v.adsrvol = clamp16(v.adsrvol + v.adsrStep); 204 | 205 | switch (v.adsr) { 206 | case ADSR::Attack: 207 | { 208 | if (v.adsrvol == 0x7FFF) { 209 | v.adsr = ADSR::Decay; 210 | 211 | v.adsrCounter = 1 << std::max(0, v.dshift - 11); 212 | v.adsrStep = -8 << std::max(0, 11 - v.dshift); 213 | v.adsrStep = (v.adsrStep * v.adsrvol) / 0x8000; 214 | } else { 215 | v.adsrCounter = 1 << std::max(0, v.ashift - 11); 216 | 217 | if (v.amode && (v.adsrvol > 0x6000)) { 218 | v.adsrCounter *= 4; 219 | } 220 | } 221 | } 222 | break; 223 | case ADSR::Decay: 224 | { 225 | if (v.adsrvol <= v.slevel) { 226 | v.adsr = ADSR::Sustain; 227 | 228 | v.adsrCounter = 1 << std::max(0, v.sshift - 11); 229 | v.adsrStep = ((v.sdir) ? DEC_TABLE[v.sstep] : INC_TABLE[v.sstep]) << std::max(0, 11 - v.sshift); 230 | 231 | if (v.smode) { 232 | if (v.sdir) v.adsrStep = (v.adsrStep * v.adsrvol) / 0x8000; 233 | if (!v.sdir && (v.adsrvol > 0x6000)) v.adsrCounter *= 4; 234 | } 235 | } else { 236 | v.adsrCounter = 1 << std::max(0, v.dshift - 11); 237 | v.adsrStep = -8 << std::max(0, 11 - v.dshift); 238 | v.adsrStep = (v.adsrStep * v.adsrvol) / 0x8000; 239 | } 240 | } 241 | break; 242 | case ADSR::Sustain: 243 | { 244 | v.adsrCounter = 1 << std::max(0, v.sshift - 11); 245 | v.adsrStep = ((v.sdir) ? DEC_TABLE[v.sstep] : INC_TABLE[v.sstep]) << std::max(0, 11 - v.sshift); 246 | 247 | if (v.smode) { 248 | if (v.sdir) v.adsrStep = (v.adsrStep * v.adsrvol) / 0x8000; 249 | if (!v.sdir && (v.adsrvol > 0x6000)) v.adsrCounter *= 4; 250 | } 251 | } 252 | break; 253 | case ADSR::Release: 254 | { 255 | if (!v.adsrvol) { 256 | v.adsr = ADSR::Off; 257 | 258 | v.on = false; 259 | } else { 260 | v.adsrCounter = 1 << std::max(0, v.rshift - 11); 261 | v.adsrStep = -8 << std::max(0, 11 - v.rshift); 262 | 263 | if (v.rmode) v.adsrStep = (v.adsrStep * v.adsrvol) / 0x8000; 264 | } 265 | } 266 | break; 267 | default: 268 | assert(false); 269 | } 270 | } 271 | 272 | /* Steps the SPU, calculates current sample */ 273 | void step() { 274 | i32 sl = 0; 275 | i32 sr = 0; 276 | 277 | if (spucnt.spuen && spucnt.unmute) { 278 | for (int i = 0; i < 24; i++) { 279 | auto &v = voices[i]; 280 | 281 | if (!v.on || !v.pitch) continue; 282 | 283 | if (!v.hasBlock) { 284 | /* Load new ADPCM block */ 285 | 286 | std::memcpy(v.adpcmBlock, &ram[v.caddr], 16); 287 | 288 | v.caddr += 16; 289 | 290 | v.shift = v.adpcmBlock[0] & 0xF; 291 | v.filter = (v.adpcmBlock[0] >> 4) & 7; 292 | 293 | if (v.shift > 12) v.shift = 9; 294 | 295 | assert(v.filter < 5); 296 | 297 | v.hasBlock = true; 298 | } 299 | 300 | const auto adpcmIdx = v.pitchCounter >> 12; 301 | 302 | /* Fetch new ADPCM sample */ 303 | 304 | for (int j = 0; j < 3; j++) v.s[j] = v.s[j + 1]; // Move old samples 305 | 306 | u8 nibble = (v.adpcmBlock[2 + (adpcmIdx >> 1)] >> (4 * (adpcmIdx & 1))) & 0xF; 307 | 308 | i32 s3 = (i32)(i16)(nibble << 12) >> v.shift; 309 | 310 | /* Apply filter */ 311 | 312 | const auto f0 = POS_XA_ADPCM_TABLE[v.filter]; 313 | const auto f1 = NEG_XA_ADPCM_TABLE[v.filter]; 314 | 315 | s3 += (f0 * v.s[2] + f1 * v.s[1] + 32) / 64; 316 | 317 | v.s[3] = clamp16S(s3); 318 | 319 | const auto s = (i32)gauss::interpolate(v.pitchCounter >> 3, v.s[0], v.s[1], v.s[2], v.s[3]); 320 | 321 | stepADSR(i); 322 | 323 | sl += (((s * v.voll) >> 15) * v.adsrvol) >> 15; 324 | sr += (((s * v.volr) >> 15) * v.adsrvol) >> 15; 325 | 326 | /* Increment pitch counter */ 327 | /* TODO: handle PMON */ 328 | 329 | u32 step = v.pitch; 330 | 331 | if (step > 0x3FFF) step = 0x4000; 332 | 333 | v.pitchCounter += step; 334 | 335 | if ((v.pitchCounter >> 12) >= 28) { 336 | const auto flags = v.adpcmBlock[1]; 337 | 338 | if (flags & (1 << 2)) { 339 | v.loopaddr = v.caddr; 340 | } 341 | 342 | switch (flags & 3) { 343 | case 0: case 2: 344 | break; 345 | case 1: // Release and force mute 346 | doRelease(i); 347 | 348 | v.adsrvol = 0; 349 | 350 | v.caddr = v.loopaddr; 351 | break; 352 | case 3: // Release 353 | doRelease(i); 354 | 355 | v.caddr = v.loopaddr; 356 | break; 357 | } 358 | 359 | v.pitchCounter = 0; 360 | 361 | v.hasBlock = false; 362 | } 363 | } 364 | } 365 | 366 | sound[2 * soundIdx + 0] = (sl * mvoll) >> 15; 367 | sound[2 * soundIdx + 1] = (sr * mvolr) >> 15; 368 | 369 | soundIdx++; 370 | 371 | scheduler::addEvent(idStep, 0, SPU_RATE); 372 | } 373 | 374 | /* Handle Key Off event */ 375 | void doKOFF() { 376 | for (int i = 0; i < 24; i++) { 377 | if (koff & (1 << i)) doRelease(i); 378 | } 379 | } 380 | 381 | /* Handle Key On event */ 382 | void doKON() { 383 | for (int i = 0; i < 24; i++) { 384 | auto &v = voices[i]; 385 | 386 | if (kon & (1 << i)) { 387 | /* TODO: set initial volume etc */ 388 | 389 | v.caddr = 8 * v.addr; 390 | 391 | v.loopaddr = v.caddr; 392 | 393 | startADSR(i); 394 | 395 | v.on = true; 396 | } 397 | } 398 | } 399 | 400 | void init() { 401 | const i16 out = 0; 402 | 403 | std::memset(&voices, 0, 24 * sizeof(Voice)); 404 | 405 | /* Clear sound out file */ 406 | std::ofstream file; 407 | 408 | file.open("snd.bin", std::ios::out | std::ios::binary | std::ios::trunc); 409 | 410 | file.write((char *)&out, 2); 411 | 412 | file.close(); 413 | 414 | ram.resize(RAM_SIZE); 415 | 416 | idStep = scheduler::registerEvent([](int, i64) { step(); }); 417 | 418 | scheduler::addEvent(idStep, 0, SPU_RATE); 419 | } 420 | 421 | /* Write audio to file */ 422 | void save() { 423 | std::ofstream file; 424 | 425 | file.open("snd.bin", std::ios::out | std::ios::binary | std::ios::app); 426 | 427 | file.write((char *)sound, 4 * soundIdx); 428 | 429 | file.close(); 430 | 431 | soundIdx = 0; 432 | } 433 | 434 | u16 readRAM(u32 addr) { 435 | assert(addr < RAM_SIZE); 436 | 437 | u16 data; 438 | 439 | std::memcpy(&data, &ram[addr], 2); 440 | 441 | return data; 442 | } 443 | 444 | /* Writes a halfword to SPU RAM */ 445 | void writeRAM(u16 data) { 446 | assert(caddr < RAM_SIZE); 447 | 448 | std::printf("[SPU ] [0x%05X] = 0x%04X\n", caddr, data); 449 | 450 | std::memcpy(&ram[caddr], &data, 2); 451 | 452 | ram[caddr] = data; 453 | 454 | caddr += 2; 455 | } 456 | 457 | u16 read(u32 addr) { 458 | u16 data; 459 | 460 | if (addr < static_cast(SPUReg::MVOLL)) { // SPU voices 461 | const auto vID = (addr >> 4) & 0x1F; 462 | 463 | auto &v = voices[vID]; 464 | 465 | switch (addr & ~(0x1F << 4)) { 466 | case static_cast(SPUReg::VOLL): 467 | std::printf("[SPU ] 16-bit read @ V%u_VOLL\n", vID); 468 | return v.voll >> 1; 469 | case static_cast(SPUReg::VOLR): 470 | std::printf("[SPU ] 16-bit read @ V%u_VOLR\n", vID); 471 | return v.volr >> 1; 472 | case static_cast(SPUReg::PITCH): 473 | std::printf("[SPU ] 16-bit read @ V%u_PITCH\n", vID); 474 | return v.pitch; 475 | case static_cast(SPUReg::ADDR): 476 | std::printf("[SPU ] 16-bit read @ V%u_ADDR\n", vID); 477 | return v.addr; 478 | case static_cast(SPUReg::ADSR): 479 | std::printf("[SPU ] 16-bit read @ V%u_ADSR_LO\n", vID); 480 | break; 481 | case static_cast(SPUReg::ADSR) + 2: 482 | std::printf("[SPU ] 16-bit read @ V%u_ADSR_HI\n", vID); 483 | break; 484 | case static_cast(SPUReg::ADSRVOL): 485 | //std::printf("[SPU ] 16-bit read @ V%u_ADSRVOL\n", vID); 486 | return v.adsrvol; 487 | default: 488 | std::printf("[SPU ] Unhandled 16-bit voice %u read @ 0x%08X\n", vID, addr); 489 | 490 | exit(0); 491 | } 492 | 493 | return 0; 494 | } else if (inRange(addr, SPU_BASE + 0x188, 0x18)) { // Voice control 495 | switch (addr) { 496 | case static_cast(SPUReg::KON): 497 | std::printf("[SPU ] 16-bit read @ KON_LO\n"); 498 | return kon; 499 | case static_cast(SPUReg::KON) + 2: 500 | std::printf("[SPU ] 16-bit read @ KON_HI\n"); 501 | return kon >> 16; 502 | case static_cast(SPUReg::KOFF): 503 | std::printf("[SPU ] 16-bit read @ KOFF_LO\n"); 504 | return koff; 505 | case static_cast(SPUReg::KOFF) + 2: 506 | std::printf("[SPU ] 16-bit read @ KOFF_HI\n"); 507 | return koff >> 16; 508 | case static_cast(SPUReg::NON): 509 | std::printf("[SPU ] 16-bit read @ NON_LO\n"); 510 | return 0; 511 | case static_cast(SPUReg::NON) + 2: 512 | std::printf("[SPU ] 16-bit read @ NON_HI\n"); 513 | return 0; 514 | case static_cast(SPUReg::REVON): 515 | std::printf("[SPU ] 16-bit read @ REVON_LO\n"); 516 | return 0; 517 | case static_cast(SPUReg::REVON) + 2: 518 | std::printf("[SPU ] 16-bit read @ REVON_HI\n"); 519 | return 0; 520 | default: 521 | std::printf("[SPU ] Unhandled 16-bit voice control read @ 0x%08X\n", addr); 522 | 523 | exit(0); 524 | } 525 | } else if (inRange(addr, SPU_BASE + 0x1A2, 0x1E)) { // SPU control 526 | switch (addr) { 527 | case static_cast(SPUReg::SPUADDR): 528 | std::printf("[SPU ] 16-bit read @ SPUADDR\n"); 529 | return spuaddr; 530 | case static_cast(SPUReg::SPUCNT): 531 | std::printf("[SPU ] 16-bit read @ SPUCNT\n"); 532 | 533 | data = spucnt.cden << 0; 534 | data |= spucnt.exten << 1; 535 | data |= spucnt.cdrev << 2; 536 | data |= spucnt.extrev << 3; 537 | data |= spucnt.trxmod << 4; 538 | data |= spucnt.irqen << 6; 539 | data |= spucnt.reven << 7; 540 | data |= spucnt.nstep << 8; 541 | data |= spucnt.nshift << 10; 542 | data |= spucnt.unmute << 14; 543 | data |= spucnt.spuen << 15; 544 | break; 545 | case static_cast(SPUReg::FIFOCNT): 546 | std::printf("[SPU ] 16-bit read @ FIFOCNT\n"); 547 | return 4; 548 | case static_cast(SPUReg::SPUSTAT): 549 | std::printf("[SPU ] 16-bit read @ SPUSTAT\n"); 550 | 551 | data = spustat.spumod; 552 | data |= spustat.irq9 << 6; 553 | data |= spustat.dmard << 7; 554 | data |= spustat.wrreq << 8; 555 | data |= spustat.rdreq << 9; 556 | data |= spustat.busy << 10; 557 | data |= spustat.cbuf << 11; 558 | break; 559 | case static_cast(SPUReg::CVOLL): 560 | std::printf("[SPU ] 16-bit read @ CVOLL\n"); 561 | return 0; 562 | case static_cast(SPUReg::CVOLR): 563 | std::printf("[SPU ] 16-bit read @ CVOLR\n"); 564 | return 0; 565 | default: 566 | std::printf("[SPU ] Unhandled control 16-bit read @ 0x%08X\n", addr); 567 | 568 | exit(0); 569 | } 570 | } else { 571 | std::printf("[SPU ] Unhandled 16-bit read @ 0x%08X\n", addr); 572 | 573 | exit(0); 574 | } 575 | 576 | return data; 577 | } 578 | 579 | void write(u32 addr, u16 data) { 580 | if (addr < static_cast(SPUReg::MVOLL)) { // SPU voices 581 | const auto vID = (addr >> 4) & 0x1F; 582 | 583 | auto &v = voices[vID]; 584 | 585 | switch (addr & ~(0x1F << 4)) { 586 | case static_cast(SPUReg::VOLL): 587 | std::printf("[SPU ] 16-bit write @ V%u_VOLL = 0x%04X\n", vID, data); 588 | 589 | v.voll = data << 1; 590 | break; 591 | case static_cast(SPUReg::VOLR): 592 | std::printf("[SPU ] 16-bit write @ V%u_VOLR = 0x%04X\n", vID, data); 593 | v.volr = data << 1; 594 | break; 595 | case static_cast(SPUReg::PITCH): 596 | std::printf("[SPU ] 16-bit write @ V%u_PITCH = 0x%04X\n", vID, data); 597 | 598 | v.pitch = data; 599 | break; 600 | case static_cast(SPUReg::ADDR): 601 | std::printf("[SPU ] 16-bit write @ V%u_ADDR = 0x%04X\n", vID, data); 602 | 603 | v.addr = data; 604 | break; 605 | case static_cast(SPUReg::ADSR): 606 | std::printf("[SPU ] 16-bit write @ V%u_ADSR_LO = 0x%04X\n", vID, data); 607 | 608 | v.slevel = ((data & 0xF) + 1) * 0x800; 609 | v.dshift = (data >> 4) & 0xF; 610 | v.astep = (data >> 8) & 3; 611 | v.ashift = (data >> 10) & 0x1F; 612 | v.amode = data & (1 << 15); 613 | break; 614 | case static_cast(SPUReg::ADSR) + 2: 615 | std::printf("[SPU ] 16-bit write @ V%u_ADSR_HI = 0x%04X\n", vID, data); 616 | 617 | v.rshift = (data >> 0) & 0x1F; 618 | v.rmode = data & (1 << 5); 619 | v.sstep = (data >> 6) & 3; 620 | v.sshift = (data >> 8) & 0x1F; 621 | v.sdir = data & (1 << 14); 622 | v.smode = data & (1 << 15); 623 | break; 624 | case static_cast(SPUReg::ADSRVOL): 625 | std::printf("[SPU ] 16-bit write @ V%u_ADSRVOL = 0x%04X\n", vID, data); 626 | 627 | v.adsrvol = data; 628 | 629 | if (v.adsrvol < 0) v.adsrvol = 0; 630 | break; 631 | case static_cast(SPUReg::LOOP): 632 | std::printf("[SPU ] 16-bit write @ V%u_LOOP = 0x%04X\n", vID, data); 633 | 634 | v.loopaddr = 8 * data; 635 | break; 636 | default: 637 | std::printf("[SPU ] Unhandled 16-bit voice %u write @ 0x%08X = 0x%04X\n", vID, addr, data); 638 | 639 | exit(0); 640 | } 641 | } else if (inRange(addr, SPU_BASE + 0x180, 8)) { // SPU volume control 642 | switch (addr) { 643 | case static_cast(SPUReg::MVOLL): 644 | std::printf("[SPU ] 16-bit write @ MVOLL = 0x%04X\n", data); 645 | 646 | mvoll = data << 1; 647 | break; 648 | case static_cast(SPUReg::MVOLR): 649 | std::printf("[SPU ] 16-bit write @ MVOLR = 0x%04X\n", data); 650 | 651 | mvolr = data << 1; 652 | break; 653 | case static_cast(SPUReg::VLOUT): 654 | std::printf("[SPU ] 16-bit write @ VLOUT = 0x%04X\n", data); 655 | break; 656 | case static_cast(SPUReg::VROUT): 657 | std::printf("[SPU ] 16-bit write @ VROUT = 0x%04X\n", data); 658 | break; 659 | default: 660 | std::printf("[SPU ] Unhandled 16-bit control write @ 0x%08X = 0x%04X\n", addr, data); 661 | 662 | exit(0); 663 | } 664 | } else if (inRange(addr, SPU_BASE + 0x188, 0x18)) { // Voice control 665 | switch (addr) { 666 | case static_cast(SPUReg::KON): 667 | std::printf("[SPU ] 16-bit write @ KON_LO = 0x%04X\n", data); 668 | 669 | kon = (kon & 0xFFFF0000) | data; 670 | break; 671 | case static_cast(SPUReg::KON) + 2: 672 | std::printf("[SPU ] 16-bit write @ KON_HI = 0x%04X\n", data); 673 | 674 | kon = (kon & 0xFFFF) | (data << 16); 675 | 676 | doKON(); 677 | break; 678 | case static_cast(SPUReg::KOFF): 679 | std::printf("[SPU ] 16-bit write @ KOFF_LO = 0x%04X\n", data); 680 | 681 | koff = (koff & 0xFFFF0000) | data; 682 | break; 683 | case static_cast(SPUReg::KOFF) + 2: 684 | std::printf("[SPU ] 16-bit write @ KOFF_HI = 0x%04X\n", data); 685 | 686 | koff = (koff & 0xFFFF) | (data << 16); 687 | 688 | doKOFF(); 689 | break; 690 | case static_cast(SPUReg::PMON): 691 | std::printf("[SPU ] 16-bit write @ PMON_LO = 0x%04X\n", data); 692 | break; 693 | case static_cast(SPUReg::PMON) + 2: 694 | std::printf("[SPU ] 16-bit write @ PMON_HI = 0x%04X\n", data); 695 | break; 696 | case static_cast(SPUReg::NON): 697 | std::printf("[SPU ] 16-bit write @ NON_LO = 0x%04X\n", data); 698 | break; 699 | case static_cast(SPUReg::NON) + 2: 700 | std::printf("[SPU ] 16-bit write @ NON_HI = 0x%04X\n", data); 701 | break; 702 | case static_cast(SPUReg::REVON): 703 | std::printf("[SPU ] 16-bit write @ REVON_LO = 0x%04X\n", data); 704 | break; 705 | case static_cast(SPUReg::REVON) + 2: 706 | std::printf("[SPU ] 16-bit write @ REVON_HI = 0x%04X\n", data); 707 | break; 708 | case static_cast(SPUReg::VON): 709 | std::printf("[SPU ] 16-bit write @ VON_LO = 0x%04X\n", data); 710 | break; 711 | case static_cast(SPUReg::VON) + 2: 712 | std::printf("[SPU ] 16-bit write @ VON_HI = 0x%04X\n", data); 713 | break; 714 | default: 715 | std::printf("[SPU ] Unhandled 16-bit voice control write @ 0x%08X = 0x%04X\n", addr, data); 716 | 717 | exit(0); 718 | } 719 | } else if (inRange(addr, SPU_BASE + 0x1A2, 0x1E)) { // SPU control 720 | switch (addr) { 721 | case static_cast(SPUReg::REVADDR): 722 | std::printf("[SPU ] 16-bit write @ REVADDR = 0x%04X\n", data); 723 | break; 724 | case static_cast(SPUReg::SPUADDR): 725 | std::printf("[SPU ] 16-bit write @ SPUADDR = 0x%04X\n", data); 726 | 727 | spuaddr = data; 728 | 729 | caddr = 8 * spuaddr; 730 | break; 731 | case static_cast(SPUReg::SPUDATA): 732 | //std::printf("[SPU ] 16-bit write @ SPUDATA = 0x%04X\n", data); 733 | 734 | writeRAM(data); 735 | break; 736 | case static_cast(SPUReg::SPUCNT): 737 | std::printf("[SPU ] 16-bit write @ SPUCNT = 0x%04X\n", data); 738 | 739 | spucnt.cden = data & (1 << 0); 740 | spucnt.exten = data & (1 << 1); 741 | spucnt.cdrev = data & (1 << 2); 742 | spucnt.extrev = data & (1 << 3); 743 | spucnt.trxmod = (data >> 4) & 3; 744 | spucnt.irqen = data & (1 << 6); 745 | spucnt.reven = data & (1 << 7); 746 | spucnt.nstep = (data >> 8) & 3; 747 | spucnt.nshift = (data >> 10) & 0xF; 748 | spucnt.unmute = data & (1 << 14); 749 | spucnt.spuen = data & (1 << 15); 750 | 751 | spustat.spumod = data & 0x3F; 752 | spustat.dmard = data & (1 << 5); 753 | 754 | if (!spucnt.irqen) spustat.irq9 = false; 755 | break; 756 | case static_cast(SPUReg::FIFOCNT): 757 | std::printf("[SPU ] 16-bit write @ FIFOCNT = 0x%04X\n", data); 758 | 759 | assert(data == 0x0004); // ?? 760 | break; 761 | case static_cast(SPUReg::CDVOLL): 762 | std::printf("[SPU ] 16-bit write @ CDVOLL = 0x%04X\n", data); 763 | break; 764 | case static_cast(SPUReg::CDVOLR): 765 | std::printf("[SPU ] 16-bit write @ CDVOLR = 0x%04X\n", data); 766 | break; 767 | case static_cast(SPUReg::EVOLL): 768 | std::printf("[SPU ] 16-bit write @ EVOLL = 0x%04X\n", data); 769 | break; 770 | case static_cast(SPUReg::EVOLR): 771 | std::printf("[SPU ] 16-bit write @ EVOLR = 0x%04X\n", data); 772 | break; 773 | default: 774 | std::printf("[SPU ] Unhandled 16-bit control write @ 0x%08X = 0x%04X\n", addr, data); 775 | 776 | exit(0); 777 | } 778 | } else if (inRange(addr, SPU_BASE + 0x1C0, 0x40)) { 779 | std::printf("[SPU ] Unhandled 16-bit reverb write @ 0x%08X = 0x%04X\n", addr, data); 780 | } else { 781 | std::printf("[SPU ] Unhandled 16-bit write @ 0x%08X = 0x%04X\n", addr, data); 782 | 783 | exit(0); 784 | } 785 | } 786 | 787 | } 788 | -------------------------------------------------------------------------------- /src/core/cpu/gte.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "gte.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace ps::cpu::gte { 13 | 14 | typedef i16 Matrix[3][3]; 15 | typedef i16 Vec16[3]; 16 | typedef i32 Vec32[3]; 17 | 18 | /* --- GTE constants --- */ 19 | 20 | constexpr auto X = 0, Y = 1, Z = 2; 21 | constexpr auto R = 0, G = 1, B = 2; 22 | 23 | /* --- GTE instructions --- */ 24 | 25 | enum Opcode { 26 | RTPS = 0x01, 27 | NCLIP = 0x06, 28 | MVMVA = 0x12, 29 | NCDS = 0x13, 30 | SQR = 0x28, 31 | AVSZ3 = 0x2D, 32 | AVSZ4 = 0x2E, 33 | RTPT = 0x30, 34 | GPF = 0x3D, 35 | GPL = 0x3E, 36 | NCCT = 0x3F, 37 | }; 38 | 39 | /* --- GTE registers --- */ 40 | 41 | enum GTEReg { 42 | VXY0 = 0x00, VZ0 = 0x01, 43 | VXY1 = 0x02, VZ1 = 0x03, 44 | VXY2 = 0x04, VZ2 = 0x05, 45 | RGBC = 0x06, 46 | OTZ = 0x07, 47 | IR0 = 0x08, IR1 = 0x09, IR2 = 0x0A, IR3 = 0x0B, 48 | SXY0 = 0x0C, SXY1 = 0x0D, SXY2 = 0x0E, SXYP = 0x0F, 49 | SZ0 = 0x10, SZ1 = 0x11, SZ2 = 0x12, SZ3 = 0x13, 50 | RGB0 = 0x14, RGB1 = 0x15, RGB2 = 0x16, 51 | MAC0 = 0x18, MAC1 = 0x19, MAC2 = 0x1A, MAC3 = 0x1B, 52 | LZCS = 0x1E, LZCR = 0x1F, 53 | }; 54 | 55 | enum ControlReg { 56 | RT11RT12 = 0x00, RT13RT21 = 0x01, RT22RT23 = 0x02, RT31RT32 = 0x03, RT33 = 0x04, 57 | TRX = 0x05, TRY = 0x06, TRZ = 0x07, 58 | L11L12 = 0x08, L13L21 = 0x09, L22L23 = 0x0A, L31L32 = 0x0B, L33 = 0x0C, 59 | RBK = 0x0D, GBK = 0x0E, BBK = 0x0F, 60 | LR1LR2 = 0x10, LR3LG1 = 0x11, LG2LG3 = 0x12, LB1LB2 = 0x13, LB3 = 0x14, 61 | RFC = 0x15, GFC = 0x16, BFC = 0x17, 62 | OFX = 0x18, OFY = 0x19, 63 | H = 0x1A, 64 | DCA = 0x1B, DCB = 0x1C, 65 | ZSF3 = 0x1D, ZSF4 = 0x1E, 66 | FLAG = 0x1F, 67 | }; 68 | 69 | /* Light color matrix */ 70 | enum LCM { 71 | LR, LB, LG, 72 | }; 73 | 74 | /* --- GTE registers --- */ 75 | 76 | Vec16 v[3]; // Vectors 0-2 77 | u8 rgbc[4]; // Color/Code 78 | u16 otz; 79 | i16 ir[4]; // 16-bit Accumulators 80 | i32 mac[4]; // Accumulators 81 | 82 | u32 lzcs, lzcr; // Leading Zero Count Source/Result 83 | 84 | /* --- GTE FIFOs --- */ 85 | 86 | u32 sxy[3]; // Screen X/Y (three entries) 87 | u16 sz[4]; // Screen Z (four entries) 88 | u32 rgb[3]; // Color/code FIFO 89 | 90 | /* --- GTE control registers --- */ 91 | 92 | Matrix rt; // Rotation matrix 93 | Vec32 tr; // Translation vector X/Y/Z 94 | Matrix ls; // Light source matrix 95 | Vec32 bk; // Background color R/G/B 96 | Matrix lc; // Light color matrix 97 | Vec32 fc; // Far color R/G/B 98 | i32 ofx, ofy; // Screen offset X/Y 99 | u16 h; // Projection plane distance 100 | i16 dca; // Depth cueing parameter A 101 | i32 dcb; // Depth cueing parameter B 102 | i16 zsf3, zsf4; // Z scale factors 103 | 104 | int countLeadingBits(u32 a) { 105 | if (a & (1 << 31)) { 106 | return std::__countl_one(a); 107 | } 108 | 109 | return std::__countl_zero(a); 110 | } 111 | 112 | u32 get(u32 idx) { 113 | switch (idx) { 114 | case GTEReg::OTZ: 115 | //std::printf("[GTE ] Read @ OTZ\n"); 116 | return otz; 117 | case GTEReg::IR0: 118 | //std::printf("[GTE ] Read @ IR0\n"); 119 | return ir[0]; 120 | case GTEReg::IR1: 121 | //std::printf("[GTE ] Read @ IR1\n"); 122 | return ir[1]; 123 | case GTEReg::IR2: 124 | //std::printf("[GTE ] Read @ IR2\n"); 125 | return ir[2]; 126 | case GTEReg::IR3: 127 | //std::printf("[GTE ] Read @ IR3\n"); 128 | return ir[3]; 129 | case GTEReg::SXY0: 130 | //std::printf("[GTE ] Read @ SXY0\n"); 131 | return sxy[0]; 132 | case GTEReg::SXY1: 133 | //std::printf("[GTE ] Read @ SXY1\n"); 134 | return sxy[1]; 135 | case GTEReg::SXY2: 136 | //std::printf("[GTE ] Read @ SXY2\n"); 137 | return sxy[2]; 138 | case GTEReg::SZ0: 139 | //std::printf("[GTE ] Read @ SXY0\n"); 140 | return sz[0]; 141 | case GTEReg::SZ1: 142 | //std::printf("[GTE ] Read @ SXY1\n"); 143 | return sz[1]; 144 | case GTEReg::SZ2: 145 | //std::printf("[GTE ] Read @ SXY2\n"); 146 | return sz[2]; 147 | case GTEReg::SZ3: 148 | //std::printf("[GTE ] Read @ SXY3\n"); 149 | return sz[3]; 150 | case GTEReg::RGB0: 151 | //std::printf("[GTE ] Read @ RGB0\n"); 152 | return rgb[0]; 153 | case GTEReg::RGB1: 154 | //std::printf("[GTE ] Read @ RGB1\n"); 155 | return rgb[1]; 156 | case GTEReg::RGB2: 157 | //std::printf("[GTE ] Read @ RGB2\n"); 158 | return rgb[2]; 159 | case GTEReg::MAC0: 160 | //std::printf("[GTE ] Read @ MAC0\n"); 161 | return mac[0]; 162 | case GTEReg::MAC1: 163 | //std::printf("[GTE ] Read @ MAC1\n"); 164 | return mac[1]; 165 | case GTEReg::MAC2: 166 | //std::printf("[GTE ] Read @ MAC2\n"); 167 | return mac[2]; 168 | case GTEReg::MAC3: 169 | //std::printf("[GTE ] Read @ MAC3\n"); 170 | return mac[3]; 171 | case GTEReg::LZCS: 172 | //std::printf("[GTE ] Read @ LZCS\n"); 173 | return lzcs; 174 | case GTEReg::LZCR: 175 | //std::printf("[GTE ] Read @ LZCR\n"); 176 | return lzcr; 177 | default: 178 | std::printf("[GTE ] Unhandled read @ %u\n", idx); 179 | 180 | exit(0); 181 | } 182 | } 183 | 184 | u32 getControl(u32 idx) { 185 | switch (idx) { 186 | case ControlReg::FLAG: 187 | //std::printf("[GTE ] Control read @ FLAG\n"); 188 | return 0; 189 | default: 190 | std::printf("[GTE ] Unhandled control read @ %u\n", idx); 191 | 192 | exit(0); 193 | } 194 | } 195 | 196 | void set(u32 idx, u32 data) { 197 | switch (idx) { 198 | case GTEReg::VXY0: 199 | //std::printf("[GTE ] Write @ VXY0 = 0x%08X\n", data); 200 | 201 | v[0][X] = data; 202 | v[0][Y] = data >> 16; 203 | break; 204 | case GTEReg::VZ0: 205 | //std::printf("[GTE ] Write @ VZ0 = 0x%08X\n", data); 206 | 207 | v[0][Z] = data; 208 | break; 209 | case GTEReg::VXY1: 210 | //std::printf("[GTE ] Write @ VXY1 = 0x%08X\n", data); 211 | 212 | v[1][X] = data; 213 | v[1][Y] = data >> 16; 214 | break; 215 | case GTEReg::VZ1: 216 | //std::printf("[GTE ] Write @ VZ1 = 0x%08X\n", data); 217 | 218 | v[1][Z] = data; 219 | break; 220 | case GTEReg::VXY2: 221 | //std::printf("[GTE ] Write @ VXY2 = 0x%08X\n", data); 222 | 223 | v[2][X] = data; 224 | v[2][Y] = data >> 16; 225 | break; 226 | case GTEReg::VZ2: 227 | //std::printf("[GTE ] Write @ VZ2 = 0x%08X\n", data); 228 | 229 | v[2][Z] = data; 230 | break; 231 | case GTEReg::RGBC: 232 | //std::printf("[GTE ] Write @ RGBC = 0x%08X\n", data); 233 | 234 | rgbc[0] = data; 235 | rgbc[1] = data >> 8; 236 | rgbc[2] = data >> 16; 237 | rgbc[3] = data >> 24; 238 | break; 239 | case GTEReg::IR0: 240 | //std::printf("[GTE ] Write @ IR0 = 0x%08X\n", data); 241 | 242 | ir[0] = data; 243 | break; 244 | case GTEReg::IR1: 245 | //std::printf("[GTE ] Write @ IR1 = 0x%08X\n", data); 246 | 247 | ir[1] = data; 248 | break; 249 | case GTEReg::IR2: 250 | //std::printf("[GTE ] Write @ IR2 = 0x%08X\n", data); 251 | 252 | ir[2] = data; 253 | break; 254 | case GTEReg::IR3: 255 | //std::printf("[GTE ] Write @ IR3 = 0x%08X\n", data); 256 | 257 | ir[3] = data; 258 | break; 259 | case GTEReg::RGB0: 260 | //std::printf("[GTE ] Write @ RGB0 = 0x%08X\n", data); 261 | 262 | rgb[0] = data; 263 | break; 264 | case GTEReg::RGB1: 265 | //std::printf("[GTE ] Write @ RGB1 = 0x%08X\n", data); 266 | 267 | rgb[1] = data; 268 | break; 269 | case GTEReg::RGB2: 270 | //std::printf("[GTE ] Write @ RGB2 = 0x%08X\n", data); 271 | 272 | rgb[2] = data; 273 | break; 274 | case GTEReg::LZCS: 275 | //std::printf("[GTE ] Write @ LZCS = 0x%08X\n", data); 276 | 277 | lzcs = data; 278 | lzcr = countLeadingBits(data); 279 | break; 280 | default: 281 | std::printf("[GTE ] Unhandled write @ %u = 0x%08X\n", idx, data); 282 | 283 | exit(0); 284 | } 285 | } 286 | 287 | void setControl(u32 idx, u32 data) { 288 | switch (idx) { 289 | case ControlReg::RT11RT12: 290 | //std::printf("[GTE ] Control write @ RT11RT12 = 0x%08X\n", data); 291 | 292 | rt[0][0] = data; 293 | rt[0][1] = data >> 16; 294 | break; 295 | case ControlReg::RT13RT21: 296 | //std::printf("[GTE ] Control write @ RT13RT21 = 0x%08X\n", data); 297 | 298 | rt[0][2] = data; 299 | rt[1][0] = data >> 16; 300 | break; 301 | case ControlReg::RT22RT23: 302 | //std::printf("[GTE ] Control write @ RT22RT23 = 0x%08X\n", data); 303 | 304 | rt[1][1] = data; 305 | rt[1][2] = data >> 16; 306 | break; 307 | case ControlReg::RT31RT32: 308 | //std::printf("[GTE ] Control write @ RT31RT32 = 0x%08X\n", data); 309 | 310 | rt[2][0] = data; 311 | rt[2][1] = data >> 16; 312 | break; 313 | case ControlReg::RT33: 314 | //std::printf("[GTE ] Control write @ RT33 = 0x%08X\n", data); 315 | 316 | rt[2][2] = data; 317 | break; 318 | case ControlReg::TRX: 319 | //std::printf("[GTE ] Control write @ TRX = 0x%08X\n", data); 320 | 321 | tr[X] = data; 322 | break; 323 | case ControlReg::TRY: 324 | //std::printf("[GTE ] Control write @ TRY = 0x%08X\n", data); 325 | 326 | tr[Y] = data; 327 | break; 328 | case ControlReg::TRZ: 329 | //std::printf("[GTE ] Control write @ TRZ = 0x%08X\n", data); 330 | 331 | tr[Z] = data; 332 | break; 333 | case ControlReg::L11L12: 334 | //std::printf("[GTE ] Control write @ L11L12 = 0x%08X\n", data); 335 | 336 | ls[0][0] = data; 337 | ls[0][1] = data >> 16; 338 | break; 339 | case ControlReg::L13L21: 340 | //std::printf("[GTE ] Control write @ L13L21 = 0x%08X\n", data); 341 | 342 | ls[0][2] = data; 343 | ls[1][0] = data >> 16; 344 | break; 345 | case ControlReg::L22L23: 346 | //std::printf("[GTE ] Control write @ L22L23 = 0x%08X\n", data); 347 | 348 | ls[1][1] = data; 349 | ls[1][2] = data >> 16; 350 | break; 351 | case ControlReg::L31L32: 352 | //std::printf("[GTE ] Control write @ L31L32 = 0x%08X\n", data); 353 | 354 | ls[2][0] = data; 355 | ls[2][1] = data >> 16; 356 | break; 357 | case ControlReg::L33: 358 | //std::printf("[GTE ] Control write @ L33 = 0x%08X\n", data); 359 | 360 | ls[2][2] = data; 361 | break; 362 | case ControlReg::RBK: 363 | //std::printf("[GTE ] Control write @ RBK = 0x%08X\n", data); 364 | 365 | bk[R] = data; 366 | break; 367 | case ControlReg::GBK: 368 | //std::printf("[GTE ] Control write @ GBK = 0x%08X\n", data); 369 | 370 | bk[G] = data; 371 | break; 372 | case ControlReg::BBK: 373 | //std::printf("[GTE ] Control write @ BBK = 0x%08X\n", data); 374 | 375 | bk[B] = data; 376 | break; 377 | case ControlReg::LR1LR2: 378 | //std::printf("[GTE ] Control write @ LR1LR2 = 0x%08X\n", data); 379 | 380 | lc[LCM::LR][0] = data; 381 | lc[LCM::LR][1] = data >> 16; 382 | break; 383 | case ControlReg::LR3LG1: 384 | //std::printf("[GTE ] Control write @ LR3LG1 = 0x%08X\n", data); 385 | 386 | lc[LCM::LR][2] = data; 387 | lc[LCM::LG][0] = data >> 16; 388 | break; 389 | case ControlReg::LG2LG3: 390 | //std::printf("[GTE ] Control write @ LG2LG3 = 0x%08X\n", data); 391 | 392 | lc[LCM::LG][1] = data; 393 | lc[LCM::LG][2] = data >> 16; 394 | break; 395 | case ControlReg::LB1LB2: 396 | //std::printf("[GTE ] Control write @ LB1LB2 = 0x%08X\n", data); 397 | 398 | lc[LCM::LB][0] = data; 399 | lc[LCM::LB][1] = data >> 16; 400 | break; 401 | case ControlReg::LB3: 402 | //std::printf("[GTE ] Control write @ LB3 = 0x%08X\n", data); 403 | 404 | lc[LCM::LB][2] = data; 405 | break; 406 | case ControlReg::RFC: 407 | //std::printf("[GTE ] Control write @ RFC = 0x%08X\n", data); 408 | 409 | fc[R] = data; 410 | break; 411 | case ControlReg::GFC: 412 | //std::printf("[GTE ] Control write @ GFC = 0x%08X\n", data); 413 | 414 | fc[G] = data; 415 | break; 416 | case ControlReg::BFC: 417 | //std::printf("[GTE ] Control write @ BFC = 0x%08X\n", data); 418 | 419 | fc[B] = data; 420 | break; 421 | case ControlReg::OFX: 422 | //std::printf("[GTE ] Control write @ OFX = 0x%08X\n", data); 423 | 424 | ofx = data; 425 | break; 426 | case ControlReg::OFY: 427 | //std::printf("[GTE ] Control write @ OFY = 0x%08X\n", data); 428 | 429 | ofy = data; 430 | break; 431 | case ControlReg::H: 432 | //std::printf("[GTE ] Control write @ H = 0x%08X\n", data); 433 | 434 | h = data; 435 | break; 436 | case ControlReg::DCA: 437 | //std::printf("[GTE ] Control write @ DCA = 0x%08X\n", data); 438 | 439 | dca = data; 440 | break; 441 | case ControlReg::DCB: 442 | //std::printf("[GTE ] Control write @ DCB = 0x%08X\n", data); 443 | 444 | dcb = data; 445 | break; 446 | case ControlReg::ZSF3: 447 | //std::printf("[GTE ] Control write @ ZSF3 = 0x%08X\n", data); 448 | 449 | zsf3 = data; 450 | break; 451 | case ControlReg::ZSF4: 452 | //std::printf("[GTE ] Control write @ ZSF4 = 0x%08X\n", data); 453 | 454 | zsf4 = data; 455 | break; 456 | default: 457 | std::printf("[GTE ] Unhandled control write @ %u = 0x%08X\n", idx, data); 458 | 459 | exit(0); 460 | } 461 | } 462 | 463 | /* --- MAC/IR handlers --- */ 464 | 465 | /* Sets IR, performs clipping checks */ 466 | void setIR(u32 idx, i64 data, bool lm) { 467 | static const i64 IR_MIN[] = { 0, -0x8000, -0x8000, -0x8000 }; 468 | static const i64 IR_MAX[] = { 0x1000, 0x7FFF, 0x7FFF, 0x7FFF }; 469 | 470 | const auto irMin = (lm) ? 0 : IR_MIN[idx]; 471 | 472 | /* Check for clipping */ 473 | if (data > IR_MAX[idx]) { 474 | data = IR_MAX[idx]; 475 | 476 | /* TODO: set IR overflow flags */ 477 | } else if (data < irMin) { 478 | data = irMin; 479 | 480 | /* TODO: set IR overflow flags */ 481 | } 482 | 483 | ir[idx] = data; 484 | } 485 | 486 | /* Sets MAC, performs overflow checks */ 487 | void setMAC(u32 idx, i64 data, int shift) { 488 | /* TODO: check for MAC overflow */ 489 | 490 | /* Shift value, store low 32 bits of the result in MAC */ 491 | mac[idx] = data >> shift; 492 | } 493 | 494 | /* Sets MAC and IR, performs overflow checks */ 495 | void setMACIR(u32 idx, i64 data, int shift, bool lm) { 496 | /* TODO: check for MAC overflow */ 497 | 498 | /* Shift value, store low 32 bits of the result in MAC */ 499 | mac[idx] = data >> shift; 500 | 501 | setIR(idx, mac[idx], lm); 502 | } 503 | 504 | /* Sign-extends MAC values */ 505 | i64 extsMAC(u32 idx, i64 data) { 506 | static const int MAC_WIDTH[] = { 31, 44, 44, 44 }; 507 | 508 | /* TODO: check for MAC overflows */ 509 | 510 | const int shift = 64 - MAC_WIDTH[idx]; 511 | 512 | return (data << shift) >> shift; 513 | } 514 | 515 | /* GTE division (unsigned Newton-Raphson) */ 516 | u32 div(u32 a, u32 b) { 517 | static const u8 unrTable[] = { 518 | 0xFF, 0xFD, 0xFB, 0xF9, 0xF7, 0xF5, 0xF3, 0xF1, 0xEF, 0xEE, 0xEC, 0xEA, 0xE8, 0xE6, 0xE4, 0xE3, 519 | 0xE1, 0xDF, 0xDD, 0xDC, 0xDA, 0xD8, 0xD6, 0xD5, 0xD3, 0xD1, 0xD0, 0xCE, 0xCD, 0xCB, 0xC9, 0xC8, 520 | 0xC6, 0xC5, 0xC3, 0xC1, 0xC0, 0xBE, 0xBD, 0xBB, 0xBA, 0xB8, 0xB7, 0xB5, 0xB4, 0xB2, 0xB1, 0xB0, 521 | 0xAE, 0xAD, 0xAB, 0xAA, 0xA9, 0xA7, 0xA6, 0xA4, 0xA3, 0xA2, 0xA0, 0x9F, 0x9E, 0x9C, 0x9B, 0x9A, 522 | 0x99, 0x97, 0x96, 0x95, 0x94, 0x92, 0x91, 0x90, 0x8F, 0x8D, 0x8C, 0x8B, 0x8A, 0x89, 0x87, 0x86, 523 | 0x85, 0x84, 0x83, 0x82, 0x81, 0x7F, 0x7E, 0x7D, 0x7C, 0x7B, 0x7A, 0x79, 0x78, 0x77, 0x75, 0x74, 524 | 0x73, 0x72, 0x71, 0x70, 0x6F, 0x6E, 0x6D, 0x6C, 0x6B, 0x6A, 0x69, 0x68, 0x67, 0x66, 0x65, 0x64, 525 | 0x63, 0x62, 0x61, 0x60, 0x5F, 0x5E, 0x5D, 0x5D, 0x5C, 0x5B, 0x5A, 0x59, 0x58, 0x57, 0x56, 0x55, 526 | 0x54, 0x53, 0x53, 0x52, 0x51, 0x50, 0x4F, 0x4E, 0x4D, 0x4D, 0x4C, 0x4B, 0x4A, 0x49, 0x48, 0x48, 527 | 0x47, 0x46, 0x45, 0x44, 0x43, 0x43, 0x42, 0x41, 0x40, 0x3F, 0x3F, 0x3E, 0x3D, 0x3C, 0x3C, 0x3B, 528 | 0x3A, 0x39, 0x39, 0x38, 0x37, 0x36, 0x36, 0x35, 0x34, 0x33, 0x33, 0x32, 0x31, 0x31, 0x30, 0x2F, 529 | 0x2E, 0x2E, 0x2D, 0x2C, 0x2C, 0x2B, 0x2A, 0x2A, 0x29, 0x28, 0x28, 0x27, 0x26, 0x26, 0x25, 0x24, 530 | 0x24, 0x23, 0x22, 0x22, 0x21, 0x20, 0x20, 0x1F, 0x1E, 0x1E, 0x1D, 0x1D, 0x1C, 0x1B, 0x1B, 0x1A, 531 | 0x19, 0x19, 0x18, 0x18, 0x17, 0x16, 0x16, 0x15, 0x15, 0x14, 0x14, 0x13, 0x12, 0x12, 0x11, 0x11, 532 | 0x10, 0x0F, 0x0F, 0x0E, 0x0E, 0x0D, 0x0D, 0x0C, 0x0C, 0x0B, 0x0A, 0x0A, 0x09, 0x09, 0x08, 0x08, 533 | 0x07, 0x07, 0x06, 0x06, 0x05, 0x05, 0x04, 0x04, 0x03, 0x03, 0x02, 0x02, 0x01, 0x01, 0x00, 0x00, 534 | 0x00, 535 | }; 536 | 537 | if ((2 * b) <= a) { 538 | /* TODO: set overflow flags */ 539 | 540 | return 0x1FFFF; 541 | } 542 | 543 | const auto shift = std::__countl_zero(b); 544 | 545 | a <<= shift; 546 | b <<= shift; 547 | 548 | const auto u = 0x101 + unrTable[(b + 0x40) >> 7]; 549 | 550 | b |= 0x8000; 551 | 552 | auto d = ((b * -u) + 0x80) >> 8; 553 | 554 | d = ((u * (0x20000 + d)) + 0x80) >> 8; 555 | 556 | const auto n = (a * d + 0x8000) >> 16; 557 | 558 | if (n > 0x1FFFF) return 0x1FFFF; 559 | 560 | return n; 561 | } 562 | 563 | /* Matrix-vector multiplication */ 564 | void mulMV(const Matrix &m, const Vec16 &vtx, int shift, bool lm) { 565 | for (int i = 0; i < 3; i++) setMACIR(i + 1, extsMAC(i + 1, (i64)m[i][X] * (i64)vtx[X] + (i64)m[i][Y] * (i64)vtx[Y] + (i64)m[i][Z] * (i64)vtx[Z]), shift, lm); 566 | } 567 | 568 | /* Matrix-vector multiplication with translation */ 569 | void mulMVT(const Matrix &m, const Vec16 &vtx, const Vec32 &t, int shift, bool lm) { 570 | for (int i = 0; i < 3; i++) setMACIR(i + 1, extsMAC(i + 1, extsMAC(i + 1, ((i64)t[i] << 12) + (i64)m[i][X] * (i64)vtx[X]) + (i64)m[i][Y] * (i64)vtx[Y] + (i64)m[i][Z] * (i64)vtx[Z]), shift, lm); 571 | } 572 | 573 | /* Color interpolation */ 574 | void intCol(i64 mac1, i64 mac2, i64 mac3, int shift, bool lm) { 575 | setMACIR(1, ((i64)fc[0] << 12) - mac1, shift, lm); 576 | setMACIR(2, ((i64)fc[1] << 12) - mac2, shift, lm); 577 | setMACIR(3, ((i64)fc[2] << 12) - mac3, shift, lm); 578 | 579 | setMACIR(1, (i64)ir[1] * (i64)ir[0] + mac1, shift, lm); 580 | setMACIR(2, (i64)ir[2] * (i64)ir[0] + mac2, shift, lm); 581 | setMACIR(3, (i64)ir[3] * (i64)ir[0] + mac3, shift, lm); 582 | } 583 | 584 | /* Color saturation */ 585 | u8 satCol(u32 idx, i32 col) { 586 | if (col < 0) { 587 | /* TODO: set flags */ 588 | 589 | return 0; 590 | } else if (col > 0xFF) { 591 | /* TODO: set flags */ 592 | 593 | return 0xFF; 594 | } 595 | 596 | return (u8)col; 597 | } 598 | 599 | /* --- GTE FIFO handlers --- */ 600 | 601 | i16 getSX(u32 idx) { 602 | return (i16)sxy[idx]; 603 | } 604 | 605 | i16 getSY(u32 idx) { 606 | return (i16)(sxy[idx] >> 16); 607 | } 608 | 609 | /* Pushes a color/code */ 610 | void pushRGB(u8 *col) { 611 | /* Advance FIFO stages */ 612 | for (int i = 0; i < 2; i++) rgb[i] = rgb[i + 1]; 613 | 614 | rgb[2] = (u32)(col[3] << 24) | ((u32)col[2] << 16) | ((u32)col[1] << 8) | (u32)col[0]; 615 | } 616 | 617 | /* Pushes screen X and Y values, performs clipping checks */ 618 | void pushSXY(i64 x, i64 y) { 619 | if (x > 1023) { 620 | x = 1023; 621 | } else if (x < -1024) { 622 | x = -1024; 623 | } 624 | 625 | if (y > 1023) { 626 | y = 1023; 627 | } else if (y < -1024) { 628 | y = -1024; 629 | } 630 | 631 | /* Advance FIFO stages */ 632 | for (int i = 0; i < 2; i++) sxy[i] = sxy[i + 1]; 633 | 634 | sxy[2] = ((u32)(u16)y << 16) | (u32)(u16)x; 635 | } 636 | 637 | /* Pushes a screen Z value, performs clipping checks */ 638 | void pushSZ(i64 data) { 639 | if (data < 0) { 640 | data = 0; 641 | } else if (data > 0xFFFF) { 642 | data = 0xFFFF; 643 | } 644 | 645 | /* Advance FIFO stages */ 646 | for (int i = 0; i < 3; i++) sz[i] = sz[i + 1]; 647 | 648 | sz[3] = data; 649 | } 650 | 651 | /* AVerage Screen Z (3 values) */ 652 | void iAVSZ3() { 653 | /* Multiply Zs by Z scale factor */ 654 | 655 | setMAC(0, (i64)zsf3 * ((i64)sz[1] + (i64)sz[2] + (i64)sz[3]), 0); 656 | 657 | /* Clip and set ordering table Z */ 658 | 659 | auto z = mac[0] >> 12; 660 | 661 | if (z > 0xFFFF) { 662 | z = 0xFFFF; 663 | } else if (z < 0) { 664 | z = 0; 665 | } 666 | 667 | otz = z; 668 | 669 | //std::printf("[GTE:AVSZ3 ] OTZ = 0x%04x\n", otz); 670 | } 671 | 672 | /* AVerage Screen Z (4 values) */ 673 | void iAVSZ4() { 674 | /* Multiply Zs by Z scale factor */ 675 | 676 | setMAC(0, (i64)zsf4 * ((i64)sz[0] + (i64)sz[1] + (i64)sz[2] + (i64)sz[3]), 0); 677 | 678 | /* Clip and set ordering table Z */ 679 | 680 | auto z = mac[0] >> 12; 681 | 682 | if (z > 0xFFFF) { 683 | z = 0xFFFF; 684 | } else if (z < 0) { 685 | z = 0; 686 | } 687 | 688 | otz = z; 689 | 690 | //std::printf("[GTE:AVSZ4 ] OTZ = 0x%04x\n", otz); 691 | } 692 | 693 | /* General purpose interpolation */ 694 | void iGPF(u32 cmd) { 695 | const bool lm = cmd & (1 << 10); 696 | const bool sf = cmd & (1 << 19); 697 | 698 | const auto shift = 12 * sf; 699 | 700 | for (int i = 1; i < 4; i++) setMACIR(i, (i64)ir[i] * (i64)ir[0], shift, lm); 701 | 702 | /* Calculate and push color/code */ 703 | 704 | u8 col[4]; 705 | 706 | for (int i = 0; i < 3; i++) col[i] = satCol(i, mac[i + 1] >> 4); 707 | 708 | col[3] = rgbc[3]; 709 | 710 | pushRGB(col); 711 | 712 | //std::printf("[GTE:GPF ] RGB2 = 0x%08x\n", rgb[2]); 713 | } 714 | 715 | /* General purpose interpolation with base */ 716 | void iGPL(u32 cmd) { 717 | const bool lm = cmd & (1 << 10); 718 | const bool sf = cmd & (1 << 19); 719 | 720 | const auto shift = 12 * sf; 721 | 722 | for (int i = 1; i < 4; i++) setMACIR(i, ((i64)ir[i] * (i64)ir[0] + mac[i]) << shift, shift, lm); 723 | 724 | /* Calculate and push color/code */ 725 | 726 | u8 col[4]; 727 | 728 | for (int i = 0; i < 3; i++) col[i] = satCol(i, mac[i + 1] >> 4); 729 | 730 | col[3] = rgbc[3]; 731 | 732 | pushRGB(col); 733 | 734 | //std::printf("[GTE:GPL ] RGB2 = 0x%08x\n", rgb[2]); 735 | } 736 | 737 | /* Vector-matrix multiply with vector add */ 738 | void iMVMVA(u32 cmd) { 739 | const bool lm = cmd & (1 << 10); 740 | const bool sf = cmd & (1 << 19); 741 | 742 | const auto shift = 12 * sf; 743 | 744 | Matrix m; 745 | 746 | switch ((cmd >> 17) & 3) { 747 | case 0: 748 | for (int i = 0; i < 3; i++) { 749 | m[i][X] = rt[i][X]; 750 | m[i][Y] = rt[i][Y]; 751 | m[i][Z] = rt[i][Z]; 752 | } 753 | break; 754 | case 1: 755 | for (int i = 0; i < 3; i++) { 756 | m[i][X] = ls[i][X]; 757 | m[i][Y] = ls[i][Y]; 758 | m[i][Z] = ls[i][Z]; 759 | } 760 | break; 761 | case 2: 762 | for (int i = 0; i < 3; i++) { 763 | m[i][X] = lc[i][X]; 764 | m[i][Y] = lc[i][Y]; 765 | m[i][Z] = lc[i][Z]; 766 | } 767 | break; 768 | default: 769 | std::printf("[GTE:MVMVA ] Unhandled matrix %u\n", (cmd >> 17) & 3); 770 | 771 | exit(0); 772 | } 773 | 774 | Vec16 vtx; 775 | 776 | switch ((cmd >> 15) & 3) { 777 | case 0: 778 | vtx[X] = v[0][X]; 779 | vtx[Y] = v[0][Y]; 780 | vtx[Z] = v[0][Z]; 781 | break; 782 | case 1: 783 | vtx[X] = v[1][X]; 784 | vtx[Y] = v[1][Y]; 785 | vtx[Z] = v[1][Z]; 786 | break; 787 | case 2: 788 | vtx[X] = v[2][X]; 789 | vtx[Y] = v[2][Y]; 790 | vtx[Z] = v[2][Z]; 791 | break; 792 | case 3: 793 | vtx[X] = ir[1]; 794 | vtx[Y] = ir[2]; 795 | vtx[Z] = ir[3]; 796 | break; 797 | } 798 | 799 | Vec32 vt; 800 | 801 | switch ((cmd >> 13) & 3) { 802 | case 0: 803 | vt[X] = tr[X]; 804 | vt[Y] = tr[Y]; 805 | vt[Z] = tr[Z]; 806 | break; 807 | case 1: 808 | vt[X] = bk[X]; 809 | vt[Y] = bk[Y]; 810 | vt[Z] = bk[Z]; 811 | break; 812 | case 2: 813 | vt[X] = fc[X]; 814 | vt[Y] = fc[Y]; 815 | vt[Z] = fc[Z]; 816 | break; 817 | case 3: 818 | vt[X] = 0; 819 | vt[Y] = 0; 820 | vt[Z] = 0; 821 | break; 822 | } 823 | 824 | mulMVT(m, vtx, vt, shift, lm); 825 | } 826 | 827 | /* Normal Color Color(??) Triple */ 828 | void iNCCT(u32 cmd) { 829 | //std::printf("[GTE ] NCCT\n"); 830 | 831 | const bool lm = cmd & (1 << 10); 832 | const bool sf = cmd & (1 << 19); 833 | 834 | const auto shift = 12 * sf; 835 | 836 | for (int i = 0; i < 3; i++) { 837 | mulMV(ls, v[i], shift, lm); 838 | 839 | Vec16 vtx = { ir[1], ir[2], ir[3] }; 840 | 841 | mulMVT(lc, vtx, bk, shift, lm); 842 | 843 | /* Calculate and push color/code */ 844 | 845 | u8 col[4]; 846 | 847 | for (int j = 0; j < 3; j++) col[j] = satCol(i, mac[j + 1] >> 4); 848 | 849 | col[3] = rgbc[3]; 850 | 851 | pushRGB(col); 852 | 853 | //std::printf("[GTE:NCCT ] RGB2 = 0x%08x\n", rgb[2]); 854 | } 855 | } 856 | 857 | /* Normal Color Depth cue Single */ 858 | void iNCDS(u32 cmd) { 859 | //std::printf("[GTE ] NCDS\n"); 860 | 861 | const bool lm = cmd & (1 << 10); 862 | const bool sf = cmd & (1 << 19); 863 | 864 | const auto shift = 12 * sf; 865 | 866 | mulMV(ls, v[0], shift, lm); 867 | 868 | Vec16 vtx = { ir[1], ir[2], ir[3] }; 869 | 870 | mulMVT(lc, vtx, bk, shift, lm); 871 | 872 | intCol(((i32)rgbc[0] * (i32)ir[1]) << 4, ((i32)rgbc[1] * (i32)ir[2]) << 4, ((i32)rgbc[2] * (i32)ir[3]) << 4, shift, lm); 873 | 874 | /* Calculate and push color/code */ 875 | 876 | u8 col[4]; 877 | 878 | for (int i = 0; i < 3; i++) col[i] = satCol(i, mac[i + 1] >> 4); 879 | 880 | col[3] = rgbc[3]; 881 | 882 | pushRGB(col); 883 | 884 | //std::printf("[GTE:NCDS ] RGB2 = 0x%08x\n", rgb[2]); 885 | } 886 | 887 | /* Normal CLIPping */ 888 | void iNCLIP() { 889 | //std::printf("[GTE ] NCLIP\n"); 890 | 891 | const auto clip = (i64)getSX(0) * (i64)getSY(1) + (i64)getSX(1) * (i64)getSY(2) + (i64)getSX(2) * (i64)getSY(0) - (i64)getSX(0) * (i64)getSY(2) - (i64)getSX(1) * (i64)getSY(0) - (i64)getSX(2) * (i64)getSY(1); 892 | 893 | setMAC(0, clip, 0); 894 | 895 | //std::printf("[GTE:NCLIP ] MAC0 = 0x%08x\n", mac[0]); 896 | } 897 | 898 | /* Rotate/Translate Perspective Single */ 899 | void iRTPS(u32 cmd) { 900 | //std::printf("[GTE ] RTPS\n"); 901 | 902 | const bool lm = cmd & (1 << 10); 903 | const bool sf = cmd & (1 << 19); 904 | 905 | const auto shift = 12 * sf; 906 | 907 | /* Do perspective transformation on vector Vi */ 908 | const auto x = extsMAC(1, extsMAC(1, 0x1000 * (i64)tr[X] + rt[0][X] * v[0][X]) + rt[0][Y] * v[0][Y] + rt[0][Z] * v[0][Z]); 909 | const auto y = extsMAC(2, extsMAC(2, 0x1000 * (i64)tr[Y] + rt[1][X] * v[0][X]) + rt[1][Y] * v[0][Y] + rt[1][Z] * v[0][Z]); 910 | const auto z = extsMAC(3, extsMAC(3, 0x1000 * (i64)tr[Z] + rt[2][X] * v[0][X]) + rt[2][Y] * v[0][Y] + rt[2][Z] * v[0][Z]); 911 | 912 | /* Truncate results to 32 bits */ 913 | setMAC(1, x, shift); 914 | setMAC(2, y, shift); 915 | setMAC(3, z, shift); 916 | 917 | setIR(1, mac[1], lm); 918 | setIR(2, mac[2], lm); 919 | 920 | setIR(3, z >> shift, false); 921 | 922 | /* Push new screen Z */ 923 | 924 | pushSZ(mac[3] >> (12 * !sf)); 925 | 926 | //std::printf("[GTE:RTPS ] SZ = 0x%04x\n", sz[3]); 927 | 928 | /* Calculate and push new screen XY */ 929 | 930 | //const auto unr = (i64)div(h, sz[3]); 931 | const auto unr = ((0x10000 * h) + (sz[3] >> 1)) / sz[3]; 932 | 933 | const auto sx = unr * (i64)ir[1] + (i64)ofx; 934 | const auto sy = unr * (i64)ir[2] + (i64)ofy; 935 | 936 | pushSXY(sx >> 16, sy >> 16); 937 | 938 | //std::printf("[GTE:RTPS ] SXY = 0x%08x\n", sxy[2]); 939 | 940 | /* TODO: check for SX/SY MAC overflow */ 941 | 942 | /* Depth cue */ 943 | 944 | const auto dc = unr * (i64)dca + (i64)dcb; 945 | 946 | setMAC(0, dc, 0); 947 | 948 | setIR(0, dc >> 12, true); 949 | 950 | //std::printf("[GTE:RTPS ] IR0 = 0x%04x\n", ir[0]); 951 | } 952 | 953 | /* Rotate/Translate Perspective Triple */ 954 | void iRTPT(u32 cmd) { 955 | //std::printf("[GTE ] RTPT\n"); 956 | 957 | const bool lm = cmd & (1 << 10); 958 | const bool sf = cmd & (1 << 19); 959 | 960 | const auto shift = 12 * sf; 961 | 962 | for (int i = 0; i < 3; i++) { 963 | /* Do perspective transformation on vector Vi */ 964 | const auto x = extsMAC(1, extsMAC(1, 0x1000 * (i64)tr[X] + rt[0][X] * v[i][X]) + rt[0][Y] * v[i][Y] + rt[0][Z] * v[i][Z]); 965 | const auto y = extsMAC(2, extsMAC(2, 0x1000 * (i64)tr[Y] + rt[1][X] * v[i][X]) + rt[1][Y] * v[i][Y] + rt[1][Z] * v[i][Z]); 966 | const auto z = extsMAC(3, extsMAC(3, 0x1000 * (i64)tr[Z] + rt[2][X] * v[i][X]) + rt[2][Y] * v[i][Y] + rt[2][Z] * v[i][Z]); 967 | 968 | /* Truncate results to 32 bits */ 969 | setMAC(1, x, shift); 970 | setMAC(2, y, shift); 971 | setMAC(3, z, shift); 972 | 973 | setIR(1, mac[1], lm); 974 | setIR(2, mac[2], lm); 975 | 976 | setIR(3, z >> shift, false); 977 | 978 | /* Push new screen Z */ 979 | 980 | pushSZ(mac[3] >> (12 * !sf)); 981 | 982 | //std::printf("[GTE:RTPT ] SZ = 0x%04x\n", sz[3]); 983 | 984 | /* Calculate and push new screen XY */ 985 | 986 | //const auto unr = (i64)div(h, sz[3]); 987 | const auto unr = ((0x10000 * h) + (sz[3] >> 1)) / sz[3]; 988 | 989 | const auto sx = unr * (i64)ir[1] + (i64)ofx; 990 | const auto sy = unr * (i64)ir[2] + (i64)ofy; 991 | 992 | pushSXY(sx >> 16, sy >> 16); 993 | 994 | //std::printf("[GTE:RTPT ] SXY = 0x%08x\n", sxy[2]); 995 | 996 | /* TODO: check for SX/SY MAC overflow */ 997 | 998 | /* Depth cue */ 999 | 1000 | const auto dc = unr * (i64)dca + (i64)dcb; 1001 | 1002 | setMAC(0, dc, 0); 1003 | 1004 | setIR(0, dc >> 12, true); 1005 | 1006 | //std::printf("[GTE:RTPT ] IR0 = 0x%04x\n", ir[0]); 1007 | } 1008 | } 1009 | 1010 | /* SQuare Root */ 1011 | void iSQR(u32 cmd) { 1012 | const bool lm = cmd & (1 << 10); 1013 | const bool sf = cmd & (1 << 19); 1014 | 1015 | const auto shift = 12 * sf; 1016 | 1017 | for (int i = 1; i < 4; i++) mac[i] = ((i32)ir[i] * (i32)ir[i]) >> shift; 1018 | for (int i = 1; i < 4; i++) setIR(i, mac[i], lm); 1019 | } 1020 | 1021 | void doCmd(u32 cmd) { 1022 | const auto opcode = cmd & 0x3F; 1023 | 1024 | switch (opcode) { 1025 | case Opcode::RTPS : iRTPS(cmd); break; 1026 | case Opcode::NCLIP: iNCLIP(); break; 1027 | case Opcode::MVMVA: iMVMVA(cmd); break; 1028 | case Opcode::NCDS : iNCDS(cmd); break; 1029 | case Opcode::SQR : iSQR(cmd); break; 1030 | case Opcode::AVSZ3: iAVSZ3(); break; 1031 | case Opcode::AVSZ4: iAVSZ4(); break; 1032 | case Opcode::RTPT : iRTPT(cmd); break; 1033 | case Opcode::GPF : iGPF(cmd); break; 1034 | case Opcode::GPL : iGPL(cmd); break; 1035 | case Opcode::NCCT : iNCCT(cmd); break; 1036 | default: 1037 | std::printf("[GTE ] Unhandled instruction 0x%02X (0x%07X)\n", opcode, cmd); 1038 | 1039 | exit(0); 1040 | } 1041 | } 1042 | 1043 | } 1044 | -------------------------------------------------------------------------------- /src/core/gpu/gpu.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Mari is a PlayStation emulator. 3 | * Copyright (C) 2023 Lady Starbreeze (Michelle-Marie Schiller) 4 | */ 5 | 6 | #include "gpu.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "../intc.hpp" 15 | #include "../Mari.hpp" 16 | #include "../scheduler.hpp" 17 | #include "../timer/timer.hpp" 18 | #include "../spu/spu.hpp" 19 | 20 | namespace ps::gpu { 21 | 22 | using Interrupt = intc::Interrupt; 23 | 24 | /* --- GPU constants --- */ 25 | 26 | constexpr i64 CYCLES_PER_HDRAW = 2560 / 1.57; 27 | constexpr i64 CYCLES_PER_SCANLINE = 3413 / 1.57; // NTSC 28 | constexpr i64 SCANLINES_PER_VDRAW = 240; 29 | constexpr i64 SCANLINES_PER_FRAME = 263; 30 | 31 | constexpr size_t VRAM_WIDTH = 1024; 32 | constexpr size_t VRAM_HEIGHT = 512; 33 | 34 | /* 2D vertex */ 35 | struct Vertex { 36 | Vertex() : x(0), y(0), c(0), tex(0) {} 37 | Vertex(u32 v) : x((i32)((v & 0x7FF) << 21) >> 21), y((i32)(((v >> 16) & 0x7FF) << 21) >> 21), c(0), tex(0) {} 38 | Vertex(u32 v, u32 c) : x((i32)((v & 0x7FF) << 21) >> 21), y((i32)(((v >> 16) & 0x7FF) << 21) >> 21), c(c & 0xFFFFFF), tex(0) {} 39 | Vertex(u32 v, u32 c, u32 tex) : x((i32)((v & 0x7FF) << 21) >> 21), y((i32)(((v >> 16) & 0x7FF) << 21) >> 21), c(c & 0xFFFFFF), tex(tex) {} 40 | 41 | i32 x, y; // Coordinates 42 | 43 | u32 c; // Color 44 | u32 tex; // Tex coord 45 | }; 46 | 47 | /* Texture window */ 48 | struct TexWindow { 49 | u32 maskX, maskY; 50 | u32 ofsX, ofsY; 51 | }; 52 | 53 | /* Drawing area */ 54 | struct XYArea { 55 | i32 x0, x1, y0, y1; 56 | }; 57 | 58 | /* Drawing offset */ 59 | struct XYOffset { 60 | i32 xofs, yofs; 61 | }; 62 | 63 | /* Copy info */ 64 | struct CopyInfo { 65 | u32 cx, cy; // Current X/Y 66 | 67 | u32 xMin, yMin; 68 | u32 xMax, yMax; 69 | }; 70 | 71 | enum GPUState { 72 | ReceiveCommand, 73 | ReceiveArguments, 74 | CopyRectangle, 75 | }; 76 | 77 | GPUState state = GPUState::ReceiveCommand; 78 | int argCount; 79 | 80 | u8 cmd; // Current command 81 | std::queue cmdParam; 82 | 83 | std::vector vram; 84 | 85 | /* GPU drawing parameters */ 86 | XYArea xyarea; 87 | XYOffset xyoffset; 88 | TexWindow texWindow; 89 | 90 | CopyInfo dstCopyInfo, srcCopyInfo; 91 | 92 | i64 lineCounter = 0; 93 | 94 | u32 drawMode; 95 | 96 | u32 gpuread = 0; 97 | u32 gpustat = 7 << 26; 98 | 99 | u64 idHBLANK, idScanline; // Scheduler 100 | 101 | /* Handles HBLANK events */ 102 | void hblankEvent(i64 c) { 103 | timer::stepHBLANK(); 104 | 105 | scheduler::addEvent(idHBLANK, 0, CYCLES_PER_SCANLINE); 106 | } 107 | 108 | /* Handles scanline events */ 109 | void scanlineEvent(i64 c) { 110 | ++lineCounter; 111 | 112 | if (lineCounter < SCANLINES_PER_VDRAW) { 113 | if (lineCounter & 1) { 114 | gpustat |= 1 << 31; 115 | } else { 116 | gpustat &= ~(1 << 31); 117 | } 118 | } else { 119 | gpustat &= ~(1 << 31); 120 | } 121 | 122 | if (lineCounter == SCANLINES_PER_VDRAW) { 123 | intc::sendInterrupt(Interrupt::VBLANK); 124 | 125 | timer::gateVBLANKStart(); 126 | 127 | spu::save(); 128 | 129 | update((u8 *)vram.data()); 130 | } else if (lineCounter == SCANLINES_PER_FRAME) { 131 | timer::gateVBLANKEnd(); 132 | 133 | lineCounter = 0; 134 | } 135 | 136 | scheduler::addEvent(idScanline, 0, CYCLES_PER_SCANLINE); 137 | } 138 | 139 | void setArgCount(int c) { 140 | argCount = c; 141 | 142 | state = GPUState::ReceiveArguments; 143 | } 144 | 145 | /* Converts BGR24 to BGR555 */ 146 | inline u16 toBGR555(u32 c) { 147 | const u16 b = (c >> 19) & 0x1F; 148 | const u16 g = (c >> 11) & 0x1F; 149 | const u16 r = (c >> 3) & 0x1F; 150 | 151 | return (b << 10) | (g << 5) | r; 152 | } 153 | 154 | template 155 | void drawPixel(i32 x, i32 y, u32 c) { 156 | if constexpr (conv) { 157 | vram[x + 1024 * y] = toBGR555(c); 158 | } else { 159 | vram[x + 1024 * y] = c; 160 | } 161 | } 162 | 163 | i32 edgeFunction(const Vertex &a, const Vertex &b, const Vertex &c) { 164 | return (b.x - a.x) * (c.y - a.y) - (b.y - a.y) * (c.x - a.x); 165 | } 166 | 167 | u16 fetchTex(i32 texX, i32 texY, u32 texPage, u32 clut) { 168 | static const u32 texDepth[4] = { 4, 8, 16, 0 }; 169 | 170 | /* Apply tex window */ 171 | texX = (texX & ~texWindow.maskX) | (texWindow.ofsX & texWindow.maskX); 172 | texY = (texY & ~texWindow.maskY) | (texWindow.ofsY & texWindow.maskY); 173 | 174 | /* Get tex page coordinates */ 175 | const auto texPageX = texPage & 0xF; 176 | const auto texPageY = 256 * ((texPage >> 4) & 1); 177 | 178 | const auto depth = texDepth[(texPage >> 7) & 3]; 179 | 180 | u32 x = 0; 181 | 182 | switch (depth) { 183 | case 4: 184 | x = 64 * texPageX + (texX / 4); 185 | break; 186 | case 8: 187 | x = 64 * texPageX + (texX / 2); 188 | break; 189 | case 16: 190 | x = 64 * texPageX + texX; 191 | break; 192 | default: 193 | break; 194 | } 195 | 196 | const auto y = texPageY + texY; 197 | 198 | const auto texel = vram[x + 1024 * y]; 199 | 200 | if (depth == 16) return texel; 201 | 202 | const auto clutX = 16 * (clut & 0x3F); 203 | const auto clutY = (clut >> 6) & 0x1FF; 204 | 205 | const auto clutOfs = (depth == 4) ? (texel >> (4 * (texX & 3))) & 0xF : (texel >> (8 * (texX & 1))) & 0xFF; 206 | 207 | return vram[(clutX + clutOfs) + 1024 * clutY]; 208 | } 209 | 210 | /* Draws a flat shaded triangle */ 211 | void drawFlatTri(const Vertex &v0, const Vertex &v1, const Vertex &v2, u32 color) { 212 | Vertex p, a, b, c; 213 | 214 | a = v0; 215 | 216 | /* Ensure the winding order is correct */ 217 | if (edgeFunction(v0, v1, v2) < 0) { 218 | b = v2; 219 | c = v1; 220 | } else { 221 | b = v1; 222 | c = v2; 223 | } 224 | 225 | /* Offset coordinates */ 226 | a.x += xyoffset.xofs; 227 | b.x += xyoffset.xofs; 228 | c.x += xyoffset.xofs; 229 | a.y += xyoffset.yofs; 230 | b.y += xyoffset.yofs; 231 | c.y += xyoffset.yofs; 232 | 233 | /* Calculate bounding box */ 234 | auto xMin = std::min(a.x, std::min(b.x, c.x)); 235 | auto yMin = std::min(a.y, std::min(b.y, c.y)); 236 | auto xMax = std::max(a.x, std::max(b.x, c.x)); 237 | auto yMax = std::max(a.y, std::max(b.y, c.y)); 238 | 239 | xMin = std::max(xMin, xyarea.x0); 240 | yMin = std::max(yMin, xyarea.y0); 241 | xMax = std::min(xMax, xyarea.x1); 242 | yMax = std::min(yMax, xyarea.y1); 243 | 244 | color = toBGR555(color); 245 | 246 | for (p.y = yMin; p.y < yMax; p.y++) { 247 | for (p.x = xMin; p.x < xMax; p.x++) { 248 | /* Calculate weights */ 249 | const auto w0 = edgeFunction(b, c, p); 250 | const auto w1 = edgeFunction(c, a, p); 251 | const auto w2 = edgeFunction(a, b, p); 252 | 253 | /* Is point inside of triangle ? */ 254 | if ((w0 >= 0) && (w1 >= 0) && (w2 >= 0)) drawPixel(p.x, p.y, color); 255 | } 256 | } 257 | } 258 | 259 | /* Draws a flat rectangle */ 260 | void drawFlatRect(const Vertex &v, i32 w, i32 h, u32 color) { 261 | auto a = v; 262 | 263 | /* Offset coordinates */ 264 | a.x += xyoffset.xofs; 265 | a.y += xyoffset.yofs; 266 | 267 | /* Calculate bounding box */ 268 | auto xMin = std::max(a.x, xyarea.x0); 269 | auto yMin = std::max(a.y, xyarea.y0); 270 | auto xMax = std::min(xMin + w, xyarea.x1); 271 | auto yMax = std::min(yMin + h, xyarea.y1); 272 | 273 | for (auto y = yMin; y < yMax; y++) { 274 | for (auto x = xMin; x < xMax; x++) { 275 | drawPixel(x, y, color); 276 | } 277 | } 278 | } 279 | 280 | /* Draws a Gouraud shaded triangle */ 281 | void drawShadedTri(const Vertex &v0, const Vertex &v1, const Vertex &v2) { 282 | Vertex p, a, b, c; 283 | 284 | a = v0; 285 | 286 | /* Ensure the winding order is correct */ 287 | if (edgeFunction(v0, v1, v2) < 0) { 288 | b = v2; 289 | c = v1; 290 | } else { 291 | b = v1; 292 | c = v2; 293 | } 294 | 295 | /* Offset coordinates */ 296 | a.x += xyoffset.xofs; 297 | b.x += xyoffset.xofs; 298 | c.x += xyoffset.xofs; 299 | a.y += xyoffset.yofs; 300 | b.y += xyoffset.yofs; 301 | c.y += xyoffset.yofs; 302 | 303 | const auto area = edgeFunction(a, b, c); 304 | 305 | /* Calculate bounding box */ 306 | auto xMin = std::min(a.x, std::min(b.x, c.x)); 307 | auto yMin = std::min(a.y, std::min(b.y, c.y)); 308 | auto xMax = std::max(a.x, std::max(b.x, c.x)); 309 | auto yMax = std::max(a.y, std::max(b.y, c.y)); 310 | 311 | xMin = std::max(xMin, xyarea.x0); 312 | yMin = std::max(yMin, xyarea.y0); 313 | xMax = std::min(xMax, xyarea.x1); 314 | yMax = std::min(yMax, xyarea.y1); 315 | 316 | for (p.y = yMin; p.y < yMax; p.y++) { 317 | for (p.x = xMin; p.x < xMax; p.x++) { 318 | /* Calculate weights */ 319 | const auto w0 = edgeFunction(b, c, p); 320 | const auto w1 = edgeFunction(c, a, p); 321 | const auto w2 = edgeFunction(a, b, p); 322 | 323 | /* Is point inside of triangle ? */ 324 | if ((w0 >= 0) && (w1 >= 0) && (w2 >= 0)) { 325 | /* Interpolate color */ 326 | const u32 cr = (w0 * ((a.c >> 0) & 0xFF) + w1 * ((b.c >> 0) & 0xFF) + w2 * ((c.c >> 0) & 0xFF)) / area; 327 | const u32 cg = (w0 * ((a.c >> 8) & 0xFF) + w1 * ((b.c >> 8) & 0xFF) + w2 * ((c.c >> 8) & 0xFF)) / area; 328 | const u32 cb = (w0 * ((a.c >> 16) & 0xFF) + w1 * ((b.c >> 16) & 0xFF) + w2 * ((c.c >> 16) & 0xFF)) / area; 329 | 330 | const auto color = (cb << 16) | (cg << 8) | cr; 331 | 332 | drawPixel(p.x, p.y, color); 333 | } 334 | } 335 | } 336 | } 337 | 338 | /* Draws a textured rectangle */ 339 | void drawTexturedRect(const Vertex &v, i32 w, i32 h, u32 clut) { 340 | auto a = v; 341 | 342 | /* Offset coordinates */ 343 | a.x += xyoffset.xofs; 344 | a.y += xyoffset.yofs; 345 | 346 | /* Calculate bounding box */ 347 | auto xMin = std::max(a.x, xyarea.x0); 348 | auto yMin = std::max(a.y, xyarea.y0); 349 | auto xMax = std::min(xMin + w, xyarea.x1); 350 | auto yMax = std::min(yMin + h, xyarea.y1); 351 | 352 | const u32 texX = (a.tex >> 0) & 0xFF; 353 | const u32 texY = (a.tex >> 8) & 0xFF; 354 | 355 | u32 xc = 0, yc = 0; 356 | 357 | for (auto y = yMin; y < yMax; y++) { 358 | for (auto x = xMin; x < xMax; x++) { 359 | const auto color = fetchTex(texX + xc, texY + yc, drawMode, clut); 360 | 361 | ++xc; 362 | 363 | /* TODO: handle semi-transparency/blending */ 364 | if (!color) continue; 365 | 366 | drawPixel(x, y, color); 367 | } 368 | 369 | xc = 0; 370 | 371 | ++yc; 372 | } 373 | } 374 | 375 | /* Draws a textured triangle */ 376 | void drawTexturedTri(const Vertex &v0, const Vertex &v1, const Vertex &v2, u32 clut, u32 texPage) { 377 | Vertex p, a, b, c; 378 | 379 | a = v0; 380 | 381 | /* Ensure the winding order is correct */ 382 | if (edgeFunction(v0, v1, v2) < 0) { 383 | b = v2; 384 | c = v1; 385 | } else { 386 | b = v1; 387 | c = v2; 388 | } 389 | 390 | /* Offset coordinates */ 391 | a.x += xyoffset.xofs; 392 | b.x += xyoffset.xofs; 393 | c.x += xyoffset.xofs; 394 | a.y += xyoffset.yofs; 395 | b.y += xyoffset.yofs; 396 | c.y += xyoffset.yofs; 397 | 398 | const auto area = edgeFunction(a, b, c); 399 | 400 | /* Calculate bounding box */ 401 | auto xMin = std::min(a.x, std::min(b.x, c.x)); 402 | auto yMin = std::min(a.y, std::min(b.y, c.y)); 403 | auto xMax = std::max(a.x, std::max(b.x, c.x)); 404 | auto yMax = std::max(a.y, std::max(b.y, c.y)); 405 | 406 | xMin = std::max(xMin, xyarea.x0); 407 | yMin = std::max(yMin, xyarea.y0); 408 | xMax = std::min(xMax, xyarea.x1); 409 | yMax = std::min(yMax, xyarea.y1); 410 | 411 | for (p.y = yMin; p.y < yMax; p.y++) { 412 | for (p.x = xMin; p.x < xMax; p.x++) { 413 | /* Calculate weights */ 414 | const auto w0 = edgeFunction(b, c, p); 415 | const auto w1 = edgeFunction(c, a, p); 416 | const auto w2 = edgeFunction(a, b, p); 417 | 418 | /* Is point inside of triangle ? */ 419 | if ((w0 >= 0) && (w1 >= 0) && (w2 >= 0)) { 420 | /* Interpolate tex coords */ 421 | const u32 texX = (w0 * ((a.tex >> 0) & 0xFF) + w1 * ((b.tex >> 0) & 0xFF) + w2 * ((c.tex >> 0) & 0xFF)) / area; 422 | const u32 texY = (w0 * ((a.tex >> 8) & 0xFF) + w1 * ((b.tex >> 8) & 0xFF) + w2 * ((c.tex >> 8) & 0xFF)) / area; 423 | 424 | const auto color = fetchTex(texX, texY, texPage, clut); 425 | 426 | /* TODO: handle semi-transparency/blending */ 427 | if (!color) continue; 428 | 429 | drawPixel(p.x, p.y, color); 430 | } 431 | } 432 | } 433 | } 434 | 435 | /* GP0(0x02) Fill Rectangle */ 436 | void fillRect() { 437 | /* Convert 24-bit to 15-bit here to speed up things */ 438 | const auto c = toBGR555(cmdParam.front() & 0xFFFFFF); cmdParam.pop(); 439 | 440 | const auto coords = cmdParam.front(); cmdParam.pop(); 441 | const auto dims = cmdParam.front(); cmdParam.pop(); 442 | 443 | auto x0 = 16 * ((coords >> 0) & 0xFFFF); // In 16px units 444 | auto y0 = 16 * ((coords >> 16) & 0xFFFF); // In 16px units 445 | 446 | const auto width = 16 * ((dims >> 0) & 0xFFFF); // In 16px units 447 | const auto height = 16 * ((dims >> 16) & 0xFFFF); // In 16px units 448 | 449 | /* Calculate rectangle */ 450 | const auto xMin = std::max((i32)x0, xyarea.x0); 451 | const auto yMin = std::max((i32)y0, xyarea.y0); 452 | const auto xMax = std::min((i32)(width + x0), xyarea.x1); 453 | const auto yMax = std::min((i32)(height + y0), xyarea.y1); 454 | 455 | for (auto y = yMin; y < yMax; y++) 456 | { 457 | for (auto x = xMin; x < xMax; x++) 458 | { 459 | drawPixel(x, y, c); 460 | } 461 | } 462 | 463 | state = GPUState::ReceiveCommand; 464 | } 465 | 466 | /* GP0(0x20) Draw Flat Tri (opaque) */ 467 | void drawTri20() { 468 | const auto color = cmdParam.front(); cmdParam.pop(); 469 | 470 | const auto v0 = cmdParam.front(); cmdParam.pop(); 471 | const auto v1 = cmdParam.front(); cmdParam.pop(); 472 | const auto v2 = cmdParam.front(); cmdParam.pop(); 473 | 474 | drawFlatTri(Vertex(v0), Vertex(v1), Vertex(v2), color); 475 | 476 | state = GPUState::ReceiveCommand; 477 | } 478 | 479 | /* GP0(0x24) Draw Textured Tri */ 480 | void drawTri24() { 481 | const auto c = cmdParam.front(); cmdParam.pop(); 482 | 483 | Vertex v[3]; 484 | 485 | for (int i = 0; i < 3; i++) { 486 | const auto v0 = cmdParam.front(); cmdParam.pop(); 487 | const auto t0 = cmdParam.front(); cmdParam.pop(); 488 | 489 | v[i] = Vertex(v0, c, t0); 490 | } 491 | 492 | const auto clut = v[0].tex >> 16; 493 | 494 | u32 texPage; 495 | if (edgeFunction(v[0], v[1], v[2]) < 0) { 496 | texPage = v[2].tex >> 16; 497 | } else { 498 | texPage = v[1].tex >> 16; 499 | } 500 | 501 | drawTexturedTri(v[0], v[1], v[2], clut, texPage); 502 | 503 | state = GPUState::ReceiveCommand; 504 | } 505 | 506 | /* GP0(0x30) Draw Shaded Triangle (opaque) */ 507 | void drawTri30() { 508 | const auto c0 = cmdParam.front(); cmdParam.pop(); 509 | const auto v0 = cmdParam.front(); cmdParam.pop(); 510 | const auto c1 = cmdParam.front(); cmdParam.pop(); 511 | const auto v1 = cmdParam.front(); cmdParam.pop(); 512 | const auto c2 = cmdParam.front(); cmdParam.pop(); 513 | const auto v2 = cmdParam.front(); cmdParam.pop(); 514 | 515 | drawShadedTri(Vertex(v0, c0), Vertex(v1, c1), Vertex(v2, c2)); 516 | 517 | state = GPUState::ReceiveCommand; 518 | } 519 | 520 | /* GP0(0x34) Draw Shaded Textured Triangle */ 521 | void drawTri34() { 522 | Vertex v[3]; 523 | 524 | for (int i = 0; i < 3; i++) { 525 | const auto c0 = cmdParam.front(); cmdParam.pop(); 526 | const auto v0 = cmdParam.front(); cmdParam.pop(); 527 | const auto t0 = cmdParam.front(); cmdParam.pop(); 528 | 529 | v[i] = Vertex(v0, c0, t0); 530 | } 531 | 532 | const auto clut = v[0].tex >> 16; 533 | 534 | u32 texPage; 535 | if (edgeFunction(v[0], v[1], v[2]) < 0) { 536 | texPage = v[2].tex >> 16; 537 | } else { 538 | texPage = v[1].tex >> 16; 539 | } 540 | 541 | drawTexturedTri(v[0], v[1], v[2], clut, texPage); 542 | 543 | state = GPUState::ReceiveCommand; 544 | } 545 | 546 | /* GP0(0x28) Draw Flat Quadrilateral (opaque) */ 547 | void drawQuad28() { 548 | const auto color = cmdParam.front(); cmdParam.pop(); 549 | 550 | const auto v0 = cmdParam.front(); cmdParam.pop(); 551 | const auto v1 = cmdParam.front(); cmdParam.pop(); 552 | const auto v2 = cmdParam.front(); cmdParam.pop(); 553 | const auto v3 = cmdParam.front(); cmdParam.pop(); 554 | 555 | drawFlatTri(Vertex(v0), Vertex(v1), Vertex(v2), color); 556 | drawFlatTri(Vertex(v1), Vertex(v2), Vertex(v3), color); 557 | 558 | state = GPUState::ReceiveCommand; 559 | } 560 | 561 | /* GP0(0x2C) Draw Textured Quadrilateral (semi-transparent, blended) */ 562 | void drawQuad2C() { 563 | const auto c = cmdParam.front(); cmdParam.pop(); 564 | 565 | Vertex v[4]; 566 | 567 | for (int i = 0; i < 4; i++) { 568 | const auto v0 = cmdParam.front(); cmdParam.pop(); 569 | const auto t0 = cmdParam.front(); cmdParam.pop(); 570 | 571 | v[i] = Vertex(v0, c, t0); 572 | } 573 | 574 | const auto clut = v[0].tex >> 16; 575 | 576 | u32 texPage; 577 | if (edgeFunction(v[0], v[1], v[2]) < 0) { 578 | texPage = v[2].tex >> 16; 579 | } else { 580 | texPage = v[1].tex >> 16; 581 | } 582 | 583 | drawTexturedTri(v[0], v[1], v[2], clut, texPage); 584 | drawTexturedTri(v[1], v[2], v[3], clut, texPage); 585 | 586 | state = GPUState::ReceiveCommand; 587 | } 588 | 589 | /* GP0(0x38) Draw Shaded Quadrilateral (opaque) */ 590 | void drawQuad38() { 591 | const auto c0 = cmdParam.front(); cmdParam.pop(); 592 | const auto v0 = cmdParam.front(); cmdParam.pop(); 593 | const auto c1 = cmdParam.front(); cmdParam.pop(); 594 | const auto v1 = cmdParam.front(); cmdParam.pop(); 595 | const auto c2 = cmdParam.front(); cmdParam.pop(); 596 | const auto v2 = cmdParam.front(); cmdParam.pop(); 597 | const auto c3 = cmdParam.front(); cmdParam.pop(); 598 | const auto v3 = cmdParam.front(); cmdParam.pop(); 599 | 600 | drawShadedTri(Vertex(v0, c0), Vertex(v1, c1), Vertex(v2, c2)); 601 | drawShadedTri(Vertex(v1, c1), Vertex(v2, c2), Vertex(v3, c3)); 602 | 603 | state = GPUState::ReceiveCommand; 604 | } 605 | 606 | /* GP0(0x3E) Draw Shaded Textured Quadrilateral */ 607 | void drawQuad3E() { 608 | Vertex v[4]; 609 | 610 | for (int i = 0; i < 4; i++) { 611 | const auto c0 = cmdParam.front(); cmdParam.pop(); 612 | const auto v0 = cmdParam.front(); cmdParam.pop(); 613 | const auto t0 = cmdParam.front(); cmdParam.pop(); 614 | 615 | v[i] = Vertex(v0, c0, t0); 616 | } 617 | 618 | const auto clut = v[0].tex >> 16; 619 | 620 | u32 texPage; 621 | if (edgeFunction(v[0], v[1], v[2]) < 0) { 622 | texPage = v[2].tex >> 16; 623 | } else { 624 | texPage = v[1].tex >> 16; 625 | } 626 | 627 | drawTexturedTri(v[0], v[1], v[2], clut, texPage); 628 | drawTexturedTri(v[1], v[2], v[3], clut, texPage); 629 | 630 | state = GPUState::ReceiveCommand; 631 | } 632 | 633 | /* GP0(0x60) Draw Flat Rectangle (variable) */ 634 | void drawRect60() { 635 | const auto c = cmdParam.front(); cmdParam.pop(); 636 | const auto v = cmdParam.front(); cmdParam.pop(); 637 | 638 | const auto dims = cmdParam.front(); cmdParam.pop(); 639 | 640 | Vertex v0 = Vertex(v, c); 641 | 642 | drawFlatRect(v0, dims & 0xFFFF, dims >> 16, c); 643 | 644 | state = GPUState::ReceiveCommand; 645 | } 646 | 647 | /* GP0(0x65) Draw Textured Rectangle (variable, opaque) */ 648 | void drawRect65() { 649 | const auto c = cmdParam.front(); cmdParam.pop(); 650 | const auto v = cmdParam.front(); cmdParam.pop(); 651 | const auto t = cmdParam.front(); cmdParam.pop(); 652 | 653 | const auto dims = cmdParam.front(); cmdParam.pop(); 654 | 655 | Vertex v0 = Vertex(v, c, t); 656 | 657 | const auto clut = v0.tex >> 16; 658 | 659 | drawTexturedRect(v0, dims & 0xFFFF, dims >> 16, clut); 660 | 661 | state = GPUState::ReceiveCommand; 662 | } 663 | 664 | /* GP0(0x68) Draw Flat Rectangle (1x1) */ 665 | void drawRect68() { 666 | const auto c = cmdParam.front(); cmdParam.pop(); 667 | const auto v = cmdParam.front(); cmdParam.pop(); 668 | 669 | Vertex v0 = Vertex(v, c); 670 | 671 | drawFlatRect(v0, 1, 1, c); 672 | 673 | state = GPUState::ReceiveCommand; 674 | } 675 | 676 | /* GP0(0x74) Draw Textured Rectangle (8x8, opaque) */ 677 | void drawRect74() { 678 | const auto c = cmdParam.front(); cmdParam.pop(); 679 | const auto v = cmdParam.front(); cmdParam.pop(); 680 | const auto t = cmdParam.front(); cmdParam.pop(); 681 | 682 | Vertex v0 = Vertex(v, c, t); 683 | 684 | const auto clut = v0.tex >> 16; 685 | 686 | drawTexturedRect(v0, 8, 8, clut); 687 | 688 | state = GPUState::ReceiveCommand; 689 | } 690 | 691 | /* GP0(0x78) Draw Flat Rectangle (8x8) */ 692 | void drawRect78() { 693 | const auto c = cmdParam.front(); cmdParam.pop(); 694 | const auto v = cmdParam.front(); cmdParam.pop(); 695 | 696 | Vertex v0 = Vertex(v, c); 697 | 698 | drawFlatRect(v0, 8, 8, c); 699 | 700 | state = GPUState::ReceiveCommand; 701 | } 702 | 703 | /* GP0(0x7C) Draw Textured Rectangle (16x16, opaque) */ 704 | void drawRect7C() { 705 | const auto c = cmdParam.front(); cmdParam.pop(); 706 | const auto v = cmdParam.front(); cmdParam.pop(); 707 | const auto t = cmdParam.front(); cmdParam.pop(); 708 | 709 | Vertex v0 = Vertex(v, c, t); 710 | 711 | const auto clut = v0.tex >> 16; 712 | 713 | drawTexturedRect(v0, 16, 16, clut); 714 | 715 | state = GPUState::ReceiveCommand; 716 | } 717 | 718 | /* GP0(0xA0) Copy Rectangle (CPU->VRAM) */ 719 | void copyCPUToVRAM() { 720 | const auto coords = cmdParam.front(); cmdParam.pop(); 721 | const auto dims = cmdParam.front(); cmdParam.pop(); 722 | 723 | dstCopyInfo.xMin = (coords >> 0) & 0xFFFF; 724 | dstCopyInfo.yMin = (coords >> 16) & 0xFFFF; 725 | 726 | dstCopyInfo.xMax = ((dims >> 0) & 0xFFFF) + dstCopyInfo.xMin; 727 | dstCopyInfo.yMax = (dims >> 16) & 0xFFFF; 728 | 729 | argCount = (((dims >> 16) * (dims & 0xFFFF) + 1) & ~1) / 2; 730 | 731 | dstCopyInfo.cx = dstCopyInfo.xMin; 732 | dstCopyInfo.cy = dstCopyInfo.yMin; 733 | 734 | state = GPUState::CopyRectangle; 735 | } 736 | 737 | /* GP0(0xC0) Copy Rectangle (VRAM->CPU) */ 738 | void copyVRAMToCPU() { 739 | const auto coords = cmdParam.front(); cmdParam.pop(); 740 | const auto dims = cmdParam.front(); cmdParam.pop(); 741 | 742 | srcCopyInfo.xMin = (coords >> 0) & 0xFFFF; 743 | srcCopyInfo.yMin = (coords >> 16) & 0xFFFF; 744 | 745 | srcCopyInfo.xMax = ((dims >> 0) & 0xFFFF) + srcCopyInfo.xMin; 746 | srcCopyInfo.yMax = (dims >> 16) & 0xFFFF; 747 | 748 | argCount = (((dims >> 16) * (dims & 0xFFFF) + 1) & ~1) / 2; 749 | 750 | srcCopyInfo.cx = srcCopyInfo.xMin; 751 | srcCopyInfo.cy = srcCopyInfo.yMin; 752 | 753 | state = GPUState::CopyRectangle; 754 | } 755 | 756 | /* GP0(0x80) Copy Rectangle (VRAM->VRAM) */ 757 | void copyVRAMToVRAM() { 758 | const auto srcCoord = cmdParam.front(); cmdParam.pop(); 759 | const auto dstCoord = cmdParam.front(); cmdParam.pop(); 760 | const auto dims = cmdParam.front(); cmdParam.pop(); 761 | 762 | /* Set up transfer */ 763 | 764 | dstCopyInfo.xMin = (dstCoord >> 0) & 0xFFFF; 765 | dstCopyInfo.yMin = (dstCoord >> 16) & 0xFFFF; 766 | 767 | dstCopyInfo.cx = dstCopyInfo.xMin; 768 | dstCopyInfo.cy = dstCopyInfo.yMin; 769 | 770 | srcCopyInfo.xMin = (srcCoord >> 0) & 0xFFFF; 771 | srcCopyInfo.yMin = (srcCoord >> 16) & 0xFFFF; 772 | 773 | srcCopyInfo.xMax = ((dims >> 0) & 0xFFFF) + srcCopyInfo.xMin; 774 | srcCopyInfo.yMax = (dims >> 16) & 0xFFFF; 775 | 776 | srcCopyInfo.cx = srcCopyInfo.xMin; 777 | srcCopyInfo.cy = srcCopyInfo.yMin; 778 | 779 | /* Copy data */ 780 | 781 | while (true) { 782 | vram[dstCopyInfo.cx + 1024 * dstCopyInfo.cy] = vram[srcCopyInfo.cx + 1024 * srcCopyInfo.cy]; 783 | 784 | srcCopyInfo.cx++; 785 | dstCopyInfo.cx++; 786 | 787 | if (srcCopyInfo.cx >= srcCopyInfo.xMax) { 788 | srcCopyInfo.cy++; 789 | dstCopyInfo.cy++; 790 | 791 | if (srcCopyInfo.cy >= srcCopyInfo.yMax) break; 792 | 793 | srcCopyInfo.cx = srcCopyInfo.xMin; 794 | dstCopyInfo.cx = dstCopyInfo.xMin; 795 | } 796 | 797 | vram[dstCopyInfo.cx + 1024 * dstCopyInfo.cy] = vram[srcCopyInfo.cx + 1024 * srcCopyInfo.cy]; 798 | 799 | srcCopyInfo.cx++; 800 | dstCopyInfo.cx++; 801 | 802 | if (srcCopyInfo.cx >= srcCopyInfo.xMax) { 803 | srcCopyInfo.cy++; 804 | dstCopyInfo.cy++; 805 | 806 | if (srcCopyInfo.cy >= srcCopyInfo.yMax) break; 807 | 808 | srcCopyInfo.cx = srcCopyInfo.xMin; 809 | dstCopyInfo.cx = dstCopyInfo.xMin; 810 | } 811 | } 812 | 813 | state = GPUState::ReceiveCommand; 814 | } 815 | 816 | void init() { 817 | idHBLANK = scheduler::registerEvent([](int, i64 c) { hblankEvent(c); }); 818 | idScanline = scheduler::registerEvent([](int, i64 c) { scanlineEvent(c); }); 819 | 820 | vram.resize(VRAM_WIDTH * VRAM_HEIGHT); 821 | 822 | scheduler::addEvent(idHBLANK, 0, CYCLES_PER_HDRAW); 823 | scheduler::addEvent(idScanline, 0, CYCLES_PER_SCANLINE); 824 | } 825 | 826 | u32 readGPUREAD() { 827 | u32 data; 828 | 829 | if (state != GPUState::CopyRectangle) { 830 | data = gpuread; 831 | 832 | gpuread = 0; 833 | 834 | return data; 835 | } 836 | 837 | auto &c = srcCopyInfo; 838 | 839 | data = vram[c.cx + 1024 * c.cy]; 840 | 841 | c.cx++; 842 | 843 | if (c.cx == c.xMax) { 844 | c.cy++; 845 | 846 | c.cx = c.xMin; 847 | } 848 | 849 | data |= (u32)vram[c.cx + 1024 * c.cy] << 16; 850 | 851 | c.cx++; 852 | 853 | if (c.cx == c.xMax) { 854 | c.cy++; 855 | 856 | c.cx = c.xMin; 857 | } 858 | 859 | if (!--argCount) state = GPUState::ReceiveCommand; 860 | 861 | return data; 862 | } 863 | 864 | void writeGP0(u32 data) { 865 | switch (state) { 866 | case GPUState::ReceiveCommand: 867 | { 868 | cmd = data >> 24; 869 | 870 | switch (cmd) { 871 | case 0x00: 872 | //std::printf("[GPU:GP0 ] NOP\n"); 873 | break; 874 | case 0x01: 875 | //std::printf("[GPU:GP0 ] Clear Cache\n"); 876 | break; 877 | case 0x02: 878 | //std::printf("[GPU:GP0 ] Fill VRAM\n"); 879 | 880 | cmdParam.push(data); // Also first argument 881 | 882 | setArgCount(2); 883 | break; 884 | case 0x1F: 885 | //std::printf("[GPU:GP0 ] Request Interrupt (0x%08X)\n", data); 886 | 887 | gpustat |= 1 << 24; 888 | 889 | intc::sendInterrupt(Interrupt::GPU); 890 | break; 891 | case 0x20: 892 | case 0x22: 893 | //std::printf("[GPU:GP0 ] Draw Flat Tri (opaque)\n"); 894 | 895 | cmdParam.push(data); // Also first argument 896 | 897 | setArgCount(3); 898 | break; 899 | case 0x24: 900 | case 0x25: 901 | case 0x26: 902 | case 0x27: 903 | //std::printf("[GPU:GP0 ] Draw Textured Tri\n"); 904 | 905 | cmdParam.push(data); // Also first argument 906 | 907 | setArgCount(6); 908 | break; 909 | case 0x28: 910 | case 0x29: 911 | case 0x2A: 912 | case 0x2B: 913 | //std::printf("[GPU:GP0 ] Draw Flat Quad (opaque)\n"); 914 | 915 | cmdParam.push(data); // Also first argument 916 | 917 | setArgCount(4); 918 | break; 919 | case 0x2C: 920 | case 0x2D: 921 | case 0x2E: 922 | case 0x2F: 923 | //std::printf("[GPU:GP0 ] Draw Textured Quad (semi-transparent, blended)\n"); 924 | 925 | cmdParam.push(data); // Also first argument 926 | 927 | setArgCount(8); 928 | break; 929 | case 0x30: 930 | case 0x32: 931 | //std::printf("[GPU:GP0 ] Draw Shaded Tri (opaque)\n"); 932 | 933 | cmdParam.push(data); // Also first argument 934 | 935 | setArgCount(5); 936 | break; 937 | case 0x34: 938 | case 0x36: 939 | //std::printf("[GPU:GP0 ] Draw Shaded Textured Tri\n"); 940 | 941 | cmdParam.push(data); // Also first argument 942 | 943 | setArgCount(8); 944 | break; 945 | case 0x38: 946 | case 0x3A: 947 | //std::printf("[GPU:GP0 ] Draw Shaded Quad (opaque)\n"); 948 | 949 | cmdParam.push(data); // Also first argument 950 | 951 | setArgCount(7); 952 | break; 953 | case 0x3C: 954 | case 0x3E: 955 | //std::printf("[GPU:GP0 ] Draw Shaded Textured Quad (opaque)\n"); 956 | 957 | cmdParam.push(data); // Also first argument 958 | 959 | setArgCount(11); 960 | break; 961 | case 0x40: 962 | case 0x42: 963 | setArgCount(2); 964 | break; 965 | case 0x60: 966 | case 0x62: 967 | //std::printf("[GPU:GP0 ] Draw Flat Rectangle (variable)\n"); 968 | 969 | cmdParam.push(data); // Also first argument 970 | 971 | setArgCount(2); 972 | break; 973 | case 0x64: 974 | case 0x65: 975 | case 0x66: 976 | case 0x67: 977 | //std::printf("[GPU:GP0 ] Draw Textured Rectangle (variable, opaque)\n"); 978 | 979 | cmdParam.push(data); // Also first argument 980 | 981 | setArgCount(3); 982 | break; 983 | case 0x68: 984 | //std::printf("[GPU:GP0 ] Draw Flat Rectangle (1x1)\n"); 985 | 986 | cmdParam.push(data); // Also first argument 987 | 988 | setArgCount(1); 989 | break; 990 | case 0x78: 991 | //std::printf("[GPU:GP0 ] Draw Flat Rectangle (8x8)\n"); 992 | 993 | cmdParam.push(data); // Also first argument 994 | 995 | setArgCount(1); 996 | break; 997 | case 0x74: 998 | case 0x75: 999 | //std::printf("[GPU:GP0 ] Draw Textured Rectangle (8x8)\n"); 1000 | 1001 | cmdParam.push(data); // Also first argument 1002 | 1003 | setArgCount(2); 1004 | break; 1005 | case 0x7C: 1006 | case 0x7D: 1007 | //std::printf("[GPU:GP0 ] Draw Textured Rectangle (16x16)\n"); 1008 | 1009 | cmdParam.push(data); // Also first argument 1010 | 1011 | setArgCount(2); 1012 | break; 1013 | case 0x80: 1014 | case 0x8E: 1015 | //std::printf("[GPU:GP0 ] Copy Rectangle (VRAM->VRAM)\n"); 1016 | 1017 | setArgCount(3); 1018 | break; 1019 | case 0xA0: 1020 | case 0xB1: 1021 | //std::printf("[GPU:GP0 ] Copy Rectangle (CPU->VRAM)\n"); 1022 | 1023 | setArgCount(2); 1024 | break; 1025 | case 0xC0: 1026 | //std::printf("[GPU:GP0 ] Copy Rectangle (VRAM->CPU)\n"); 1027 | 1028 | setArgCount(2); 1029 | break; 1030 | case 0xE1: 1031 | //std::printf("[GPU:GP0 ] Set Draw Mode\n"); 1032 | 1033 | drawMode = data & 0xFFFFFF; 1034 | break; 1035 | case 0xE2: 1036 | //std::printf("[GPU:GP0 ] Set Texture Window\n"); 1037 | 1038 | texWindow.maskX = 8 * ((data >> 0) & 0x1F); 1039 | texWindow.maskY = 8 * ((data >> 5) & 0x1F); 1040 | texWindow.ofsX = 8 * ((data >> 10) & 0x1F); 1041 | texWindow.ofsY = 8 * ((data >> 15) & 0x1F); 1042 | break; 1043 | case 0xE3: 1044 | //std::printf("[GPU:GP0 ] Set Drawing Area (TL)\n"); 1045 | 1046 | xyarea.x0 = (data >> 0) & 0x3FF; 1047 | xyarea.y0 = (data >> 10) & 0x1FF; 1048 | break; 1049 | case 0xE4: 1050 | //std::printf("[GPU:GP0 ] Set Drawing Area (BR)\n"); 1051 | 1052 | xyarea.x1 = (data >> 0) & 0x3FF; 1053 | xyarea.y1 = (data >> 10) & 0x1FF; 1054 | break; 1055 | case 0xE5: 1056 | //std::printf("[GPU:GP0 ] Set Drawing Offset\n"); 1057 | 1058 | xyoffset.xofs = ((i32)(((data >> 0) & 0x7FF) << 21) >> 21); 1059 | xyoffset.yofs = ((i32)(((data >> 11) & 0x7FF) << 21) >> 21); 1060 | break; 1061 | case 0xE6: 1062 | //std::printf("[GPU:GP0 ] Set Mask Bit\n"); 1063 | break; 1064 | case 0x06: 1065 | case 0xFF: // ??? 1066 | //std::printf("[GPU:GP0 ] Invalid command 0x%02X (0x%08X)\n", cmd, data); 1067 | break; 1068 | default: 1069 | std::printf("[GPU ] Unhandled GP0 command 0x%02X (0x%08X)\n", cmd, data); 1070 | 1071 | exit(0); 1072 | } 1073 | } 1074 | break; 1075 | case GPUState::ReceiveArguments: 1076 | ////std::printf("[GPU:GP0 ] 0x%08X\n", data); 1077 | 1078 | cmdParam.push(data); 1079 | 1080 | if (!--argCount) { 1081 | switch (cmd) { 1082 | case 0x02: fillRect(); break; 1083 | case 0x20: 1084 | case 0x22: 1085 | drawTri20(); 1086 | break; 1087 | case 0x24: 1088 | case 0x25: 1089 | case 0x26: 1090 | case 0x27: 1091 | drawTri24(); 1092 | break; 1093 | case 0x28: 1094 | case 0x29: 1095 | case 0x2A: 1096 | case 0x2B: 1097 | drawQuad28(); 1098 | break; 1099 | case 0x2C: 1100 | case 0x2D: 1101 | case 0x2E: 1102 | case 0x2F: 1103 | drawQuad2C(); 1104 | break; 1105 | case 0x30: 1106 | case 0x32: 1107 | drawTri30(); 1108 | break; 1109 | case 0x34: 1110 | case 0x36: 1111 | drawTri34(); 1112 | break; 1113 | case 0x38: 1114 | case 0x3A: 1115 | drawQuad38(); 1116 | break; 1117 | case 0x3C: 1118 | case 0x3E: 1119 | drawQuad3E(); 1120 | break; 1121 | case 0x60: 1122 | case 0x62: 1123 | drawRect60(); 1124 | break; 1125 | case 0x64: 1126 | case 0x65: 1127 | case 0x66: 1128 | case 0x67: 1129 | drawRect65(); 1130 | break; 1131 | case 0x68: drawRect68(); break; 1132 | case 0x74: 1133 | case 0x75: 1134 | drawRect74(); 1135 | break; 1136 | case 0x78: drawRect78(); break; 1137 | case 0x7C: 1138 | case 0x7D: 1139 | drawRect7C(); 1140 | break; 1141 | case 0x80: 1142 | case 0x8E: 1143 | copyVRAMToVRAM(); 1144 | break; 1145 | case 0xA0: 1146 | case 0xB1: 1147 | copyCPUToVRAM(); 1148 | break; 1149 | case 0xC0: copyVRAMToCPU(); break; 1150 | default: 1151 | while (cmdParam.size()) cmdParam.pop(); 1152 | 1153 | state = GPUState::ReceiveCommand; 1154 | } 1155 | } 1156 | break; 1157 | case GPUState::CopyRectangle: 1158 | { 1159 | auto &c = dstCopyInfo; 1160 | 1161 | ////std::printf("[GPU:GP0 ] [0x%08X] = 0x%04X\n", c.cx + 1024 * c.cy, data & 0xFFFF); 1162 | 1163 | vram[c.cx + 1024 * c.cy] = data; 1164 | 1165 | c.cx++; 1166 | 1167 | if (c.cx >= c.xMax) { 1168 | c.cy++; 1169 | 1170 | c.cx = c.xMin; 1171 | } 1172 | 1173 | ////std::printf("[GPU:GP0 ] [0x%08X] = 0x%04X\n", c.cx + 1024 * c.cy, data >> 16); 1174 | 1175 | vram[c.cx + 1024 * c.cy] = data >> 16; 1176 | 1177 | c.cx++; 1178 | 1179 | if (c.cx >= c.xMax) { 1180 | c.cy++; 1181 | 1182 | c.cx = c.xMin; 1183 | } 1184 | 1185 | if (!--argCount) state = GPUState::ReceiveCommand; 1186 | } 1187 | break; 1188 | default: 1189 | exit(0); 1190 | } 1191 | } 1192 | 1193 | u32 readStatus() { 1194 | return gpustat; 1195 | } 1196 | 1197 | void writeGP1(u32 data) { 1198 | const auto cmd = data >> 24; 1199 | 1200 | switch (cmd) { 1201 | case 0x00: 1202 | //std::printf("[GPU:GP1 ] Reset GPU\n"); 1203 | break; 1204 | case 0x01: 1205 | //std::printf("[GPU:GP1 ] Reset Command Buffer\n"); 1206 | break; 1207 | case 0x02: 1208 | //std::printf("[GPU:GP1 ] Ack GPU Interrupt\n"); 1209 | break; 1210 | case 0x03: 1211 | //std::printf("[GPU:GP1 ] Enable Display\n"); 1212 | break; 1213 | case 0x04: 1214 | //std::printf("[GPU:GP1 ] Set DMA Direction\n"); 1215 | break; 1216 | case 0x05: 1217 | //std::printf("[GPU:GP1 ] Set Display Area\n"); 1218 | break; 1219 | case 0x06: 1220 | //std::printf("[GPU:GP1 ] Set Horizontal Range\n"); 1221 | break; 1222 | case 0x07: 1223 | //std::printf("[GPU:GP1 ] Set Vertical Range\n"); 1224 | break; 1225 | case 0x08: 1226 | //std::printf("[GPU:GP1 ] Set Display Mode\n"); 1227 | break; 1228 | case 0x10: 1229 | //std::printf("[GPU:GP1 ] Get GPU Info\n"); 1230 | 1231 | switch (data & 7) { 1232 | case 2: // Texture Window 1233 | gpuread = ((texWindow.ofsY / 8) << 15) | ((texWindow.ofsX / 8) << 10) | ((texWindow.maskY / 8) << 5) | (texWindow.ofsX / 8); 1234 | break; 1235 | case 3: 1236 | gpuread = (xyarea.y0 << 10) | xyarea.x0; 1237 | break; 1238 | case 4: 1239 | gpuread = (xyarea.y1 << 10) | xyarea.x1; 1240 | break; 1241 | case 5: // Drawing Offset 1242 | break; 1243 | default: 1244 | break; 1245 | } 1246 | break; 1247 | default: 1248 | std::printf("[GPU ] Unhandled GP1 command 0x%02X (0x%08X)\n", cmd, data); 1249 | 1250 | exit(0); 1251 | } 1252 | } 1253 | 1254 | } 1255 | --------------------------------------------------------------------------------