public VPC(PCEngine pce, VDC vdc1, VDC vdc2, VCE vce, HuC6280 cpu) { PCE = pce; VDC1 = vdc1; VDC2 = vdc2; VCE = vce; CPU = cpu; // latch initial video buffer FrameBuffer = vdc1.GetVideoBuffer(); FrameWidth = vdc1.BufferWidth; FrameHeight = vdc1.BufferHeight; }
private void Init(GameInfo game, byte[] rom) { Cpu = new HuC6280(MemoryCallbacks); VCE = new VCE(); VDC1 = new VDC(this, Cpu, VCE); PSG = new HuC6280PSG(735); SCSI = new ScsiCDBus(this, disc); Cpu.Logger = s => Tracer.Put(s); if (TurboGrafx) { Ram = new byte[0x2000]; Cpu.ReadMemory21 = ReadMemory; Cpu.WriteMemory21 = WriteMemory; Cpu.WriteVDC = VDC1.WriteVDC; _soundProvider = PSG; CDAudio = new CDAudio(null, 0); } else if (SuperGrafx) { VDC2 = new VDC(this, Cpu, VCE); VPC = new VPC(this, VDC1, VDC2, VCE, Cpu); Ram = new byte[0x8000]; Cpu.ReadMemory21 = ReadMemorySGX; Cpu.WriteMemory21 = WriteMemorySGX; Cpu.WriteVDC = VDC1.WriteVDC; _soundProvider = PSG; CDAudio = new CDAudio(null, 0); } else if (TurboCD) { Ram = new byte[0x2000]; CDRam = new byte[0x10000]; ADPCM = new ADPCM(this, SCSI); Cpu.ReadMemory21 = ReadMemoryCD; Cpu.WriteMemory21 = WriteMemoryCD; Cpu.WriteVDC = VDC1.WriteVDC; CDAudio = new CDAudio(disc); SetCDAudioCallback(); PSG.MaxVolume = short.MaxValue * 3 / 4; SoundMixer = new SoundMixer(735, PSG, CDAudio, ADPCM); _soundProvider = SoundMixer; Cpu.ThinkAction = cycles => { SCSI.Think(); ADPCM.Think(cycles); }; } if (rom.Length == 0x60000) { // 384k roms require special loading code. Why ;_; // In memory, 384k roms look like [1st 256k][Then full 384k] RomData = new byte[0xA0000]; var origRom = rom; for (int i = 0; i < 0x40000; i++) { RomData[i] = origRom[i]; } for (int i = 0; i < 0x60000; i++) { RomData[i + 0x40000] = origRom[i]; } RomLength = RomData.Length; } else if (rom.Length > 1024 * 1024) { // If the rom is bigger than 1 megabyte, switch to Street Fighter 2 mapper Cpu.ReadMemory21 = ReadMemorySF2; Cpu.WriteMemory21 = WriteMemorySF2; RomData = rom; RomLength = RomData.Length; // user request: current value of the SF2MapperLatch on the tracelogger Cpu.Logger = s => Tracer.Put(new TraceInfo { Disassembly = $"{SF2MapperLatch:X1}:{s}", RegisterInfo = "" }); } else { // normal rom. RomData = rom; RomLength = RomData.Length; } if (game["BRAM"] || Type == NecSystemType.TurboCD) { BramEnabled = true; BRAM = new byte[2048]; // pre-format BRAM. damn are we helpful. BRAM[0] = 0x48; BRAM[1] = 0x55; BRAM[2] = 0x42; BRAM[3] = 0x4D; BRAM[4] = 0x00; BRAM[5] = 0x88; BRAM[6] = 0x10; BRAM[7] = 0x80; } if (game["SuperSysCard"]) { SuperRam = new byte[0x30000]; } if (game["ArcadeCard"]) { ArcadeRam = new byte[0x200000]; ArcadeCard = true; ArcadeCardRewindHack = Settings.ArcadeCardRewindHack; for (int i = 0; i < 4; i++) { ArcadePage[i] = new ArcadeCardPage(); } } if (game["PopulousSRAM"]) { PopulousRAM = new byte[0x8000]; Cpu.ReadMemory21 = ReadMemoryPopulous; Cpu.WriteMemory21 = WriteMemoryPopulous; } // the gamedb can force sprite limit on, ignoring settings if (game["ForceSpriteLimit"] || game.NotInDatabase) { ForceSpriteLimit = true; } if (game["CdVol"]) { CDAudio.MaxVolume = int.Parse(game.OptionValue("CdVol")); } if (game["PsgVol"]) { PSG.MaxVolume = int.Parse(game.OptionValue("PsgVol")); } if (game["AdpcmVol"]) { ADPCM.MaxVolume = int.Parse(game.OptionValue("AdpcmVol")); } // the gamedb can also force equalizevolumes on if (TurboCD && (Settings.EqualizeVolume || game["EqualizeVolumes"] || game.NotInDatabase)) { SoundMixer.EqualizeVolumes(); } // Ok, yes, HBlankPeriod's only purpose is game-specific hax. // 1) At least they're not coded directly into the emulator, but instead data-driven. // 2) The games which have custom HBlankPeriods work without it, the override only // serves to clean up minor gfx anomalies. // 3) There's no point in haxing the timing with incorrect values in an attempt to avoid this. // The proper fix is cycle-accurate/bus-accurate timing. That isn't coming to the C# // version of this core. Let's just acknolwedge that the timing is imperfect and fix // it in the least intrusive and most honest way we can. if (game["HBlankPeriod"]) { VDC1.HBlankCycles = game.GetIntValue("HBlankPeriod"); } // This is also a hack. Proper multi-res/TV emulation will be a native-code core feature. if (game["MultiResHack"]) { VDC1.MultiResHack = game.GetIntValue("MultiResHack"); } Cpu.ResetPC(); Tracer = new TraceBuffer { Header = Cpu.TraceHeader }; var ser = new BasicServiceProvider(this); ServiceProvider = ser; ser.Register <ITraceable>(Tracer); ser.Register <IDisassemblable>(Cpu); ser.Register <IVideoProvider>((IVideoProvider)VPC ?? VDC1); ser.Register <ISoundProvider>(_soundProvider); ser.Register <IStatable>(new StateSerializer(SyncState)); SetupMemoryDomains(); }
private void checkBoxVDC2_CheckedChanged(object sender, EventArgs e) { vdc = checkBoxVDC2.Checked ? emu.VDC2 : emu.VDC1; UpdateValues(); }
private void RenderSpritesScanline(VDC vdc, byte lowPriority, byte highPriority, bool show) { if (vdc.SpritesEnabled == false) { return; } // clear inter-sprite priority buffer Array.Clear(InterSpritePriorityBuffer, 0, FrameWidth); var testRange = new MutableIntRange(0, vdc.ActiveLine + 1); for (int i = 0; i < 64; i++) { int y = (vdc.SpriteAttributeTable[(i * 4) + 0] & 1023) - 64; int x = (vdc.SpriteAttributeTable[(i * 4) + 1] & 1023) - 32; ushort flags = vdc.SpriteAttributeTable[(i * 4) + 3]; byte height = heightTable[(flags >> 12) & 3]; testRange.Min = vdc.ActiveLine - height; if (!testRange.StrictContains(y)) { continue; } int patternNo = (((vdc.SpriteAttributeTable[(i * 4) + 2]) >> 1) & 0x1FF); int paletteBase = 256 + ((flags & 15) * 16); int width = (flags & 0x100) == 0 ? 16 : 32; bool priority = (flags & 0x80) != 0; bool hflip = (flags & 0x0800) != 0; bool vflip = (flags & 0x8000) != 0; if (width == 32) { patternNo &= 0x1FE; } int yofs; if (vflip == false) { yofs = (vdc.ActiveLine - y) & 15; if (height == 32) { patternNo &= 0x1FD; if (vdc.ActiveLine - y >= 16) { y += 16; patternNo += 2; } } else if (height == 64) { patternNo &= 0x1F9; if (vdc.ActiveLine - y >= 48) { y += 48; patternNo += 6; } else if (vdc.ActiveLine - y >= 32) { y += 32; patternNo += 4; } else if (vdc.ActiveLine - y >= 16) { y += 16; patternNo += 2; } } } else // vflip == true { yofs = 15 - ((vdc.ActiveLine - y) & 15); if (height == 32) { patternNo &= 0x1FD; if (vdc.ActiveLine - y < 16) { y += 16; patternNo += 2; } } else if (height == 64) { patternNo &= 0x1F9; if (vdc.ActiveLine - y < 16) { y += 48; patternNo += 6; } else if (vdc.ActiveLine - y < 32) { y += 32; patternNo += 4; } else if (vdc.ActiveLine - y < 48) { y += 16; patternNo += 2; } } } if (hflip == false) { if (x + width > 0 && y + height > 0) { for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++) { byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + (xs - x)]; if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0) { InterSpritePriorityBuffer[xs] = 1; byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { if (show) { FrameBuffer[((vdc.ActiveLine + vdc.ViewStartLine - PCE.Settings.Top_Line) * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; } PriorityBuffer[xs] = myPriority; } } } } if (width == 32) { patternNo++; x += 16; for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++) { byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + (xs - x)]; if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0) { InterSpritePriorityBuffer[xs] = 1; byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { if (show) { FrameBuffer[((vdc.ActiveLine + vdc.ViewStartLine - PCE.Settings.Top_Line) * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; } PriorityBuffer[xs] = myPriority; } } } } } else { // hflip = true if (x + width > 0 && y + height > 0) { if (width == 32) { patternNo++; } for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++) { byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + 15 - (xs - x)]; if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0) { InterSpritePriorityBuffer[xs] = 1; byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { if (show) { FrameBuffer[((vdc.ActiveLine + vdc.ViewStartLine - PCE.Settings.Top_Line) * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; } PriorityBuffer[xs] = myPriority; } } } if (width == 32) { patternNo--; x += 16; for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++) { byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + 15 - (xs - x)]; if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0) { InterSpritePriorityBuffer[xs] = 1; byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { if (show) { FrameBuffer[((vdc.ActiveLine + vdc.ViewStartLine - PCE.Settings.Top_Line) * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; } PriorityBuffer[xs] = myPriority; } } } } } } } }
private unsafe void RenderBackgroundScanline(VDC vdc, byte priority, bool show) { if (vdc.BackgroundEnabled == false) { return; } // per-line parameters int vertLine = vdc.BackgroundY; vertLine %= vdc.BatHeight * 8; int yTile = (vertLine / 8); int yOfs = vertLine % 8; int xScroll = vdc.Registers[BXR] & 0x3FF; int BatRowMask = vdc.BatWidth - 1; fixed(ushort *VRAMptr = vdc.VRAM) fixed(int *PALptr = VCE.Palette) fixed(byte *Patternptr = vdc.PatternBuffer) fixed(int *FBptr = FrameBuffer) fixed(byte *Priortyptr = PriorityBuffer) { // pointer to the BAT and the framebuffer for this line ushort *BatRow = VRAMptr + yTile * vdc.BatWidth; int * dst = FBptr + (vdc.ActiveLine + vdc.ViewStartLine - PCE.Settings.Top_Line) * FrameWidth; // parameters that change per tile ushort BatEnt; int tileNo, paletteNo, paletteBase; byte * src; // calculate tile number and offset for first tile int xTile = (xScroll >> 3) & BatRowMask; int xOfs = xScroll & 7; // update per-tile parameters for first tile BatEnt = BatRow[xTile]; tileNo = BatEnt & 2047; paletteNo = BatEnt >> 12; paletteBase = paletteNo * 16; src = Patternptr + (tileNo << 6 | yOfs << 3 | xOfs); for (int x = 0; x < FrameWidth; x++) { if (Priortyptr[x] < priority) { byte c = *src; if (c != 0) { dst[x] = show ? PALptr[paletteBase + c] : PALptr[0]; Priortyptr[x] = priority; } } xOfs++; src++; if (xOfs == 8) { // update tile number xOfs = 0; xTile++; xTile &= BatRowMask; // update per-tile parameters BatEnt = BatRow[xTile]; tileNo = BatEnt & 2047; paletteNo = BatEnt >> 12; paletteBase = paletteNo * 16; src = Patternptr + (tileNo << 6 | yOfs << 3 | xOfs); } } } }
void Init(GameInfo game, byte[] rom) { Controller = NullController.GetNullController(); Cpu = new HuC6280(CoreComm); VCE = new VCE(); VDC1 = new VDC(this, Cpu, VCE); PSG = new HuC6280PSG(); SCSI = new ScsiCDBus(this, disc); Cpu.Logger = (s) => CoreComm.Tracer.Put(s); if (TurboGrafx) { Ram = new byte[0x2000]; Cpu.ReadMemory21 = ReadMemory; Cpu.WriteMemory21 = WriteMemory; Cpu.WriteVDC = VDC1.WriteVDC; soundProvider = PSG; CDAudio = new CDAudio(null, 0); } else if (SuperGrafx) { VDC2 = new VDC(this, Cpu, VCE); VPC = new VPC(this, VDC1, VDC2, VCE, Cpu); Ram = new byte[0x8000]; Cpu.ReadMemory21 = ReadMemorySGX; Cpu.WriteMemory21 = WriteMemorySGX; Cpu.WriteVDC = VDC1.WriteVDC; soundProvider = PSG; CDAudio = new CDAudio(null, 0); } else if (TurboCD) { Ram = new byte[0x2000]; CDRam = new byte[0x10000]; ADPCM = new ADPCM(this, SCSI); Cpu.ReadMemory21 = ReadMemoryCD; Cpu.WriteMemory21 = WriteMemoryCD; Cpu.WriteVDC = VDC1.WriteVDC; CDAudio = new CDAudio(disc); SetCDAudioCallback(); PSG.MaxVolume = short.MaxValue * 3 / 4; SoundMixer = new SoundMixer(PSG, CDAudio, ADPCM); SoundSynchronizer = new MetaspuSoundProvider(ESynchMethod.ESynchMethod_V); soundProvider = SoundSynchronizer; Cpu.ThinkAction = (cycles) => { SCSI.Think(); ADPCM.Think(cycles); }; } if (rom.Length == 0x60000) { // 384k roms require special loading code. Why ;_; // In memory, 384k roms look like [1st 256k][Then full 384k] RomData = new byte[0xA0000]; var origRom = rom; for (int i = 0; i < 0x40000; i++) RomData[i] = origRom[i]; for (int i = 0; i < 0x60000; i++) RomData[i + 0x40000] = origRom[i]; RomLength = RomData.Length; } else if (rom.Length > 1024 * 1024) { // If the rom is bigger than 1 megabyte, switch to Street Fighter 2 mapper Cpu.ReadMemory21 = ReadMemorySF2; Cpu.WriteMemory21 = WriteMemorySF2; RomData = rom; RomLength = RomData.Length; // user request: current value of the SF2MapperLatch on the tracelogger Cpu.Logger = (s) => CoreComm.Tracer.Put(string.Format("{0:X1}:{1}", SF2MapperLatch, s)); } else { // normal rom. RomData = rom; RomLength = RomData.Length; } if (game["BRAM"] || Type == NecSystemType.TurboCD) { BramEnabled = true; BRAM = new byte[2048]; // pre-format BRAM. damn are we helpful. BRAM[0] = 0x48; BRAM[1] = 0x55; BRAM[2] = 0x42; BRAM[3] = 0x4D; BRAM[4] = 0x00; BRAM[5] = 0x88; BRAM[6] = 0x10; BRAM[7] = 0x80; } if (game["SuperSysCard"]) SuperRam = new byte[0x30000]; if (game["ArcadeCard"]) { ArcadeRam = new byte[0x200000]; ArcadeCard = true; ArcadeCardRewindHack = _settings.ArcadeCardRewindHack; for (int i = 0; i < 4; i++) ArcadePage[i] = new ArcadeCardPage(); } if (game["PopulousSRAM"]) { PopulousRAM = new byte[0x8000]; Cpu.ReadMemory21 = ReadMemoryPopulous; Cpu.WriteMemory21 = WriteMemoryPopulous; } // the gamedb can force sprite limit on, ignoring settings if (game["ForceSpriteLimit"] || game.NotInDatabase) ForceSpriteLimit = true; if (game["CdVol"]) CDAudio.MaxVolume = int.Parse(game.OptionValue("CdVol")); if (game["PsgVol"]) PSG.MaxVolume = int.Parse(game.OptionValue("PsgVol")); if (game["AdpcmVol"]) ADPCM.MaxVolume = int.Parse(game.OptionValue("AdpcmVol")); // the gamedb can also force equalizevolumes on if (TurboCD && (_settings.EqualizeVolume || game["EqualizeVolumes"] || game.NotInDatabase)) SoundMixer.EqualizeVolumes(); // Ok, yes, HBlankPeriod's only purpose is game-specific hax. // 1) At least they're not coded directly into the emulator, but instead data-driven. // 2) The games which have custom HBlankPeriods work without it, the override only // serves to clean up minor gfx anomalies. // 3) There's no point in haxing the timing with incorrect values in an attempt to avoid this. // The proper fix is cycle-accurate/bus-accurate timing. That isn't coming to the C# // version of this core. Let's just acknolwedge that the timing is imperfect and fix // it in the least intrusive and most honest way we can. if (game["HBlankPeriod"]) VDC1.HBlankCycles = game.GetIntValue("HBlankPeriod"); // This is also a hack. Proper multi-res/TV emulation will be a native-code core feature. if (game["MultiResHack"]) VDC1.MultiResHack = game.GetIntValue("MultiResHack"); Cpu.ResetPC(); SetupMemoryDomains(); }
void RenderSpritesScanline(VDC vdc, byte lowPriority, byte highPriority, bool show) { if (vdc.SpritesEnabled == false) return; // clear inter-sprite priority buffer Array.Clear(InterSpritePriorityBuffer, 0, FrameWidth); for (int i = 0; i < 64; i++) { int y = (vdc.SpriteAttributeTable[(i * 4) + 0] & 1023) - 64; int x = (vdc.SpriteAttributeTable[(i * 4) + 1] & 1023) - 32; ushort flags = vdc.SpriteAttributeTable[(i * 4) + 3]; int height = heightTable[(flags >> 12) & 3]; if (y + height <= vdc.ActiveLine || y > vdc.ActiveLine) continue; int patternNo = (((vdc.SpriteAttributeTable[(i * 4) + 2]) >> 1) & 0x1FF); int paletteBase = 256 + ((flags & 15) * 16); int width = (flags & 0x100) == 0 ? 16 : 32; bool priority = (flags & 0x80) != 0; bool hflip = (flags & 0x0800) != 0; bool vflip = (flags & 0x8000) != 0; if (width == 32) patternNo &= 0x1FE; int yofs; if (vflip == false) { yofs = (vdc.ActiveLine - y) & 15; if (height == 32) { patternNo &= 0x1FD; if (vdc.ActiveLine - y >= 16) { y += 16; patternNo += 2; } } else if (height == 64) { patternNo &= 0x1F9; if (vdc.ActiveLine - y >= 48) { y += 48; patternNo += 6; } else if (vdc.ActiveLine - y >= 32) { y += 32; patternNo += 4; } else if (vdc.ActiveLine - y >= 16) { y += 16; patternNo += 2; } } } else // vflip == true { yofs = 15 - ((vdc.ActiveLine - y) & 15); if (height == 32) { patternNo &= 0x1FD; if (vdc.ActiveLine - y < 16) { y += 16; patternNo += 2; } } else if (height == 64) { patternNo &= 0x1F9; if (vdc.ActiveLine - y < 16) { y += 48; patternNo += 6; } else if (vdc.ActiveLine - y < 32) { y += 32; patternNo += 4; } else if (vdc.ActiveLine - y < 48) { y += 16; patternNo += 2; } } } if (hflip == false) { if (x + width > 0 && y + height > 0) { for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++) { byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + (xs - x)]; if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0) { InterSpritePriorityBuffer[xs] = 1; byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; PriorityBuffer[xs] = myPriority; } } } } if (width == 32) { patternNo++; x += 16; for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++) { byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + (xs - x)]; if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0) { InterSpritePriorityBuffer[xs] = 1; byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; PriorityBuffer[xs] = myPriority; } } } } } else { // hflip = true if (x + width > 0 && y + height > 0) { if (width == 32) patternNo++; for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++) { byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + 15 - (xs - x)]; if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0) { InterSpritePriorityBuffer[xs] = 1; byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; PriorityBuffer[xs] = myPriority; } } } if (width == 32) { patternNo--; x += 16; for (int xs = x >= 0 ? x : 0; xs < x + 16 && xs >= 0 && xs < FrameWidth; xs++) { byte pixel = vdc.SpriteBuffer[(patternNo * 256) + (yofs * 16) + 15 - (xs - x)]; if (pixel != 0 && InterSpritePriorityBuffer[xs] == 0) { InterSpritePriorityBuffer[xs] = 1; byte myPriority = priority ? highPriority : lowPriority; if (PriorityBuffer[xs] < myPriority) { if (show) FrameBuffer[(vdc.ActiveLine * FrameWidth) + xs] = VCE.Palette[paletteBase + pixel]; PriorityBuffer[xs] = myPriority; } } } } } } } }
unsafe void RenderBackgroundScanline(VDC vdc, byte priority, bool show) { if (vdc.BackgroundEnabled == false) return; // per-line parameters int vertLine = vdc.BackgroundY; vertLine %= vdc.BatHeight * 8; int yTile = (vertLine / 8); int yOfs = vertLine % 8; int xScroll = vdc.Registers[BXR] & 0x3FF; int BatRowMask = vdc.BatWidth - 1; fixed (ushort* VRAMptr = vdc.VRAM) fixed (int* PALptr = VCE.Palette) fixed (byte* Patternptr = vdc.PatternBuffer) fixed (int* FBptr = FrameBuffer) fixed (byte* Priortyptr = PriorityBuffer) { // pointer to the BAT and the framebuffer for this line ushort* BatRow = VRAMptr + yTile * vdc.BatWidth; int* dst = FBptr + vdc.ActiveLine * FrameWidth; // parameters that change per tile ushort BatEnt; int tileNo, paletteNo, paletteBase; byte* src; // calculate tile number and offset for first tile int xTile = (xScroll >> 3) & BatRowMask; int xOfs = xScroll & 7; // update per-tile parameters for first tile BatEnt = BatRow[xTile]; tileNo = BatEnt & 2047; paletteNo = BatEnt >> 12; paletteBase = paletteNo * 16; src = Patternptr + (tileNo << 6 | yOfs << 3 | xOfs); for (int x = 0; x < FrameWidth; x++) { if (Priortyptr[x] < priority) { byte c = *src; if (c != 0) { dst[x] = show ? PALptr[paletteBase + c] : PALptr[0]; Priortyptr[x] = priority; } } xOfs++; src++; if (xOfs == 8) { // update tile number xOfs = 0; xTile++; xTile &= BatRowMask; // update per-tile parameters BatEnt = BatRow[xTile]; tileNo = BatEnt & 2047; paletteNo = BatEnt >> 12; paletteBase = paletteNo * 16; src = Patternptr + (tileNo << 6 | yOfs << 3 | xOfs); } } } }