1350 lines
47 KiB
C#
1350 lines
47 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Iris.GBA
|
|
{
|
|
internal sealed class Video : IDisposable
|
|
{
|
|
internal enum Register
|
|
{
|
|
DISPCNT,
|
|
DISPSTAT,
|
|
VCOUNT,
|
|
|
|
BG0CNT,
|
|
BG1CNT,
|
|
BG2CNT,
|
|
BG3CNT,
|
|
|
|
BG0HOFS,
|
|
BG0VOFS,
|
|
|
|
BG1HOFS,
|
|
BG1VOFS,
|
|
|
|
BG2HOFS,
|
|
BG2VOFS,
|
|
|
|
BG3HOFS,
|
|
BG3VOFS,
|
|
|
|
BG2PA,
|
|
BG2PB,
|
|
BG2PC,
|
|
BG2PD,
|
|
BG2X_L,
|
|
BG2X_H,
|
|
BG2Y_L,
|
|
BG2Y_H,
|
|
|
|
BG3PA,
|
|
BG3PB,
|
|
BG3PC,
|
|
BG3PD,
|
|
BG3X_L,
|
|
BG3X_H,
|
|
BG3Y_L,
|
|
BG3Y_H,
|
|
|
|
WIN0H,
|
|
WIN1H,
|
|
|
|
WIN0V,
|
|
WIN1V,
|
|
|
|
WININ,
|
|
WINOUT,
|
|
|
|
MOSAIC,
|
|
|
|
BLDCNT,
|
|
BLDALPHA,
|
|
BLDY
|
|
}
|
|
|
|
private const int KB = 1024;
|
|
|
|
private const int PaletteRAM_Size = 1 * KB;
|
|
private const int VRAM_Size = 96 * KB;
|
|
private const int OAM_Size = 1 * KB;
|
|
|
|
private readonly IntPtr _paletteRAM = Marshal.AllocHGlobal(PaletteRAM_Size);
|
|
private readonly IntPtr _vram = Marshal.AllocHGlobal(VRAM_Size);
|
|
private readonly IntPtr _oam = Marshal.AllocHGlobal(OAM_Size);
|
|
|
|
private const UInt32 PaletteRAM_StartAddress = 0x0500_0000;
|
|
private const UInt32 PaletteRAM_EndAddress = 0x0600_0000;
|
|
|
|
private const UInt32 VRAM_StartAddress = 0x0600_0000;
|
|
private const UInt32 VRAM_EndAddress = 0x0700_0000;
|
|
|
|
private const UInt32 OAM_StartAddress = 0x0700_0000;
|
|
private const UInt32 OAM_EndAddress = 0x0800_0000;
|
|
|
|
private UInt16 _DISPCNT;
|
|
private UInt16 _DISPSTAT;
|
|
private UInt16 _VCOUNT;
|
|
|
|
private UInt16 _BG0CNT;
|
|
private UInt16 _BG1CNT;
|
|
private UInt16 _BG2CNT;
|
|
private UInt16 _BG3CNT;
|
|
|
|
private UInt16 _BG0HOFS;
|
|
private UInt16 _BG0VOFS;
|
|
|
|
private UInt16 _BG1HOFS;
|
|
private UInt16 _BG1VOFS;
|
|
|
|
private UInt16 _BG2HOFS;
|
|
private UInt16 _BG2VOFS;
|
|
|
|
private UInt16 _BG3HOFS;
|
|
private UInt16 _BG3VOFS;
|
|
|
|
private UInt16 _BG2PA;
|
|
private UInt16 _BG2PB;
|
|
private UInt16 _BG2PC;
|
|
private UInt16 _BG2PD;
|
|
private UInt16 _BG2X_L;
|
|
private UInt16 _BG2X_H;
|
|
private UInt16 _BG2Y_L;
|
|
private UInt16 _BG2Y_H;
|
|
|
|
private UInt16 _BG3PA;
|
|
private UInt16 _BG3PB;
|
|
private UInt16 _BG3PC;
|
|
private UInt16 _BG3PD;
|
|
private UInt16 _BG3X_L;
|
|
private UInt16 _BG3X_H;
|
|
private UInt16 _BG3Y_L;
|
|
private UInt16 _BG3Y_H;
|
|
|
|
private UInt16 _WIN0H;
|
|
private UInt16 _WIN1H;
|
|
|
|
private UInt16 _WIN0V;
|
|
private UInt16 _WIN1V;
|
|
|
|
private UInt16 _WININ;
|
|
private UInt16 _WINOUT;
|
|
|
|
private UInt16 _MOSAIC;
|
|
|
|
private UInt16 _BLDCNT;
|
|
private UInt16 _BLDALPHA;
|
|
private UInt16 _BLDY;
|
|
|
|
private const int DisplayScreenWidth = 240;
|
|
private const int DisplayScreenHeight = 160;
|
|
private const int DisplayScreenSize = DisplayScreenWidth * DisplayScreenHeight;
|
|
|
|
private const int CharacterWidth = 8;
|
|
private const int CharacterHeight = 8;
|
|
|
|
private const UInt64 HDrawCycleCount = 1006;
|
|
private const UInt64 HBlankCycleCount = 226;
|
|
|
|
private readonly Common.Scheduler _scheduler;
|
|
private readonly Common.System.PresentFrame_Delegate _presentFrameCallback;
|
|
|
|
private DMA _dma;
|
|
private InterruptControl _interruptControl;
|
|
|
|
private bool _disposed;
|
|
|
|
private readonly UInt16[] _displayFrameBuffer = new UInt16[DisplayScreenSize];
|
|
|
|
private Int32 _currentBG2X;
|
|
private Int32 _currentBG2Y;
|
|
|
|
private Int32 _currentBG3X;
|
|
private Int32 _currentBG3Y;
|
|
|
|
internal Video(Common.Scheduler scheduler, Common.System.PresentFrame_Delegate presentFrameCallback)
|
|
{
|
|
_scheduler = scheduler;
|
|
_presentFrameCallback = presentFrameCallback;
|
|
|
|
_scheduler.RegisterTask((int)GBA_System.TaskId.StartHBlank, StartHBlank);
|
|
_scheduler.RegisterTask((int)GBA_System.TaskId.StartScanline, StartScanline);
|
|
}
|
|
|
|
~Video()
|
|
{
|
|
Dispose();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed)
|
|
return;
|
|
|
|
Marshal.FreeHGlobal(_paletteRAM);
|
|
Marshal.FreeHGlobal(_vram);
|
|
Marshal.FreeHGlobal(_oam);
|
|
|
|
GC.SuppressFinalize(this);
|
|
_disposed = true;
|
|
}
|
|
|
|
internal void Initialize(DMA dma, InterruptControl interruptControl, Memory memory)
|
|
{
|
|
_dma = dma;
|
|
_interruptControl = interruptControl;
|
|
|
|
const Memory.Flag flags = Memory.Flag.All & ~Memory.Flag.Write8;
|
|
memory.Map(_paletteRAM, PaletteRAM_Size, PaletteRAM_StartAddress, PaletteRAM_EndAddress, flags);
|
|
memory.Map(_vram, VRAM_Size, VRAM_StartAddress, VRAM_EndAddress, flags);
|
|
memory.Map(_oam, OAM_Size, OAM_StartAddress, OAM_EndAddress, flags);
|
|
}
|
|
|
|
internal void ResetState()
|
|
{
|
|
byte[] paletteRamData = new byte[PaletteRAM_Size];
|
|
byte[] vramData = new byte[VRAM_Size];
|
|
byte[] oamData = new byte[OAM_Size];
|
|
|
|
Marshal.Copy(paletteRamData, 0, _paletteRAM, PaletteRAM_Size);
|
|
Marshal.Copy(vramData, 0, _vram, VRAM_Size);
|
|
Marshal.Copy(oamData, 0, _oam, OAM_Size);
|
|
|
|
_DISPCNT = 0;
|
|
_DISPSTAT = 0;
|
|
_VCOUNT = 0;
|
|
|
|
_BG0CNT = 0;
|
|
_BG1CNT = 0;
|
|
_BG2CNT = 0;
|
|
_BG3CNT = 0;
|
|
|
|
_BG0HOFS = 0;
|
|
_BG0VOFS = 0;
|
|
|
|
_BG1HOFS = 0;
|
|
_BG1VOFS = 0;
|
|
|
|
_BG2HOFS = 0;
|
|
_BG2VOFS = 0;
|
|
|
|
_BG3HOFS = 0;
|
|
_BG3VOFS = 0;
|
|
|
|
_BG2PA = 0;
|
|
_BG2PB = 0;
|
|
_BG2PC = 0;
|
|
_BG2PD = 0;
|
|
_BG2X_L = 0;
|
|
_BG2X_H = 0;
|
|
_BG2Y_L = 0;
|
|
_BG2Y_H = 0;
|
|
|
|
_BG3PA = 0;
|
|
_BG3PB = 0;
|
|
_BG3PC = 0;
|
|
_BG3PD = 0;
|
|
_BG3X_L = 0;
|
|
_BG3X_H = 0;
|
|
_BG3Y_L = 0;
|
|
_BG3Y_H = 0;
|
|
|
|
_WIN0H = 0;
|
|
_WIN1H = 0;
|
|
|
|
_WIN0V = 0;
|
|
_WIN1V = 0;
|
|
|
|
_WININ = 0;
|
|
_WINOUT = 0;
|
|
|
|
_MOSAIC = 0;
|
|
|
|
_BLDCNT = 0;
|
|
_BLDALPHA = 0;
|
|
_BLDY = 0;
|
|
|
|
_scheduler.ScheduleTask((int)GBA_System.TaskId.StartHBlank, HDrawCycleCount);
|
|
|
|
Array.Clear(_displayFrameBuffer,0, _displayFrameBuffer.Length);
|
|
|
|
_currentBG2X = 0;
|
|
_currentBG2Y = 0;
|
|
|
|
_currentBG3X = 0;
|
|
_currentBG3Y = 0;
|
|
}
|
|
|
|
internal void LoadState(BinaryReader reader)
|
|
{
|
|
byte[] paletteRamData = reader.ReadBytes(PaletteRAM_Size);
|
|
byte[] vramData = reader.ReadBytes(VRAM_Size);
|
|
byte[] oamData = reader.ReadBytes(OAM_Size);
|
|
|
|
Marshal.Copy(paletteRamData, 0, _paletteRAM, PaletteRAM_Size);
|
|
Marshal.Copy(vramData, 0, _vram, VRAM_Size);
|
|
Marshal.Copy(oamData, 0, _oam, OAM_Size);
|
|
|
|
_DISPCNT = reader.ReadUInt16();
|
|
_DISPSTAT = reader.ReadUInt16();
|
|
_VCOUNT = reader.ReadUInt16();
|
|
|
|
_BG0CNT = reader.ReadUInt16();
|
|
_BG1CNT = reader.ReadUInt16();
|
|
_BG2CNT = reader.ReadUInt16();
|
|
_BG3CNT = reader.ReadUInt16();
|
|
|
|
_BG0HOFS = reader.ReadUInt16();
|
|
_BG0VOFS = reader.ReadUInt16();
|
|
|
|
_BG1HOFS = reader.ReadUInt16();
|
|
_BG1VOFS = reader.ReadUInt16();
|
|
|
|
_BG2HOFS = reader.ReadUInt16();
|
|
_BG2VOFS = reader.ReadUInt16();
|
|
|
|
_BG3HOFS = reader.ReadUInt16();
|
|
_BG3VOFS = reader.ReadUInt16();
|
|
|
|
_BG2PA = reader.ReadUInt16();
|
|
_BG2PB = reader.ReadUInt16();
|
|
_BG2PC = reader.ReadUInt16();
|
|
_BG2PD = reader.ReadUInt16();
|
|
_BG2X_L = reader.ReadUInt16();
|
|
_BG2X_H = reader.ReadUInt16();
|
|
_BG2Y_L = reader.ReadUInt16();
|
|
_BG2Y_H = reader.ReadUInt16();
|
|
|
|
_BG3PA = reader.ReadUInt16();
|
|
_BG3PB = reader.ReadUInt16();
|
|
_BG3PC = reader.ReadUInt16();
|
|
_BG3PD = reader.ReadUInt16();
|
|
_BG3X_L = reader.ReadUInt16();
|
|
_BG3X_H = reader.ReadUInt16();
|
|
_BG3Y_L = reader.ReadUInt16();
|
|
_BG3Y_H = reader.ReadUInt16();
|
|
|
|
_WIN0H = reader.ReadUInt16();
|
|
_WIN1H = reader.ReadUInt16();
|
|
|
|
_WIN0V = reader.ReadUInt16();
|
|
_WIN1V = reader.ReadUInt16();
|
|
|
|
_WININ = reader.ReadUInt16();
|
|
_WINOUT = reader.ReadUInt16();
|
|
|
|
_MOSAIC = reader.ReadUInt16();
|
|
|
|
_BLDCNT = reader.ReadUInt16();
|
|
_BLDALPHA = reader.ReadUInt16();
|
|
_BLDY = reader.ReadUInt16();
|
|
|
|
foreach (ref UInt16 color in _displayFrameBuffer.AsSpan())
|
|
color = reader.ReadUInt16();
|
|
|
|
_currentBG2X = reader.ReadInt32();
|
|
_currentBG2Y = reader.ReadInt32();
|
|
|
|
_currentBG3X = reader.ReadInt32();
|
|
_currentBG3Y = reader.ReadInt32();
|
|
}
|
|
|
|
internal void SaveState(BinaryWriter writer)
|
|
{
|
|
byte[] paletteRamData = new byte[PaletteRAM_Size];
|
|
byte[] vramData = new byte[VRAM_Size];
|
|
byte[] oamData = new byte[OAM_Size];
|
|
|
|
Marshal.Copy(_paletteRAM, paletteRamData, 0, PaletteRAM_Size);
|
|
Marshal.Copy(_vram, vramData, 0, VRAM_Size);
|
|
Marshal.Copy(_oam, oamData, 0, OAM_Size);
|
|
|
|
writer.Write(paletteRamData);
|
|
writer.Write(vramData);
|
|
writer.Write(oamData);
|
|
|
|
writer.Write(_DISPCNT);
|
|
writer.Write(_DISPSTAT);
|
|
writer.Write(_VCOUNT);
|
|
|
|
writer.Write(_BG0CNT);
|
|
writer.Write(_BG1CNT);
|
|
writer.Write(_BG2CNT);
|
|
writer.Write(_BG3CNT);
|
|
|
|
writer.Write(_BG0HOFS);
|
|
writer.Write(_BG0VOFS);
|
|
|
|
writer.Write(_BG1HOFS);
|
|
writer.Write(_BG1VOFS);
|
|
|
|
writer.Write(_BG2HOFS);
|
|
writer.Write(_BG2VOFS);
|
|
|
|
writer.Write(_BG3HOFS);
|
|
writer.Write(_BG3VOFS);
|
|
|
|
writer.Write(_BG2PA);
|
|
writer.Write(_BG2PB);
|
|
writer.Write(_BG2PC);
|
|
writer.Write(_BG2PD);
|
|
writer.Write(_BG2X_L);
|
|
writer.Write(_BG2X_H);
|
|
writer.Write(_BG2Y_L);
|
|
writer.Write(_BG2Y_H);
|
|
|
|
writer.Write(_BG3PA);
|
|
writer.Write(_BG3PB);
|
|
writer.Write(_BG3PC);
|
|
writer.Write(_BG3PD);
|
|
writer.Write(_BG3X_L);
|
|
writer.Write(_BG3X_H);
|
|
writer.Write(_BG3Y_L);
|
|
writer.Write(_BG3Y_H);
|
|
|
|
writer.Write(_WIN0H);
|
|
writer.Write(_WIN1H);
|
|
|
|
writer.Write(_WIN0V);
|
|
writer.Write(_WIN1V);
|
|
|
|
writer.Write(_WININ);
|
|
writer.Write(_WINOUT);
|
|
|
|
writer.Write(_MOSAIC);
|
|
|
|
writer.Write(_BLDCNT);
|
|
writer.Write(_BLDALPHA);
|
|
writer.Write(_BLDY);
|
|
|
|
foreach (UInt16 color in _displayFrameBuffer)
|
|
writer.Write(color);
|
|
|
|
writer.Write(_currentBG2X);
|
|
writer.Write(_currentBG2Y);
|
|
|
|
writer.Write(_currentBG3X);
|
|
writer.Write(_currentBG3Y);
|
|
}
|
|
|
|
internal UInt16 ReadRegister(Register register)
|
|
{
|
|
return register switch
|
|
{
|
|
Register.DISPCNT => _DISPCNT,
|
|
Register.DISPSTAT => _DISPSTAT,
|
|
Register.VCOUNT => _VCOUNT,
|
|
|
|
Register.BG0CNT => _BG0CNT,
|
|
Register.BG1CNT => _BG1CNT,
|
|
Register.BG2CNT => _BG2CNT,
|
|
Register.BG3CNT => _BG3CNT,
|
|
|
|
Register.WININ => _WININ,
|
|
Register.WINOUT => _WINOUT,
|
|
|
|
Register.BLDCNT => _BLDCNT,
|
|
Register.BLDALPHA => _BLDALPHA,
|
|
|
|
// should never happen
|
|
_ => throw new Exception("Iris.GBA.Video: Register read error"),
|
|
};
|
|
}
|
|
|
|
internal void WriteRegister(Register register, UInt16 value, Memory.RegisterWriteMode mode)
|
|
{
|
|
switch (register)
|
|
{
|
|
case Register.DISPCNT:
|
|
Memory.WriteRegisterHelper(ref _DISPCNT, value, mode);
|
|
break;
|
|
case Register.DISPSTAT:
|
|
Memory.WriteRegisterHelper(ref _DISPSTAT, value, mode);
|
|
break;
|
|
|
|
case Register.BG0CNT:
|
|
Memory.WriteRegisterHelper(ref _BG0CNT, value, mode);
|
|
break;
|
|
case Register.BG1CNT:
|
|
Memory.WriteRegisterHelper(ref _BG1CNT, value, mode);
|
|
break;
|
|
case Register.BG2CNT:
|
|
Memory.WriteRegisterHelper(ref _BG2CNT, value, mode);
|
|
break;
|
|
case Register.BG3CNT:
|
|
Memory.WriteRegisterHelper(ref _BG3CNT, value, mode);
|
|
break;
|
|
|
|
case Register.BG0HOFS:
|
|
Memory.WriteRegisterHelper(ref _BG0HOFS, value, mode);
|
|
break;
|
|
case Register.BG0VOFS:
|
|
Memory.WriteRegisterHelper(ref _BG0VOFS, value, mode);
|
|
break;
|
|
|
|
case Register.BG1HOFS:
|
|
Memory.WriteRegisterHelper(ref _BG1HOFS, value, mode);
|
|
break;
|
|
case Register.BG1VOFS:
|
|
Memory.WriteRegisterHelper(ref _BG1VOFS, value, mode);
|
|
break;
|
|
|
|
case Register.BG2HOFS:
|
|
Memory.WriteRegisterHelper(ref _BG2HOFS, value, mode);
|
|
break;
|
|
case Register.BG2VOFS:
|
|
Memory.WriteRegisterHelper(ref _BG2VOFS, value, mode);
|
|
break;
|
|
|
|
case Register.BG3HOFS:
|
|
Memory.WriteRegisterHelper(ref _BG3HOFS, value, mode);
|
|
break;
|
|
case Register.BG3VOFS:
|
|
Memory.WriteRegisterHelper(ref _BG3VOFS, value, mode);
|
|
break;
|
|
|
|
case Register.BG2PA:
|
|
Memory.WriteRegisterHelper(ref _BG2PA, value, mode);
|
|
break;
|
|
case Register.BG2PB:
|
|
Memory.WriteRegisterHelper(ref _BG2PB, value, mode);
|
|
break;
|
|
case Register.BG2PC:
|
|
Memory.WriteRegisterHelper(ref _BG2PC, value, mode);
|
|
break;
|
|
case Register.BG2PD:
|
|
Memory.WriteRegisterHelper(ref _BG2PD, value, mode);
|
|
break;
|
|
case Register.BG2X_L:
|
|
Memory.WriteRegisterHelper(ref _BG2X_L, value, mode);
|
|
_currentBG2X = ((_BG2X_H << 20) | (_BG2X_L << 4)) >> 4;
|
|
break;
|
|
case Register.BG2X_H:
|
|
Memory.WriteRegisterHelper(ref _BG2X_H, value, mode);
|
|
_currentBG2X = ((_BG2X_H << 20) | (_BG2X_L << 4)) >> 4;
|
|
break;
|
|
case Register.BG2Y_L:
|
|
Memory.WriteRegisterHelper(ref _BG2Y_L, value, mode);
|
|
_currentBG2Y = ((_BG2Y_H << 20) | (_BG2Y_L << 4)) >> 4;
|
|
break;
|
|
case Register.BG2Y_H:
|
|
Memory.WriteRegisterHelper(ref _BG2Y_H, value, mode);
|
|
_currentBG2Y = ((_BG2Y_H << 20) | (_BG2Y_L << 4)) >> 4;
|
|
break;
|
|
|
|
case Register.BG3PA:
|
|
Memory.WriteRegisterHelper(ref _BG3PA, value, mode);
|
|
break;
|
|
case Register.BG3PB:
|
|
Memory.WriteRegisterHelper(ref _BG3PB, value, mode);
|
|
break;
|
|
case Register.BG3PC:
|
|
Memory.WriteRegisterHelper(ref _BG3PC, value, mode);
|
|
break;
|
|
case Register.BG3PD:
|
|
Memory.WriteRegisterHelper(ref _BG3PD, value, mode);
|
|
break;
|
|
case Register.BG3X_L:
|
|
Memory.WriteRegisterHelper(ref _BG3X_L, value, mode);
|
|
_currentBG3X = ((_BG3X_H << 20) | (_BG3X_L << 4)) >> 4;
|
|
break;
|
|
case Register.BG3X_H:
|
|
Memory.WriteRegisterHelper(ref _BG3X_H, value, mode);
|
|
_currentBG3X = ((_BG3X_H << 20) | (_BG3X_L << 4)) >> 4;
|
|
break;
|
|
case Register.BG3Y_L:
|
|
Memory.WriteRegisterHelper(ref _BG3Y_L, value, mode);
|
|
_currentBG3Y = ((_BG3Y_H << 20) | (_BG3Y_L << 4)) >> 4;
|
|
break;
|
|
case Register.BG3Y_H:
|
|
Memory.WriteRegisterHelper(ref _BG3Y_H, value, mode);
|
|
_currentBG3Y = ((_BG3Y_H << 20) | (_BG3Y_L << 4)) >> 4;
|
|
break;
|
|
|
|
case Register.WIN0H:
|
|
Memory.WriteRegisterHelper(ref _WIN0H, value, mode);
|
|
break;
|
|
case Register.WIN1H:
|
|
Memory.WriteRegisterHelper(ref _WIN1H, value, mode);
|
|
break;
|
|
|
|
case Register.WIN0V:
|
|
Memory.WriteRegisterHelper(ref _WIN0V, value, mode);
|
|
break;
|
|
case Register.WIN1V:
|
|
Memory.WriteRegisterHelper(ref _WIN1V, value, mode);
|
|
break;
|
|
|
|
case Register.WININ:
|
|
Memory.WriteRegisterHelper(ref _WININ, value, mode);
|
|
break;
|
|
case Register.WINOUT:
|
|
Memory.WriteRegisterHelper(ref _WINOUT, value, mode);
|
|
break;
|
|
|
|
case Register.MOSAIC:
|
|
Memory.WriteRegisterHelper(ref _MOSAIC, value, mode);
|
|
break;
|
|
|
|
case Register.BLDCNT:
|
|
Memory.WriteRegisterHelper(ref _BLDCNT, value, mode);
|
|
break;
|
|
case Register.BLDALPHA:
|
|
Memory.WriteRegisterHelper(ref _BLDALPHA, value, mode);
|
|
break;
|
|
case Register.BLDY:
|
|
Memory.WriteRegisterHelper(ref _BLDY, value, mode);
|
|
break;
|
|
|
|
// should never happen
|
|
default:
|
|
throw new Exception("Iris.GBA.Video: Register write error");
|
|
}
|
|
}
|
|
|
|
internal void Write8_PaletteRAM(UInt32 address, Byte value)
|
|
{
|
|
UInt32 offset = (UInt32)((address - PaletteRAM_StartAddress) & ~1) % PaletteRAM_Size;
|
|
|
|
unsafe
|
|
{
|
|
Unsafe.Write<Byte>((Byte*)_paletteRAM + offset, value);
|
|
Unsafe.Write<Byte>((Byte*)_paletteRAM + offset + 1, value);
|
|
}
|
|
}
|
|
|
|
internal void Write8_VRAM(UInt32 address, Byte value)
|
|
{
|
|
UInt32 offset = (UInt32)((address - VRAM_StartAddress) & ~1) % VRAM_Size;
|
|
|
|
unsafe
|
|
{
|
|
Unsafe.Write<Byte>((Byte*)_vram + offset, value);
|
|
Unsafe.Write<Byte>((Byte*)_vram + offset + 1, value);
|
|
}
|
|
}
|
|
|
|
private void StartHBlank(UInt64 cycleCountDelay)
|
|
{
|
|
_DISPSTAT |= 0x0002; // set HBlank status
|
|
|
|
if ((_DISPSTAT & 0x0010) == 0x0010)
|
|
_interruptControl.RequestInterrupt(InterruptControl.Interrupt.HBlank);
|
|
|
|
if (_VCOUNT < DisplayScreenHeight)
|
|
_dma.PerformHBlankTransfers();
|
|
|
|
_scheduler.ScheduleTask((int)GBA_System.TaskId.StartScanline, HBlankCycleCount - cycleCountDelay);
|
|
}
|
|
|
|
private void StartScanline(UInt64 cycleCountDelay)
|
|
{
|
|
_DISPSTAT = (UInt16)(_DISPSTAT & ~0x0002); // clear HBlank status
|
|
|
|
switch (_VCOUNT)
|
|
{
|
|
// rendering
|
|
case 0:
|
|
_VCOUNT = 1;
|
|
|
|
Render();
|
|
break;
|
|
|
|
// rendering
|
|
// video transfer DMA
|
|
case > 0 and < 159:
|
|
++_VCOUNT;
|
|
|
|
_dma.PerformVideoTransfer(false);
|
|
|
|
Render();
|
|
break;
|
|
|
|
// end rendering
|
|
// start vblank
|
|
// video transfer DMA
|
|
case 159:
|
|
_VCOUNT = 160;
|
|
|
|
_DISPSTAT |= 0x0001; // set VBlank status
|
|
|
|
if ((_DISPSTAT & 0x0008) == 0x0008)
|
|
_interruptControl.RequestInterrupt(InterruptControl.Interrupt.VBlank);
|
|
|
|
_dma.PerformVBlankTransfers();
|
|
_dma.PerformVideoTransfer(false);
|
|
|
|
_presentFrameCallback(_displayFrameBuffer);
|
|
//Array.Clear(_displayFrameBuffer);
|
|
Array.Clear(_displayFrameBuffer,0, _displayFrameBuffer.Length);
|
|
|
|
_currentBG2X = ((_BG2X_H << 20) | (_BG2X_L << 4)) >> 4;
|
|
_currentBG2Y = ((_BG2Y_H << 20) | (_BG2Y_L << 4)) >> 4;
|
|
|
|
_currentBG3X = ((_BG3X_H << 20) | (_BG3X_L << 4)) >> 4;
|
|
_currentBG3Y = ((_BG3Y_H << 20) | (_BG3Y_L << 4)) >> 4;
|
|
break;
|
|
|
|
// vblank
|
|
// end video transfer DMA
|
|
case 160:
|
|
_VCOUNT = 161;
|
|
|
|
_dma.PerformVideoTransfer(true);
|
|
break;
|
|
|
|
// vblank
|
|
case > 160 and < 226:
|
|
++_VCOUNT;
|
|
break;
|
|
|
|
// end vblank
|
|
case 226:
|
|
_VCOUNT = 227;
|
|
|
|
_DISPSTAT = (UInt16)(_DISPSTAT & ~0x0001); // clear VBlank status
|
|
break;
|
|
|
|
// start rendering
|
|
case 227:
|
|
_VCOUNT = 0;
|
|
|
|
Render();
|
|
break;
|
|
}
|
|
|
|
if (_VCOUNT == ((_DISPSTAT >> 8) & 0xff))
|
|
{
|
|
_DISPSTAT |= 0x0004; // set VCountMatch status
|
|
|
|
if ((_DISPSTAT & 0x0020) == 0x0020)
|
|
_interruptControl.RequestInterrupt(InterruptControl.Interrupt.VCountMatch);
|
|
}
|
|
else
|
|
{
|
|
_DISPSTAT = (UInt16)(_DISPSTAT & ~0x0004); // clear VCountMatch status
|
|
}
|
|
|
|
_scheduler.ScheduleTask((int)GBA_System.TaskId.StartHBlank, HDrawCycleCount - cycleCountDelay);
|
|
}
|
|
|
|
private void Render()
|
|
{
|
|
UInt16 bgMode = (UInt16)(_DISPCNT & 0b111);
|
|
|
|
switch (bgMode)
|
|
{
|
|
case 0b000:
|
|
RenderBackgroundMode0();
|
|
break;
|
|
case 0b001:
|
|
RenderBackgroundMode1();
|
|
break;
|
|
case 0b010:
|
|
RenderBackgroundMode2();
|
|
break;
|
|
case 0b011:
|
|
RenderBackgroundMode3();
|
|
break;
|
|
case 0b100:
|
|
RenderBackgroundMode4();
|
|
break;
|
|
case 0b101:
|
|
RenderBackgroundMode5();
|
|
break;
|
|
|
|
// TODO: verify
|
|
case 0b110:
|
|
case 0b111:
|
|
throw new Exception($"Iris.GBA.Video: Unknown background mode {bgMode}");
|
|
}
|
|
|
|
_currentBG2X += (Int16)_BG2PB;
|
|
_currentBG2Y += (Int16)_BG2PD;
|
|
|
|
_currentBG3X += (Int16)_BG3PB;
|
|
_currentBG3Y += (Int16)_BG3PD;
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void RenderBackgroundMode0()
|
|
{
|
|
bool isFirst = true;
|
|
|
|
for (int bgPriority = 3; bgPriority >= 0; --bgPriority)
|
|
{
|
|
if (((_DISPCNT & 0x0800) == 0x0800) && ((_BG3CNT & 0b11) == bgPriority))
|
|
{
|
|
RenderTextBackground(_BG3CNT, _BG3HOFS, _BG3VOFS, isFirst);
|
|
isFirst = false;
|
|
}
|
|
|
|
if (((_DISPCNT & 0x0400) == 0x0400) && ((_BG2CNT & 0b11) == bgPriority))
|
|
{
|
|
RenderTextBackground(_BG2CNT, _BG2HOFS, _BG2VOFS, isFirst);
|
|
isFirst = false;
|
|
}
|
|
|
|
if (((_DISPCNT & 0x0200) == 0x0200) && ((_BG1CNT & 0b11) == bgPriority))
|
|
{
|
|
RenderTextBackground(_BG1CNT, _BG1HOFS, _BG1VOFS, isFirst);
|
|
isFirst = false;
|
|
}
|
|
|
|
if (((_DISPCNT & 0x0100) == 0x0100) && ((_BG0CNT & 0b11) == bgPriority))
|
|
{
|
|
RenderTextBackground(_BG0CNT, _BG0HOFS, _BG0VOFS, isFirst);
|
|
isFirst = false;
|
|
}
|
|
|
|
if ((_DISPCNT & 0x1000) == 0x1000)
|
|
RenderObjects((UInt16)bgPriority);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void RenderBackgroundMode1()
|
|
{
|
|
bool isFirst = true;
|
|
|
|
for (int bgPriority = 3; bgPriority >= 0; --bgPriority)
|
|
{
|
|
if (((_DISPCNT & 0x0400) == 0x0400) && ((_BG2CNT & 0b11) == bgPriority))
|
|
{
|
|
RenderRotationScalingBackground(_BG2CNT, _currentBG2X, _currentBG2Y, _BG2PA, _BG2PC, isFirst);
|
|
isFirst = false;
|
|
}
|
|
|
|
if (((_DISPCNT & 0x0200) == 0x0200) && ((_BG1CNT & 0b11) == bgPriority))
|
|
{
|
|
RenderTextBackground(_BG1CNT, _BG1HOFS, _BG1VOFS, isFirst);
|
|
isFirst = false;
|
|
}
|
|
|
|
if (((_DISPCNT & 0x0100) == 0x0100) && ((_BG0CNT & 0b11) == bgPriority))
|
|
{
|
|
RenderTextBackground(_BG0CNT, _BG0HOFS, _BG0VOFS, isFirst);
|
|
isFirst = false;
|
|
}
|
|
|
|
if ((_DISPCNT & 0x1000) == 0x1000)
|
|
RenderObjects((UInt16)bgPriority);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void RenderBackgroundMode2()
|
|
{
|
|
bool isFirst = true;
|
|
|
|
for (int bgPriority = 3; bgPriority >= 0; --bgPriority)
|
|
{
|
|
if (((_DISPCNT & 0x0800) == 0x0800) && ((_BG3CNT & 0b11) == bgPriority))
|
|
{
|
|
RenderRotationScalingBackground(_BG3CNT, _currentBG3X, _currentBG3Y, _BG3PA, _BG3PC, isFirst);
|
|
isFirst = false;
|
|
}
|
|
|
|
if (((_DISPCNT & 0x0400) == 0x0400) && ((_BG2CNT & 0b11) == bgPriority))
|
|
{
|
|
RenderRotationScalingBackground(_BG2CNT, _currentBG2X, _currentBG2Y, _BG2PA, _BG2PC, isFirst);
|
|
isFirst = false;
|
|
}
|
|
|
|
if ((_DISPCNT & 0x1000) == 0x1000)
|
|
RenderObjects((UInt16)bgPriority);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void RenderBackgroundMode3()
|
|
{
|
|
if ((_DISPCNT & 0x0400) == 0x0400)
|
|
{
|
|
ref UInt16 displayFrameBufferDataRef = ref MyUnSafeCommon.GetArrayDataReference(_displayFrameBuffer);
|
|
|
|
int pixelNumberBegin = _VCOUNT * DisplayScreenWidth;
|
|
int pixelNumberEnd = pixelNumberBegin + DisplayScreenWidth;
|
|
|
|
for (int pixelNumber = pixelNumberBegin; pixelNumber < pixelNumberEnd; ++pixelNumber)
|
|
{
|
|
unsafe
|
|
{
|
|
UInt16 color = Unsafe.Read<UInt16>((UInt16*)_vram + pixelNumber);
|
|
Unsafe.Add(ref displayFrameBufferDataRef, pixelNumber) = color;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((_DISPCNT & 0x1000) == 0x1000)
|
|
{
|
|
for (int bgPriority = 3; bgPriority >= 0; --bgPriority)
|
|
RenderObjects((UInt16)bgPriority);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void RenderBackgroundMode4()
|
|
{
|
|
if ((_DISPCNT & 0x0400) == 0x0400)
|
|
{
|
|
ref UInt16 displayFrameBufferDataRef = ref MyUnSafeCommon.GetArrayDataReference(_displayFrameBuffer);
|
|
|
|
int pixelNumberBegin = _VCOUNT * DisplayScreenWidth;
|
|
int pixelNumberEnd = pixelNumberBegin + DisplayScreenWidth;
|
|
|
|
UInt32 vramFrameBufferOffset = ((_DISPCNT & 0x0010) == 0) ? 0x0_0000u : 0x0_a000u;
|
|
|
|
for (int pixelNumber = pixelNumberBegin; pixelNumber < pixelNumberEnd; ++pixelNumber)
|
|
{
|
|
unsafe
|
|
{
|
|
Byte colorNumber = Unsafe.Read<Byte>((Byte*)_vram + vramFrameBufferOffset + pixelNumber);
|
|
UInt16 color = Unsafe.Read<UInt16>((UInt16*)_paletteRAM + colorNumber);
|
|
Unsafe.Add(ref displayFrameBufferDataRef, pixelNumber) = color;
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((_DISPCNT & 0x1000) == 0x1000)
|
|
{
|
|
for (int bgPriority = 3; bgPriority >= 0; --bgPriority)
|
|
RenderObjects((UInt16)bgPriority);
|
|
}
|
|
}
|
|
|
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
private void RenderBackgroundMode5()
|
|
{
|
|
if ((_DISPCNT & 0x0400) == 0x0400)
|
|
{
|
|
const int VRAM_FrameBufferWidth = 160;
|
|
const int VRAM_FrameBufferHeight = 128;
|
|
|
|
if (_VCOUNT < VRAM_FrameBufferHeight)
|
|
{
|
|
ref UInt16 displayFrameBufferDataRef = ref MyUnSafeCommon.GetArrayDataReference(_displayFrameBuffer);
|
|
|
|
int vramPixelNumberBegin = _VCOUNT * VRAM_FrameBufferWidth;
|
|
int vramPixelNumberEnd = vramPixelNumberBegin + VRAM_FrameBufferWidth;
|
|
|
|
int displayPixelNumberBegin = _VCOUNT * DisplayScreenWidth;
|
|
|
|
UInt32 vramFrameBufferOffset = ((_DISPCNT & 0x0010) == 0) ? 0x0_0000u : 0x0_a000u;
|
|
|
|
for (int vramPixelNumber = vramPixelNumberBegin, displayPixelNumber = displayPixelNumberBegin; vramPixelNumber < vramPixelNumberEnd; ++vramPixelNumber, ++displayPixelNumber)
|
|
{
|
|
unsafe
|
|
{
|
|
UInt16 color = Unsafe.Read<UInt16>((Byte*)_vram + vramFrameBufferOffset + (vramPixelNumber * 2));
|
|
Unsafe.Add(ref displayFrameBufferDataRef, displayPixelNumber) = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if ((_DISPCNT & 0x1000) == 0x1000)
|
|
{
|
|
for (int bgPriority = 3; bgPriority >= 0; --bgPriority)
|
|
RenderObjects((UInt16)bgPriority);
|
|
}
|
|
}
|
|
|
|
private void RenderTextBackground(UInt16 cnt, UInt16 hofs, UInt16 vofs, bool isFirst)
|
|
{
|
|
ref UInt16 displayFrameBufferDataRef = ref MyUnSafeCommon.GetArrayDataReference(_displayFrameBuffer);
|
|
|
|
int displayPixelNumberBegin = _VCOUNT * DisplayScreenWidth;
|
|
|
|
UInt16 virtualScreenSize = (UInt16)((cnt >> 14) & 0b11);
|
|
UInt16 screenBaseBlock = (UInt16)((cnt >> 8) & 0b1_1111);
|
|
UInt16 colorMode = (UInt16)((cnt >> 7) & 1);
|
|
UInt16 characterBaseBlock = (UInt16)((cnt >> 2) & 0b11);
|
|
|
|
int virtualScreenWidth = ((virtualScreenSize & 0b01) == 0) ? 256 : 512;
|
|
int virtualScreenHeight = ((virtualScreenSize & 0b10) == 0) ? 256 : 512;
|
|
|
|
UInt32 screenBaseBlockOffset = (UInt32)(screenBaseBlock * 2 * KB);
|
|
UInt32 characterBaseBlockOffset = (UInt32)(characterBaseBlock * 16 * KB);
|
|
|
|
int v = (_VCOUNT + vofs) % virtualScreenHeight;
|
|
|
|
const int SC_Width = 256;
|
|
const int SC_Height = 256;
|
|
const int SC_CharacterCount = (SC_Width / CharacterWidth) * (SC_Height / CharacterHeight);
|
|
|
|
int scV = v / SC_Height;
|
|
int scPixelV = v % SC_Height;
|
|
|
|
int scCharacterV = scPixelV / CharacterHeight;
|
|
int characterPixelV = scPixelV % CharacterHeight;
|
|
|
|
int scNumberBegin = scV * (virtualScreenWidth / SC_Width);
|
|
int characterNumberBegin = scCharacterV * (SC_Width / CharacterWidth);
|
|
|
|
int characterPixelNumberBegin = characterPixelV * CharacterWidth;
|
|
int characterPixelNumberBegin_VerticalFlip = (CharacterHeight - 1 - characterPixelV) * CharacterWidth;
|
|
|
|
for (int hcount = 0; hcount < DisplayScreenWidth; ++hcount)
|
|
{
|
|
int h = (hcount + hofs) % virtualScreenWidth;
|
|
|
|
int scH = h / SC_Width;
|
|
int scPixelH = h % SC_Width;
|
|
|
|
int scCharacterH = scPixelH / CharacterWidth;
|
|
int characterPixelH = scPixelH % CharacterWidth;
|
|
|
|
int scNumber = scNumberBegin + scH;
|
|
int characterNumber = characterNumberBegin + scCharacterH;
|
|
|
|
unsafe
|
|
{
|
|
UInt16 screenData = Unsafe.Read<UInt16>((Byte*)_vram + screenBaseBlockOffset + (scNumber * SC_CharacterCount * 2) + (characterNumber * 2));
|
|
|
|
UInt16 colorPalette = (UInt16)((screenData >> 12) & 0b1111);
|
|
UInt16 verticalFlipFlag = (UInt16)((screenData >> 11) & 1);
|
|
UInt16 horizontalFlipFlag = (UInt16)((screenData >> 10) & 1);
|
|
UInt16 characterName = (UInt16)(screenData & 0x3ff);
|
|
|
|
int characterPixelNumber;
|
|
|
|
if (verticalFlipFlag == 0)
|
|
characterPixelNumber = characterPixelNumberBegin;
|
|
else
|
|
characterPixelNumber = characterPixelNumberBegin_VerticalFlip;
|
|
|
|
if (horizontalFlipFlag == 0)
|
|
characterPixelNumber += characterPixelH;
|
|
else
|
|
characterPixelNumber += CharacterWidth - 1 - characterPixelH;
|
|
|
|
const UInt32 ObjectCharacterDataOffset = 0x1_0000;
|
|
|
|
UInt16 color;
|
|
|
|
// 16 colors x 16 palettes
|
|
if (colorMode == 0)
|
|
{
|
|
const int CharacterSize = 32;
|
|
UInt32 characterOffset = (UInt32)(characterBaseBlockOffset + (characterName * CharacterSize));
|
|
|
|
if (characterOffset >= ObjectCharacterDataOffset)
|
|
continue;
|
|
|
|
Byte colorNumber = Unsafe.Read<Byte>((Byte*)_vram + characterOffset + (characterPixelNumber / 2));
|
|
|
|
if ((characterPixelNumber % 2) == 0)
|
|
colorNumber &= 0b1111;
|
|
else
|
|
colorNumber >>= 4;
|
|
|
|
if (!isFirst && (colorNumber == 0))
|
|
continue;
|
|
|
|
color = Unsafe.Read<UInt16>((UInt16*)_paletteRAM + (colorPalette * 16) + colorNumber);
|
|
}
|
|
|
|
// 256 colors x 1 palette
|
|
else
|
|
{
|
|
const int CharacterSize = 64;
|
|
UInt32 characterOffset = (UInt32)(characterBaseBlockOffset + (characterName * CharacterSize));
|
|
|
|
if (characterOffset >= ObjectCharacterDataOffset)
|
|
continue;
|
|
|
|
Byte colorNumber = Unsafe.Read<Byte>((Byte*)_vram + characterOffset + characterPixelNumber);
|
|
|
|
if (!isFirst && (colorNumber == 0))
|
|
continue;
|
|
|
|
color = Unsafe.Read<UInt16>((UInt16*)_paletteRAM + colorNumber);
|
|
}
|
|
|
|
int displayPixelNumber = displayPixelNumberBegin + hcount;
|
|
Unsafe.Add(ref displayFrameBufferDataRef, displayPixelNumber) = color;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenderRotationScalingBackground(UInt16 cnt, Int32 x, Int32 y, UInt16 pa, UInt16 pc, bool isFirst)
|
|
{
|
|
ref UInt16 displayFrameBufferDataRef = ref MyUnSafeCommon.GetArrayDataReference(_displayFrameBuffer);
|
|
|
|
int displayPixelNumberBegin = _VCOUNT * DisplayScreenWidth;
|
|
|
|
UInt16 virtualScreenSize = (UInt16)((cnt >> 14) & 0b11);
|
|
UInt16 areaOverflow = (UInt16)((cnt >> 13) & 1);
|
|
UInt16 screenBaseBlock = (UInt16)((cnt >> 8) & 0b1_1111);
|
|
UInt16 characterBaseBlock = (UInt16)((cnt >> 2) & 0b11);
|
|
|
|
int virtualScreenWidth = 128 << virtualScreenSize;
|
|
int virtualScreenHeight = 128 << virtualScreenSize;
|
|
|
|
UInt32 screenBaseBlockOffset = (UInt32)(screenBaseBlock * 2 * KB);
|
|
UInt32 characterBaseBlockOffset = (UInt32)(characterBaseBlock * 16 * KB);
|
|
|
|
for (int hcount = 0; hcount < DisplayScreenWidth; ++hcount, x += (Int16)pa, y += (Int16)pc)
|
|
{
|
|
int h = x >> 8;
|
|
int v = y >> 8;
|
|
|
|
if (areaOverflow == 0)
|
|
{
|
|
if ((h < 0) || (h >= virtualScreenWidth))
|
|
continue;
|
|
|
|
if ((v < 0) || (v >= virtualScreenHeight))
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
h %= virtualScreenWidth;
|
|
v %= virtualScreenHeight;
|
|
|
|
if (h < 0)
|
|
h += virtualScreenWidth;
|
|
|
|
if (v < 0)
|
|
v += virtualScreenHeight;
|
|
}
|
|
|
|
int characterH = h / CharacterWidth;
|
|
int characterPixelH = h % CharacterWidth;
|
|
|
|
int characterV = v / CharacterHeight;
|
|
int characterPixelV = v % CharacterHeight;
|
|
|
|
int characterNumber = (characterV * (virtualScreenWidth / CharacterWidth)) + characterH;
|
|
int characterPixelNumber = (characterPixelV * CharacterWidth) + characterPixelH;
|
|
|
|
unsafe
|
|
{
|
|
Byte characterName = Unsafe.Read<Byte>((Byte*)_vram + screenBaseBlockOffset + characterNumber);
|
|
|
|
const int CharacterSize = 64;
|
|
UInt32 characterOffset = (UInt32)(characterBaseBlockOffset + (characterName * CharacterSize));
|
|
|
|
const UInt32 ObjectCharacterDataOffset = 0x1_0000;
|
|
|
|
if (characterOffset >= ObjectCharacterDataOffset)
|
|
continue;
|
|
|
|
Byte colorNumber = Unsafe.Read<Byte>((Byte*)_vram + characterOffset + characterPixelNumber);
|
|
|
|
if (!isFirst && (colorNumber == 0))
|
|
continue;
|
|
|
|
UInt16 color = Unsafe.Read<UInt16>((UInt16*)_paletteRAM + colorNumber);
|
|
|
|
int displayPixelNumber = displayPixelNumberBegin + hcount;
|
|
Unsafe.Add(ref displayFrameBufferDataRef, displayPixelNumber) = color;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void RenderObjects(UInt16 bgPriority)
|
|
{
|
|
ref UInt16 displayFrameBufferDataRef = ref MyUnSafeCommon.GetArrayDataReference(_displayFrameBuffer);
|
|
|
|
int displayPixelNumberBegin = _VCOUNT * DisplayScreenWidth;
|
|
|
|
UInt16 mappingFormat = (UInt16)((_DISPCNT >> 6) & 1);
|
|
|
|
for (int objNumber = 127; objNumber >= 0; --objNumber)
|
|
{
|
|
unsafe
|
|
{
|
|
UInt16 attribute0 = Unsafe.Read<UInt16>((UInt16*)_oam + (objNumber * 4));
|
|
UInt16 attribute1 = Unsafe.Read<UInt16>((UInt16*)_oam + (objNumber * 4) + 1);
|
|
UInt16 attribute2 = Unsafe.Read<UInt16>((UInt16*)_oam + (objNumber * 4) + 2);
|
|
|
|
UInt16 shape = (UInt16)((attribute0 >> 14) & 0b11);
|
|
UInt16 colorMode = (UInt16)((attribute0 >> 13) & 1);
|
|
UInt16 rotationScalingFlag = (UInt16)((attribute0 >> 8) & 1);
|
|
UInt16 yCoordinate = (UInt16)(attribute0 & 0xff);
|
|
|
|
UInt16 objSize = (UInt16)((attribute1 >> 14) & 0b11);
|
|
UInt16 verticalFlipFlag = (UInt16)((attribute1 >> 13) & 1);
|
|
UInt16 horizontalFlipFlag = (UInt16)((attribute1 >> 12) & 1);
|
|
UInt16 xCoordinate = (UInt16)(attribute1 & 0x1ff);
|
|
|
|
UInt16 colorPalette = (UInt16)((attribute2 >> 12) & 0b1111);
|
|
UInt16 objPriority = (UInt16)((attribute2 >> 10) & 0b11);
|
|
UInt16 characterName = (UInt16)(attribute2 & 0x3ff);
|
|
|
|
if (objPriority != bgPriority)
|
|
continue;
|
|
|
|
(int objWidth, int objHeight) = (shape, objSize) switch
|
|
{
|
|
// square
|
|
(0b00, 0b00) => (8, 8),
|
|
(0b00, 0b01) => (16, 16),
|
|
(0b00, 0b10) => (32, 32),
|
|
(0b00, 0b11) => (64, 64),
|
|
|
|
// horizontal rectangle
|
|
(0b01, 0b00) => (16, 8),
|
|
(0b01, 0b01) => (32, 8),
|
|
(0b01, 0b10) => (32, 16),
|
|
(0b01, 0b11) => (64, 32),
|
|
|
|
// vertical rectangle
|
|
(0b10, 0b00) => (8, 16),
|
|
(0b10, 0b01) => (8, 32),
|
|
(0b10, 0b10) => (16, 32),
|
|
(0b10, 0b11) => (32, 64),
|
|
|
|
// prohibited
|
|
// TODO: verify
|
|
_ => (0, 0)
|
|
};
|
|
|
|
const int VirtualScreenWidth = 512;
|
|
const int VirtualScreenHeight = 256;
|
|
|
|
int left = xCoordinate;
|
|
int right = (xCoordinate + objWidth) % VirtualScreenWidth;
|
|
|
|
int top = yCoordinate;
|
|
int bottom = (yCoordinate + objHeight) % VirtualScreenHeight;
|
|
|
|
bool leftHidden = left >= DisplayScreenWidth;
|
|
bool rightHidden = right >= DisplayScreenWidth;
|
|
|
|
bool topHidden = top >= DisplayScreenHeight;
|
|
bool bottomHidden = bottom >= DisplayScreenHeight;
|
|
|
|
if ((leftHidden && rightHidden) || (topHidden && bottomHidden))
|
|
continue;
|
|
|
|
int hBegin;
|
|
int vBegin;
|
|
|
|
if (leftHidden)
|
|
{
|
|
hBegin = VirtualScreenWidth - left;
|
|
left = 0;
|
|
}
|
|
else if (rightHidden)
|
|
{
|
|
hBegin = 0;
|
|
right = DisplayScreenWidth;
|
|
}
|
|
else
|
|
{
|
|
hBegin = 0;
|
|
}
|
|
|
|
if (topHidden)
|
|
{
|
|
vBegin = VirtualScreenHeight - top;
|
|
top = 0;
|
|
}
|
|
else if (bottomHidden)
|
|
{
|
|
vBegin = 0;
|
|
bottom = DisplayScreenHeight;
|
|
}
|
|
else
|
|
{
|
|
vBegin = 0;
|
|
}
|
|
|
|
if ((top > _VCOUNT) || (bottom <= _VCOUNT))
|
|
continue;
|
|
|
|
int v = _VCOUNT - top + vBegin;
|
|
|
|
int characterV = v / CharacterHeight;
|
|
int characterPixelV = v % CharacterHeight;
|
|
|
|
int characterNumberBegin;
|
|
int characterPixelNumberBegin = CharacterWidth;
|
|
|
|
if (verticalFlipFlag == 0)
|
|
{
|
|
characterNumberBegin = characterV;
|
|
characterPixelNumberBegin *= characterPixelV;
|
|
}
|
|
else
|
|
{
|
|
characterNumberBegin = (objHeight / CharacterHeight) - 1 - characterV;
|
|
characterPixelNumberBegin *= CharacterHeight - 1 - characterPixelV;
|
|
}
|
|
|
|
// 2D mapping
|
|
if (mappingFormat == 0)
|
|
characterNumberBegin *= (colorMode == 0) ? 32 : 16;
|
|
|
|
// 1D mapping
|
|
else
|
|
characterNumberBegin *= objWidth / CharacterWidth;
|
|
|
|
for (int hcount = left; hcount < right; ++hcount)
|
|
{
|
|
int h = hcount - left + hBegin;
|
|
|
|
int characterH = h / CharacterWidth;
|
|
int characterPixelH = h % CharacterWidth;
|
|
|
|
int characterNumber = characterNumberBegin;
|
|
int characterPixelNumber = characterPixelNumberBegin;
|
|
|
|
if (horizontalFlipFlag == 0)
|
|
{
|
|
characterNumber += characterH;
|
|
characterPixelNumber += characterPixelH;
|
|
}
|
|
else
|
|
{
|
|
characterNumber += (objWidth / CharacterWidth) - 1 - characterH;
|
|
characterPixelNumber += CharacterWidth - 1 - characterPixelH;
|
|
}
|
|
|
|
const UInt32 CharacterDataOffset = 0x1_0000;
|
|
const UInt32 PaletteOffset = 0x200;
|
|
|
|
UInt16 color;
|
|
|
|
// 16 colors x 16 palettes
|
|
if (colorMode == 0)
|
|
{
|
|
const int CharacterSize = 32;
|
|
Byte colorNumber = Unsafe.Read<Byte>((Byte*)_vram + CharacterDataOffset + (characterName * 32) + (characterNumber * CharacterSize) + (characterPixelNumber / 2));
|
|
|
|
if ((characterPixelNumber % 2) == 0)
|
|
colorNumber &= 0b1111;
|
|
else
|
|
colorNumber >>= 4;
|
|
|
|
if (colorNumber == 0)
|
|
continue;
|
|
|
|
color = Unsafe.Read<UInt16>((Byte*)_paletteRAM + PaletteOffset + (colorPalette * 16 * 2) + (colorNumber * 2));
|
|
}
|
|
|
|
// 256 colors x 1 palette
|
|
else
|
|
{
|
|
const int CharacterSize = 64;
|
|
Byte colorNumber = Unsafe.Read<Byte>((Byte*)_vram + CharacterDataOffset + (characterName * 32) + (characterNumber * CharacterSize) + characterPixelNumber);
|
|
|
|
if (colorNumber == 0)
|
|
continue;
|
|
|
|
color = Unsafe.Read<UInt16>((Byte*)_paletteRAM + PaletteOffset + (colorNumber * 2));
|
|
}
|
|
|
|
int displayPixelNumber = displayPixelNumberBegin + hcount;
|
|
Unsafe.Add(ref displayFrameBufferDataRef, displayPixelNumber) = color;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|