How to get the right colors in GameBoy Color?

0

I write GameBoy Color Emulator for fun and learn. Generating color for Gameboy classic work well, but in color version emulator generating bad colors.

I declared BPI, BPD and OPI, OPD. I set up color extraction from background memory and sprites and change it hex colors just like I do in classic version. There are some cases where some element has the correct colors.

BPI, BPD and OPI,OPD:

        public int[] backgroundMemory = new int[0x40];
        public int[] spriteMemory = new int[0x40];
        public int backgroundPaletteIndex;
        public int BackgroundPaletteData
        {
            get => backgroundMemory[backgroundPaletteIndex & 0x3F];
            set
            {
                backgroundMemory[backgroundPaletteIndex & 0x3F] = value;
                if ((backgroundPaletteIndex & 0x80) != 0)
                {
                    backgroundPaletteIndex = (0x80 | ((backgroundPaletteIndex + 1) & 0x3F));
                }
            }
        }
        public int objectPaletteIndex;
        public int ObjectPaletteData
        {
            get => spriteMemory[objectPaletteIndex & 0x3F];
            set
            {
                spriteMemory[objectPaletteIndex & 0x3F] = value;
                if ((objectPaletteIndex) != 0)
                {
                    ++objectPaletteIndex;
                }
            }
        }

write BPI, BPD, OPI, OPD from memory

case 0xFF68:
    ppu.backgroundPaletteIndex = value;
    break;
case 0xFF69:
    ppu.BackgroundPaletteData = value;
    break;
case 0xFF6A:
    ppu.objectPaletteIndex = value;
    break;
case 0xFF6B:
    ppu.ObjectPaletteData = value;
    break;

read BPI, BPD, OPI, OPD from memory

case 0xFF68:
    return ppu.backgroundPaletteIndex;
case 0xFF69:
    return ppu.BackgroundPaletteData;
case 0xFF6A:
    return ppu.objectPaletteIndex;
case 0xFF6B:
    return ppu.ObjectPaletteData;

Update background

public void UpdateBackground()
        {

            var tileMapAddress = backgroundTileMapDisplaySelect ? 0x1C00 : 0x1800;

            if (backgroundAndWindowTileDataSelect)
            {
                for (var i = 0; i < 32; ++i)
                {
                    for (var j = 0; j < 32; ++j, ++tileMapAddress)
                    {
                        if (!backgroundTileInvalidated[i, j] && !invalidateAllBackgroundTilesRequest) continue;
                        backgroundTileInvalidated[i, j] = false;
                        var tileDataAddress = _memory.videoRam[tileMapAddress] << 4;
                        var y = i << 3;
                        var x = j << 3;
                        for (var k = 0; k < 8; ++k)
                        {
                            var lowByte = _memory.videoRam[tileDataAddress++];
                            var highByte = _memory.videoRam[tileDataAddress++] << 1;
                            for (var b = 7; b >= 0; --b)
                            {
                                var index = (0x02 & highByte) | (0x01 & lowByte);
                                if (_colorMode)
                                {
                                    backgroundBuffer[y + k, x + b] = GetGbcColor(backgroundMemory, index);
                                }
                                else
                                {
                                    backgroundBuffer[y + k, x + b] = backgroundPalette[index];
                                }
                                lowByte >>= 1;
                                highByte >>= 1;
                            }
                        }
                    }
                }
            }
            else
            {
                for (var i = 0; i < 32; ++i)
                {
                    for (var j = 0; j < 32; ++j, ++tileMapAddress)
                    {
                        if (!backgroundTileInvalidated[i, j] && !invalidateAllBackgroundTilesRequest) continue;
                        backgroundTileInvalidated[i, j] = false;
                        int tileDataAddress = _memory.videoRam[tileMapAddress];
                        if (tileDataAddress > 127)
                        {
                            tileDataAddress -= 256;
                        }
                        tileDataAddress = 0x1000 + (tileDataAddress << 4);
                        var y = i << 3;
                        var x = j << 3;
                        for (var k = 0; k < 8; ++k)
                        {
                            var lowByte = _memory.videoRam[tileDataAddress++];
                            var highByte = _memory.videoRam[tileDataAddress++] << 1;
                            for (var b = 7; b >= 0; --b)
                            {
                                var index = (0x02 & highByte) | (0x01 & lowByte);
                                if (_colorMode)
                                {
                                    backgroundBuffer[y + k, x + b] = GetGbcColor(backgroundMemory, index);
                                }
                                else
                                {
                                    backgroundBuffer[y + k, x + b] = backgroundPalette[index];
                                }
                                lowByte >>= 1;
                                highByte >>= 1;
                            }
                        }
                    }
                }
            }

            invalidateAllBackgroundTilesRequest = false;
        }

