/** ** Supermodel ** A Sega Model 3 Arcade Emulator. ** Copyright 2011 Bart Trzynadlowski, Nik Henson ** ** This file is part of Supermodel. ** ** Supermodel is free software: you can redistribute it and/or modify it under ** the terms of the GNU General Public License as published by the Free ** Software Foundation, either version 3 of the License, or (at your option) ** any later version. ** ** Supermodel is distributed in the hope that it will be useful, but WITHOUT ** ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or ** FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for ** more details. ** ** You should have received a copy of the GNU General Public License along ** with Supermodel. If not, see . **/ /* * TileGen.cpp * * Implementation of the CTileGen class: 2D tile generator. * * TO-DO List: * ----------- * - For consistency, the registers should probably be byte reversed (this is a * little endian device), forcing the Model3 Read32/Write32 handlers to * manually reverse the data. This keeps with the convention for VRAM. */ #include #include "Supermodel.h" // Macros that divide memory regions into pages and mark them as dirty when they are written to #define PAGE_WIDTH 10 #define PAGE_SIZE (1<>(PAGE_WIDTH+3)] |= 1<<((addr>>PAGE_WIDTH)&7) // Offsets of memory regions within TileGen memory pool #define OFFSET_VRAM 0x000000 #define OFFSET_PAL 0x120000 #define MEM_POOL_SIZE_RW (0x120000+0x020000) #define OFFSET_VRAM_RO 0x140000 // [read-only snapshot] #define OFFSET_PAL_RO 0x260000 // [read-only snapshot] #define MEM_POOL_SIZE_RO (0x120000+0x020000) #define OFFSET_VRAM_DIRTY 0x280000 #define OFFSET_PAL_DIRTY (OFFSET_VRAM_DIRTY+DIRTY_SIZE(0x120000)) #define MEM_POOL_SIZE_DIRTY (DIRTY_SIZE(MEM_POOL_SIZE_RO)) #define MEMORY_POOL_SIZE (MEM_POOL_SIZE_RW+MEM_POOL_SIZE_RO+MEM_POOL_SIZE_DIRTY) /****************************************************************************** Save States ******************************************************************************/ void CTileGen::SaveState(CBlockFile *SaveState) { SaveState->NewBlock("Tile Generator", __FILE__); SaveState->Write(vram, 0x120000); // Don't write out palette, read-only snapshots or dirty page arrays, just VRAM SaveState->Write(regs, sizeof(regs)); } void CTileGen::LoadState(CBlockFile *SaveState) { if (OKAY != SaveState->FindBlock("Tile Generator")) { ErrorLog("Unable to load tile generator state. Save state file is corrupt."); return; } // Load memory one word at a time for (int i = 0; i < 0x120000; i += 4) { UINT32 data; SaveState->Read(&data, sizeof(data)); WriteRAM(i, data); } SaveState->Read(regs, sizeof(regs)); // If multi-threaded, update read-only snapshots too if (g_Config.gpuMultiThreaded) UpdateSnapshots(true); } /****************************************************************************** Rendering ******************************************************************************/ void CTileGen::BeginVBlank(void) { /* printf("08: %X\n", regs[0x08/4]); printf("0C: %X\n", regs[0x0C/4]); printf("20: %X\n", regs[0x20/4]); printf("40: %X\n", regs[0x40/4]); printf("44: %X\n", regs[0x44/4]); printf("60: %08X\n", regs[0x60/4]); printf("64: %08X\n", regs[0x64/4]); printf("68: %08X\n", regs[0x68/4]); printf("6C: %08X\n", regs[0x6C/4]); */ } void CTileGen::EndVBlank(void) { // } UINT32 CTileGen::SyncSnapshots(void) { if (!g_Config.gpuMultiThreaded) return 0; // Update read-only snapshots return UpdateSnapshots(false); } UINT32 CTileGen::UpdateSnapshot(bool copyWhole, UINT8 *src, UINT8 *dst, unsigned size, UINT8 *dirty) { unsigned dirtySize = DIRTY_SIZE(size); if (copyWhole) { // If updating whole region, then just copy all data in one go memcpy(dst, src, size); memset(dirty, 0, dirtySize); return size; } else { // Otherwise, loop through dirty pages array to find out what needs to be updated and copy only those parts UINT32 copied = 0; UINT8 *pSrc = src; UINT8 *pDst = dst; for (unsigned i = 0; i < dirtySize; i++) { UINT8 d = dirty[i]; if (d) { for (unsigned j = 0; j < 8; j++) { if (d&1) { // If not at very end of region, then copy an extra 4 bytes to allow for a possible 32-bit overlap UINT32 toCopy = (i < dirtySize - 1 || j < 7 ? PAGE_SIZE + 4 : PAGE_SIZE); memcpy(pDst, pSrc, toCopy); copied += toCopy; } d >>= 1; pSrc += PAGE_SIZE; pDst += PAGE_SIZE; } dirty[i] = 0; } else { pSrc += 8 * PAGE_SIZE; pDst += 8 * PAGE_SIZE; } } return copied; } } UINT32 CTileGen::UpdateSnapshots(bool copyWhole) { // Update all memory region snapshots UINT32 palCopied = UpdateSnapshot(copyWhole, (UINT8*)pal, (UINT8*)palRO, 0x020000, palDirty); UINT32 vramCopied = UpdateSnapshot(copyWhole, (UINT8*)vram, (UINT8*)vramRO, 0x120000, vramDirty); memcpy(regsRO, regs, sizeof(regs)); // Always copy whole of regs buffer //printf("TileGen copied - pal:%4uK, vram:%4uK, regs:%uK\n", palCopied / 1024, vramCopied / 1024, sizeof(regs) / 1024); return palCopied + vramCopied + sizeof(regs); } void CTileGen::BeginFrame(void) { // NOTE: Render2D->WriteVRAM(addr, data) is no longer being called for RAM addresses that are written // to and instead this class relies upon the fact that Render2D currently marks everything as dirty // with every frame. If this were to change in the future then code to handle marking the correct // parts of the renderer as dirty would need to be added here. Render2D->BeginFrame(); } void CTileGen::EndFrame(void) { Render2D->EndFrame(); } /****************************************************************************** Emulation Functions ******************************************************************************/ UINT32 CTileGen::ReadRAM(unsigned addr) { return *(UINT32 *) &vram[addr]; } void CTileGen::WriteRAM(unsigned addr, UINT32 data) { if (g_Config.gpuMultiThreaded) MARK_DIRTY(vramDirty, addr); *(UINT32 *) &vram[addr] = data; // Update palette if required if (addr >= 0x100000) { addr -= 0x100000; unsigned color = addr/4; // color index if (g_Config.gpuMultiThreaded) MARK_DIRTY(palDirty, addr); WritePalette(color, data); } } void CTileGen::InitPalette(void) { for (int i = 0; i < 0x20000/4; i++) { WritePalette(i, vram[0x100000/4 + i]); if (g_Config.gpuMultiThreaded) palRO[i] = pal[i]; } } void CTileGen::WritePalette(unsigned color, UINT32 data) { UINT8 r, g, b, a; a = 0xFF * ((data>>15)&1); // decode the RGBA (make alpha 0xFF or 0x00) a = ~a; // invert it (set on Model 3 means clear pixel) if ((data&0x8000)) r = g = b = 0; else { b = (data>>7)&0xF8; g = (data>>2)&0xF8; r = (data<<3)&0xF8; } pal[color] = (a<<24)|(b<<16)|(g<<8)|r; } void CTileGen::WriteRegister(unsigned reg, UINT32 data) { reg &= 0xFF; regs[reg/4] = data; switch (reg) { case 0x10: // IRQ acknowledge IRQ->Deassert(data&0xFF); break; case 0x60: break; case 0x64: break; case 0x68: break; case 0x6C: break; default: DebugLog("Tile Generator reg %02X = %08X\n", reg, data); //printf("%02X = %08X\n", reg, data); break; } } void CTileGen::Reset(void) { unsigned memSize = (g_Config.gpuMultiThreaded ? MEMORY_POOL_SIZE : MEM_POOL_SIZE_RW); memset(memoryPool, 0, memSize); memset(regs, 0, sizeof(regs)); memset(regsRO, 0, sizeof(regsRO)); InitPalette(); DebugLog("Tile Generator reset\n"); } /****************************************************************************** Configuration, Initialization, and Shutdown ******************************************************************************/ void CTileGen::AttachRenderer(CRender2D *Render2DPtr) { Render2D = Render2DPtr; // If multi-threaded, attach read-only snapshots to renderer instead of real ones if (g_Config.gpuMultiThreaded) { Render2D->AttachVRAM(vramRO); Render2D->AttachPalette(palRO); Render2D->AttachRegisters(regsRO); } else { Render2D->AttachVRAM(vram); Render2D->AttachPalette(pal); Render2D->AttachRegisters(regs); } DebugLog("Tile Generator attached a Render2D object\n"); } bool CTileGen::Init(CIRQ *IRQObjectPtr) { unsigned memSize = (g_Config.gpuMultiThreaded ? MEMORY_POOL_SIZE : MEM_POOL_SIZE_RW); float memSizeMB = (float)memSize/(float)0x100000; // Allocate all memory for all TileGen RAM regions memoryPool = new(std::nothrow) UINT8[memSize]; if (NULL == memoryPool) return ErrorLog("Insufficient memory for tile generator object (needs %1.1f MB).", memSizeMB); // Set up main pointers vram = (UINT8 *) &memoryPool[OFFSET_VRAM]; pal = (UINT32 *) &memoryPool[OFFSET_PAL]; // If multi-threaded, set up pointers for read-only snapshots and dirty page arrays too if (g_Config.gpuMultiThreaded) { vramRO = (UINT8 *) &memoryPool[OFFSET_VRAM_RO]; palRO = (UINT32 *) &memoryPool[OFFSET_PAL_RO]; vramDirty = (UINT8 *) &memoryPool[OFFSET_VRAM_DIRTY]; palDirty = (UINT8 *) &memoryPool[OFFSET_PAL_DIRTY]; } // Hook up the IRQ controller IRQ = IRQObjectPtr; DebugLog("Initialized Tile Generator (allocated %1.1f MB and connected to IRQ controller)\n", memSizeMB); return OKAY; } CTileGen::CTileGen(void) { IRQ = NULL; memoryPool = NULL; DebugLog("Built Tile Generator\n"); } CTileGen::~CTileGen(void) { // Dump tile generator RAM #if 0 FILE *fp; fp = fopen("tileram", "wb"); if (NULL != fp) { fwrite(memoryPool, sizeof(UINT8), 0x120000, fp); fclose(fp); printf("dumped %s\n", "tileram"); } else printf("unable to dump %s\n", "tileram"); #endif IRQ = NULL; if (memoryPool != NULL) { delete [] memoryPool; memoryPool = NULL; } DebugLog("Destroyed Tile Generator\n"); }