//Queue<double> avr = new Queue<double>(); // Step one cycle public void Step() { // If we change these, change vblank cycle count too LcdCycles++; FrameCycles++; switch (Mode) { case LcdMode.ScanlineRendering: if (LcdCycles >= HDraw_Length) { #if THREADED_SCANLINE if (scanlineThread.IsAlive == false) { throw new ArgumentException("Thread pop!"); } // Wait for scanline rendering to finsih while (drawScanline == true) { } #endif LcdCycles -= HDraw_Length; Mode = LcdMode.HBlank; if (DispStatRegister.HBlankIrqEnabled) { gba.Interrupts.RequestInterrupt(Interrupts.InterruptType.HBlank); } // Start hblank DMA's (HDMA) for(int i=0; i < 4; i++) { if(gba.Dma[i].DmaCnt.StartTiming == DmaControlRegister.DmaStartTiming.HBlank && gba.Dma[i].DmaCnt.ChannelEnabled == true) { gba.Dma[i].Started = true; } } } break; case LcdMode.HBlank: if(LcdCycles >= HBlank_Length) { LcdCycles -= HBlank_Length; CurrentScanline++; if (DispStatRegister.VCounterIrqEnabled && CurrentScanline == gba.LcdController.DispStatRegister.VCountSetting) { gba.Interrupts.RequestInterrupt(Interrupts.InterruptType.VCounterMatch); } if (CurrentScanline == 160) { Mode = LcdMode.VBlank; VblankScanlineCycles = 0; if (DispStatRegister.VBlankIrqEnabled) { gba.Interrupts.RequestInterrupt(Interrupts.InterruptType.VBlank); } // We can set the renderer drawing the frame as soon as we enter vblank lock (FrameBuffer) { // Flip frames if (FrameBuffer == frameBuffer0) { FrameBuffer = frameBuffer1; drawBuffer = frameBuffer0; } else { FrameBuffer = frameBuffer0; drawBuffer = frameBuffer1; } // Clear the draw buffer to the background color //using (var graphics = Graphics.FromImage(drawBuffer.Bitmap)) //{ // graphics.Clear(Palettes.Palette0[0]); //} } // lock to 60fps - 1000 / 60.0 double fps60 = 16.6666666; /* double frameTime = gba.EmulatorTimer.Elapsed.TotalMilliseconds - lastFrameTime; avr.Enqueue(frameTime); if (avr.Count == 11) { avr.Dequeue(); double frameAverage = avr.Average(); gba.LogMessage(String.Format("frame Ms {0:N2}", frameAverage)); } */ while (gba.EmulatorTimer.Elapsed.TotalMilliseconds - lastFrameTime < fps60) { } lastFrameTime = gba.EmulatorTimer.Elapsed.TotalMilliseconds; if (gba.OnFrame != null) { gba.OnFrame(); } } else { Mode = LcdMode.ScanlineRendering; Render(); } } break; case LcdMode.VBlank: VblankScanlineCycles++; if (LcdCycles >= VBlank_Length) { // 160 + 68 lines per screen if(CurrentScanline != 227 || FrameCycles != ScreenRefresh_Length) { throw new InvalidOperationException("LCD: Scanlines / cycles mismatch"); } LcdCycles -= VBlank_Length; FrameCycles = 0; CurrentScanline = 0; Mode = LcdMode.ScanlineRendering; Render(); if (DispStatRegister.VCounterIrqEnabled && CurrentScanline == gba.LcdController.DispStatRegister.VCountSetting) { gba.Interrupts.RequestInterrupt(Interrupts.InterruptType.VCounterMatch); } } else { // HBlanks IRQ's still fire durng vblank if(VblankScanlineCycles == HDraw_Length) { if (DispStatRegister.HBlankIrqEnabled) { gba.Interrupts.RequestInterrupt(Interrupts.InterruptType.HBlank); } } // We are within vblank if(VblankScanlineCycles == ScanLine_Length) { VblankScanlineCycles = 0; CurrentScanline++; if (DispStatRegister.VCounterIrqEnabled && CurrentScanline == gba.LcdController.DispStatRegister.VCountSetting) { gba.Interrupts.RequestInterrupt(Interrupts.InterruptType.VCounterMatch); } } } break; } }
public bool RenderSpritePixel(DirectBitmap drawBuffer, int screenX, int screenY, int priority, bool windowing, int windowRegion, ref int bgVisibleOverride) { int paletteIndex; foreach (var obj in priorityObjList[priority]) { // Clip against the bounding box which can be DoubleSize. This is the only time doublesize is actually checked if (obj.BoundingBoxScreenSpace.ContainsPoint(screenX, screenY) == false) { continue; } if (obj.Attributes.RotationAndScaling) { int sourceWidth = obj.Attributes.Dimensions.Width; int sourceHeight = obj.Attributes.Dimensions.Height; // The game will have set up the matrix to be the inverse texture mapping matrix. I.E it maps from screen space to texture space. Just what we need! OamAffineMatrix rotScaleMatrix = obj.Attributes.AffineMatrix(); // NB: Order of operations counts here! // Transform with the origin set to the centre of the sprite (that's what the - width/height /2 below is for) int originX = screenX - obj.Attributes.XPositionAdjusted() - (sourceWidth / 2); int originY = screenY - obj.Attributes.YPositionAdjusted() - (sourceHeight / 2); // Not well documented anywhere but when double size is enabled we render offset by half the original source width / height if (obj.Attributes.DoubleSize) { originX -= sourceWidth / 2; originY -= sourceHeight / 2; } int transformedX, transformedY; rotScaleMatrix.Multiply(originX, originY, out transformedX, out transformedY); // Transform back from centre of sprite transformedX += (sourceWidth / 2); transformedY += (sourceHeight / 2); paletteIndex = obj.PixelValue(transformedX, transformedY); } else { //paletteIndex = obj.PixelScreenValue(x, scanline); paletteIndex = obj.PixelValue(screenX - obj.Attributes.XPositionAdjusted(), screenY - obj.Attributes.YPositionAdjusted()); } // Pal 0 == Transparent if (paletteIndex == 0) { continue; } // TODO: I *think* this will render the Obj window correctly but i cannot test it yet // This pixel belongs to a sprite in the Obj Window and Win 0 & 1 are not enclosing this pixel if (windowing && gba.LcdController.DisplayControlRegister.DisplayObjWin && obj.Attributes.Mode == ObjAttributes.ObjMode.ObjWindow && ((windowRegion & (int)TileHelpers.WindowRegion.WindowIn) == 0)) { bgVisibleOverride = gba.LcdController.Windows[(int)Window.WindowName.WindowObj].DisplayBg0 | gba.LcdController.Windows[(int)Window.WindowName.WindowObj].DisplayBg1 | gba.LcdController.Windows[(int)Window.WindowName.WindowObj].DisplayBg2 | gba.LcdController.Windows[(int)Window.WindowName.WindowObj].DisplayBg3; return(false); } drawBuffer.SetPixel(screenX, screenY, gba.LcdController.Palettes.Palette1[paletteIndex]); return(true); } return(false); }