namespace GarboDev { public class VideoManager { public delegate void OnPresent(uint[] data); private Memory memory = null; private IRenderer renderer = null; private OnPresent presenter; private GbaManager gbaManager; private int curLine; public Memory Memory { set{ this.memory = value; } } public IRenderer Renderer { set { this.renderer = value; this.renderer.Memory = this.memory; } } public OnPresent Presenter { set { this.presenter = value; } } public VideoManager(GbaManager gbaManager) { this.gbaManager = gbaManager; } public void Reset() { this.curLine = 0; this.renderer.Memory = memory; this.renderer.Reset(); } private void EnterVBlank(Arm7Processor processor) { ushort dispstat = Memory.ReadU16(this.memory.IORam, Memory.DISPSTAT); dispstat |= 1; Memory.WriteU16(this.memory.IORam, Memory.DISPSTAT, dispstat); // Render the frame this.gbaManager.FramesRendered++; this.presenter(this.renderer.ShowFrame()); if ((dispstat & (1 << 3)) != 0) { // Fire the vblank irq processor.RequestIrq(0); } // Check for DMA triggers this.memory.VBlankDma(); } private void LeaveVBlank(Arm7Processor processor) { ushort dispstat = Memory.ReadU16(this.memory.IORam, Memory.DISPSTAT); dispstat &= 0xFFFE; Memory.WriteU16(this.memory.IORam, Memory.DISPSTAT, dispstat); processor.UpdateKeyState(); // Update the rot/scale values this.memory.Bgx[0] = (int)Memory.ReadU32(this.memory.IORam, Memory.BG2X_L); this.memory.Bgx[1] = (int)Memory.ReadU32(this.memory.IORam, Memory.BG3X_L); this.memory.Bgy[0] = (int)Memory.ReadU32(this.memory.IORam, Memory.BG2Y_L); this.memory.Bgy[1] = (int)Memory.ReadU32(this.memory.IORam, Memory.BG3Y_L); } public void EnterHBlank(Arm7Processor processor) { ushort dispstat = Memory.ReadU16(this.memory.IORam, Memory.DISPSTAT); dispstat |= 1 << 1; Memory.WriteU16(this.memory.IORam, Memory.DISPSTAT, dispstat); // Advance the bgx registers for (int bg = 0; bg <= 1; bg++) { short dmx = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PB + (uint)bg * 0x10); short dmy = (short)Memory.ReadU16(this.memory.IORam, Memory.BG2PD + (uint)bg * 0x10); this.memory.Bgx[bg] += dmx; this.memory.Bgy[bg] += dmy; } if (this.curLine < 160) { this.memory.HBlankDma(); // Trigger hblank irq if ((dispstat & (1 << 4)) != 0) { processor.RequestIrq(1); } } } /// /// /// /// /// true if end of frame public bool LeaveHBlank(Arm7Processor processor) { bool ret = false; ushort dispstat = Memory.ReadU16(this.memory.IORam, Memory.DISPSTAT); dispstat &= 0xFFF9; Memory.WriteU16(this.memory.IORam, Memory.DISPSTAT, dispstat); // Move to the next line this.curLine++; if (this.curLine >= 228) { // Start again at the beginning this.curLine = 0; } // Update registers Memory.WriteU16(this.memory.IORam, Memory.VCOUNT, (ushort)this.curLine); // Check for vblank if (this.curLine == 160) { this.EnterVBlank(processor); ret = true; } else if (this.curLine == 0) { this.LeaveVBlank(processor); } // Check y-line trigger if (((dispstat >> 8) & 0xff) == this.curLine) { dispstat = (ushort)(Memory.ReadU16(this.memory.IORam, Memory.DISPSTAT) | (1 << 2)); Memory.WriteU16(this.memory.IORam, Memory.DISPSTAT, dispstat); if ((dispstat & (1 << 5)) != 0) { processor.RequestIrq(2); } } return ret; } public void RenderLine() { if (this.curLine < 160) { this.renderer.RenderLine(this.curLine); } } } }