Update Sprite

 public void UpdateSpriteTiles()
        {

            for (var i = 0; i < 256; ++i)
            {
                if (!spriteTileInvalidated[i] && !invalidateAllSpriteTilesRequest) continue;
                spriteTileInvalidated[i] = false;
                var address = i << 4;
                for (var y = 0; y < 8; ++y)
                {
                    var lowByte = _memory.videoRam[address++];
                    var highByte = _memory.videoRam[address++] << 1;
                    for (var x = 7; x >= 0; --x)
                    {
                        var paletteIndex = (0x02 & highByte) | (0x01 & lowByte);
                        lowByte >>= 1;
                        highByte >>= 1;
                        if (paletteIndex > 0)
                        {
                            if (_colorMode)
                            { 
                                spriteTile[i, y, x, 0] = GetGbcColor(spriteMemory,paletteIndex);
                                spriteTile[i, y, x, 1] = GetGbcColor(spriteMemory, paletteIndex);
                            }
                            else
                            {
                                spriteTile[i, y, x, 0] = objectPalette0[paletteIndex];
                                spriteTile[i, y, x, 1] = objectPalette1[paletteIndex];
                            }
                        }
                        else
                        {
                            spriteTile[i, y, x, 0] = 0;
                            spriteTile[i, y, x, 1] = 0;
                        }
                    }
                }
            }

            invalidateAllSpriteTilesRequest = false;
        }

Update Window

public void UpdateWindow()
        {

            var tileMapAddress = windowTileMapDisplaySelect ? 0x1C00 : 0x1800;

            if (backgroundAndWindowTileDataSelect)
            {
                for (var i = 0; i < 18; ++i)
                {
                    for (var j = 0; j < 21; ++j)
                    {
                        if (!backgroundTileInvalidated[i, j] && !invalidateAllBackgroundTilesRequest) continue;
                        var tileDataAddress = _memory.videoRam[tileMapAddress + ((i << 5) | j)] << 4;
                        var y = i << 3;
                        var x = j << 3;
                        for (var k = 0; k < 8; ++k)
                        {
                            var lowByte = _memory.videoRam[tileDataAddress++];
                            var highByte = _memory.videoRam[tileDataAddress++] << 1;
                            for (var b = 7; b >= 0; --b)
                            {
                                var index = (0x02 & highByte) | (0x01 & lowByte);
                                if (_colorMode)
                                {
                                    windowBuffer[y + k, x + b] = GetGbcColor(backgroundMemory, index);
                                }
                                else
                                {
                                    windowBuffer[y + k, x + b] = backgroundPalette[index];
                                }
                                lowByte >>= 1;
                                highByte >>= 1;
                            }
                        }
                    }
                }
            }
            else
            {
                for (var i = 0; i < 18; ++i)
                {
                    for (var j = 0; j < 21; ++j)
                    {
                        if (!backgroundTileInvalidated[i, j] && !invalidateAllBackgroundTilesRequest) continue;
                        int tileDataAddress = _memory.videoRam[tileMapAddress + ((i << 5) | j)];
                        if (tileDataAddress > 127)
                        {
                            tileDataAddress -= 256;
                        }
                        tileDataAddress = 0x1000 + (tileDataAddress << 4);
                        var y = i << 3;
                        var x = j << 3;
                        for (var k = 0; k < 8; ++k)
                        {
                            var lowByte = _memory.videoRam[tileDataAddress++];
                            var highByte = _memory.videoRam[tileDataAddress++] << 1;
                            for (var b = 7; b >= 0; --b)
                            {
                                var index = (0x02 & highByte) | (0x01 & lowByte);
                                if (_colorMode)
                                {
                                    windowBuffer[y + k, x + b] = GetGbcColor(backgroundMemory,index);
                                }
                                else
                                {
                                    windowBuffer[y + k, x + b] = backgroundPalette[index];
                                }
                                lowByte >>= 1;
                                highByte >>= 1;
                            }
                        }
                    }
                }
            }
        }

Update Graphiscs

private void UpdateModel(bool updateBitmap)
        {
            if (updateBitmap)
            {
                var backgroundBuffer = _ppu.backgroundBuffer;
                var windowBuffer = _ppu.windowBuffer;
                var oam = _memory.oam;

                for (int y = 0, pixelIndex = 0; y < Height; ++y)
                {
                    _ppu.ly = y;
                    _ppu.lcdcMode = LcdcModeType.SearchingOamRam;
                    if (_cpu.lcdcInterruptEnabled
                        && (_ppu.lcdcOamInterruptEnabled
                        || (_ppu.lcdcLycLyCoincidenceInterruptEnabled && _ppu.lyCompare == y)))
                    {
                        _cpu.lcdcInterruptRequested = true;
                    }
                    ExecuteProcessor(800);
                    _ppu.lcdcMode = LcdcModeType.TransferingData;
                    ExecuteProcessor(1720);

                    _ppu.UpdateWindow();
                    _ppu.UpdateBackground();
                    _ppu.UpdateSpriteTiles();

                    var backgroundDisplayed = _ppu.backgroundDisplayed;
                    var scrollX = _ppu.scrollX;
                    var scrollY = _ppu.scrollY;
                    var windowDisplayed = _ppu.windowDisplayed;
                    var windowX = _ppu.windowX - 7;
                    var windowY = _ppu.windowY;

                    for (var x = 0; x < Width; ++x, ++pixelIndex)
                    {
                        uint intensity = 0;

                        if (backgroundDisplayed)
                        {
                            intensity = backgroundBuffer [0xFF & (scrollY + y), 0xFF & (scrollX + x)];
                        }

                        if (windowDisplayed && y >= windowY && y < windowY + Height && x >= windowX && x < windowX + Width
                            && windowX >= -7 && windowX < Width && windowY >= 0 && windowY < Height)
                        {
                            intensity = windowBuffer [y - windowY, x - windowX];
                        }

                        _pixels [pixelIndex] = intensity;
                    }

                    if (_ppu.spritesDisplayed)
                    {
                        var spriteTile = _ppu.spriteTile;
                        if (_ppu.largeSprites)
                        {
                            for (var address = 0; address < Width; address += 4)
                            {
                                int spriteY = oam [address];
                                int spriteX = oam [address + 1];
                                if (spriteY == 0 || spriteX == 0 || spriteY >= 160 || spriteX >= 168)
                                {
                                    continue;
                                }
                                spriteY -= 16;
                                if (spriteY > y || spriteY + 15 < y)
                                {
                                    continue;
                                }
                                spriteX -= 8;

                                var spriteTileIndex0 = 0xFE & oam [address + 2];
                                var spriteTileIndex1 = spriteTileIndex0 | 0x01;
                                var spriteFlags = oam [address + 3];
                                var spritePriority = (0x80 & spriteFlags) == 0x80;
                                var spriteYFlipped = (0x40 & spriteFlags) == 0x40;
                                var spriteXFlipped = (0x20 & spriteFlags) == 0x20;
                                var spritePalette = (0x10 & spriteFlags) == 0x10 ? 1 : 0;

                                if (spriteYFlipped)
                                {
                                    var temp = spriteTileIndex0;
                                    spriteTileIndex0 = spriteTileIndex1;
                                    spriteTileIndex1 = temp;
                                }

                                var spriteRow = y - spriteY;
                                if (spriteRow >= 0 && spriteRow < 8)
                                {
                                    var screenAddress = (y << 7) + (y << 5) + spriteX;
                                    for (var x = 0; x < 8; ++x, ++screenAddress)
                                    {
                                        var screenX = spriteX + x;
                                        if (screenX >= 0 && screenX < Width)
                                        {
                                            var color = spriteTile [spriteTileIndex0,
          spriteYFlipped ? 7 - spriteRow : spriteRow,
          spriteXFlipped ? 7 - x : x, spritePalette];
                                            if (color <= 0) continue;
                                            if (spritePriority)
                                            {
                                                if (_pixels [screenAddress] == 0xFFFFFFFF)
                                                {
                                                    _pixels [screenAddress] = color;
                                                }
                                            } else
                                            {
                                                _pixels [screenAddress] = color;
                                            }
                                        }
                                    }
                                    continue;
                                }

                                spriteY += 8;

                                spriteRow = y - spriteY;
                                if (spriteRow < 0 || spriteRow >= 8) continue;
                                {
                                    var screenAddress = (y << 7) + (y << 5) + spriteX;
                                    for (var x = 0; x < 8; ++x, ++screenAddress)
                                    {
                                        var screenX = spriteX + x;
                                        if (screenX < 0 || screenX >= Width) continue;
                                        var color = spriteTile [spriteTileIndex1,
                                            spriteYFlipped ? 7 - spriteRow : spriteRow,
                                            spriteXFlipped ? 7 - x : x, spritePalette];
                                        if (color <= 0) continue;
                                        if (spritePriority)
                                        {
                                            if (_pixels [screenAddress] == 0xFFFFFFFF)
                                            {
                                                _pixels [screenAddress] = color;
                                            }
                                        } else
                                        {
                                            _pixels [screenAddress] = color;
                                        }
                                    }
                                }
                            }
                        } else
                        {
                            for (var address = 0; address < Width; address += 4)
                            {
                                int spriteY = oam [address];
                                int spriteX = oam [address + 1];
                                if (spriteY == 0 || spriteX == 0 || spriteY >= 160 || spriteX >= 168)
                                {
                                    continue;
                                }
                                spriteY -= 16;
                                if (spriteY > y || spriteY + 7 < y)
                                {
                                    continue;
                                }
                                spriteX -= 8;

                                var spriteTileIndex = oam [address + 2];
                                var spriteFlags = oam [address + 3];
                                var spritePriority = (0x80 & spriteFlags) == 0x80;
                                var spriteYFlipped = (0x40 & spriteFlags) == 0x40;
                                var spriteXFlipped = (0x20 & spriteFlags) == 0x20;
                                var spritePalette = (0x10 & spriteFlags) == 0x10 ? 1 : 0;

                                var spriteRow = y - spriteY;
                                var screenAddress = (y << 7) + (y << 5) + spriteX;
                                for (var x = 0; x < 8; ++x, ++screenAddress)
                                {
                                    var screenX = spriteX + x;
                                    if (screenX < 0 || screenX >= Width) continue;
                                    var color = spriteTile [spriteTileIndex,
                                        spriteYFlipped ? 7 - spriteRow : spriteRow,
                                        spriteXFlipped ? 7 - x : x, spritePalette];
                                    if (color <= 0) continue;
                                    if (spritePriority)
                                    {
                                        if (_pixels [screenAddress] == 0xFFFFFFFF)
                                        {
                                            _pixels [screenAddress] = color;
                                        }
                                    } else
                                    {
                                        _pixels [screenAddress] = color;
                                    }
                                }
                            }
                        }
                    }

                    _ppu.lcdcMode = LcdcModeType.HBlank;
                    if (_cpu.lcdcInterruptEnabled && _ppu.lcdcHBlankInterruptEnabled)
                    {
                        _cpu.lcdcInterruptRequested = true;
                    }
                    ExecuteProcessor(2040);
                    AddTicksPerScanLine();
                }
            } else
            {
                for (var y = 0; y < Height; ++y)
                {
                    _ppu.ly = y;
                    _ppu.lcdcMode = LcdcModeType.SearchingOamRam;
                    if (_cpu.lcdcInterruptEnabled
                        && (_ppu.lcdcOamInterruptEnabled
                        || (_ppu.lcdcLycLyCoincidenceInterruptEnabled && _ppu.lyCompare == y)))
                    {
                        _cpu.lcdcInterruptRequested = true;
                    }
                    ExecuteProcessor(800);
                    _ppu.lcdcMode = LcdcModeType.TransferingData;
                    ExecuteProcessor(1720);
                    _ppu.lcdcMode = LcdcModeType.HBlank;
                    if (_cpu.lcdcInterruptEnabled && _ppu.lcdcHBlankInterruptEnabled)
                    {
                        _cpu.lcdcInterruptRequested = true;
                    }
                    ExecuteProcessor(2040);
                    AddTicksPerScanLine();
                }
            }

            _ppu.lcdcMode = LcdcModeType.VBlank;
            if (_cpu.vBlankInterruptEnabled)
            {
                _cpu.vBlankInterruptRequested = true;
            }
            if (_cpu.lcdcInterruptEnabled && _ppu.lcdcVBlankInterruptEnabled)
            {
                _cpu.lcdcInterruptRequested = true;
            }
            for (var y = 144; y <= 153; ++y)
            {
                _ppu.ly = y;
                if (_cpu.lcdcInterruptEnabled && _ppu.lcdcLycLyCoincidenceInterruptEnabled
                    && _ppu.lyCompare == y)
                {
                    _cpu.lcdcInterruptRequested = true;
                }
                ExecuteProcessor(4560);
                AddTicksPerScanLine();
            }
            if (Audio != null)
                _memory.soundChip.OutputSound(Audio);
        }

Get GBCColor

        private static uint GetGbcColor(int[] paletteMemory, int paletteIndex)
        {
            var rawValue = (paletteMemory[paletteIndex * 2] | (paletteMemory[(paletteIndex*2)+1] & 0x7F)<<8);
            var r = ((rawValue & 0x1F)*255)/31;
            var g = (((rawValue >> 5) & 0x1F)*255)/31;
            var b = (((rawValue >> 10) & 0x1F)*255)/31;
            return (uint)(0xFF000000 | (r << 16) | (g << 8) | (b << 0));
        }

Just how it should work menu: https://photos.app.goo.gl/M2QTHVTzoPS1jiKS9
Just as they do now: https://photos.app.goo.gl/GW7SCm3ZXHyxRikGA
First room in gameboy color: https://photos.app.goo.gl/BNHDgmuPw5j1c9Lq6
In my emulator first room: https://photos.app.goo.gl/934tm2X1jEazkcPK7


I care about the simplest solution Thanks in advance

c#
graphics
colors
emulation
gameboy
asked on Stack Overflow May 3, 2019 by Dante • edited May 3, 2019 by Dante

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0