Пример #1
0
        public unsafe void RenderVideoFrame32(VideoFrameData frame, IntPtr buffer, int stride)
        {
            if (frame.VideoMemorySnapshot.SuperGameBoyScreenStatus != 1)
            {
                if (frame.VideoMemorySnapshot.SuperGameBoyScreenStatus == 0 && (frame.VideoMemorySnapshot.LCDC & 0x80) != 0)
                {
                    if (frame.IsRunningInColorMode)
                    {
                        FillPalettes32((ushort *)frame.VideoMemorySnapshot.PaletteMemory);
                        DrawColorFrame32(frame, (byte *)buffer, stride);
                    }
                    else
                    {
                        if (frame.GreyPaletteUpdated)
                        {
                            ApplyPaletteMemoryUpdates(frame);
                            FillPalettes32((ushort *)frame.VideoMemorySnapshot.PaletteMemory);
                            SyncGreyscalePalettes();
                        }
                        DrawFrame32(frame, (byte *)buffer, stride);
                    }
                }
                else
                {
                    uint clearColor = frame.VideoMemorySnapshot.SuperGameBoyScreenStatus == 2 ?
                                      0xFF000000 :
                                      frame.VideoMemorySnapshot.SuperGameBoyScreenStatus == 3 ?
                                      LookupTables.StandardColorLookupTable32[/*videoRenderer.ClearColor*/ 0x7FFF] :
                                      0xFFFFFFFF;

                    ClearBuffer32((byte *)buffer, stride, clearColor);
                }
            }
        }
Пример #2
0
 private unsafe void ApplyPaletteMemoryUpdates(VideoFrameData frame)
 {
     foreach (var paletteAccess in frame.PaletteAccessList)
     {
         frame.VideoMemorySnapshot.PaletteMemory[paletteAccess.Offset] = paletteAccess.Value;
     }
 }
Пример #3
0
 public unsafe void RenderVideoBorder32(VideoFrameData frame, IntPtr buffer, int stride)
 {
     DrawBorder32(frame, (byte *)buffer, stride);
 }
Пример #4
0
        /// <summary>Draws the current frame into a 32 BPP buffer.</summary>
        /// <param name="frame">The frame to render.</param>
        /// <param name="buffer">The destination buffer.</param>
        /// <param name="stride">The stride of a buffer line.</param>
        private unsafe void DrawFrame32(VideoFrameData frame, byte *buffer, int stride)
        {
            // WARNING: Very looooooooooong code :D
            // I have to keep track of a lot of variables for this one-pass rendering
            // Since on GBC the priorities between BG, WIN and OBJ can sometimes be weird, I don't think there is a better way of handling this.
            // The code may lack some optimizations tough, but i try my best to keep the variable count the lower possible (taking in account the fact that MS JIT is designed to handle no more than 64 variables...)
            // If you see some possible optimization, feel free to contribute.
            // The code might be very long but it is still very well structured, so with a bit of knowledge on (C)GB hardware you should understand it easily
            // In fact I think the function works pretty much like the real lcd controller on (C)GB... ;)
            byte * bufferLine = buffer;
            uint * bufferPixel;
            int    scx, scy, wx, wy;
            int    clk, pi, data1, data2;
            bool   bgDraw, winDraw, winDraw2, objDraw, signedIndex;
            byte   objDrawn;
            uint **bgPalettes, objPalettes;
            uint * tilePalette;
            byte * bgMap, winMap,
                 bgTile, winTile;
            int     bgLineOffset, winLineOffset;
            int     bgTileIndex, pixelIndex;
            ushort *bgTiles;
            int     i, j;
            int     objHeight, objCount;
            uint    objColor = 0;

            bgPalettes  = this._backgroundPalettes32;
            objPalettes = this._spritePalettes32;

            fixed(ObjectData *objectData = this._objectData)
            fixed(ushort *paletteIndexTable = LookupTables.PaletteLookupTable,
                  flippedPaletteIndexTable  = LookupTables.FlippedPaletteLookupTable)
            fixed(uint *backgroundPalette   = this._backgroundPalette, objectPalette1 = this._objectPalette1, objectPalette2 = this._objectPalette2)
            {
                tilePalette = bgPalettes[0];

                data1       = frame.VideoMemorySnapshot.LCDC;
                bgDraw      = (data1 & 0x01) != 0;
                bgMap       = frame.VideoMemorySnapshot.VideoMemory + ((data1 & 0x08) != 0 ? 0x1C00 : 0x1800);
                winDraw     = (data1 & 0x20) != 0;
                winMap      = frame.VideoMemorySnapshot.VideoMemory + ((data1 & 0x40) != 0 ? 0x1C00 : 0x1800);
                objDraw     = (data1 & 0x02) != 0;
                objHeight   = (data1 & 0x04) != 0 ? 16 : 8;
                signedIndex = (data1 & 0x10) == 0;
                bgTiles     = (ushort *)(signedIndex ? frame.VideoMemorySnapshot.VideoMemory + 0x1000 : frame.VideoMemorySnapshot.VideoMemory);

                scx = frame.VideoMemorySnapshot.SCX;
                scy = frame.VideoMemorySnapshot.SCY;
                wx  = frame.VideoMemorySnapshot.WX - 7;
                wy  = frame.VideoMemorySnapshot.WY;

                UpdatePalette(frame.VideoMemorySnapshot.BGP, tilePalette, backgroundPalette);
                UpdatePalette(frame.VideoMemorySnapshot.OBP0, objPalettes[0], objectPalette1);
                UpdatePalette(frame.VideoMemorySnapshot.OBP1, objPalettes[1], objectPalette2);

                // Initialize the clock, and the port access index.
                clk = 4;                 // Be off by 4 clock cycles (the minimum time required by a DMG CPU instruction) to compensate write cycle imprecision.
                pi  = 0;

                for (i = 0; i < 144; i++)                 // Loop on frame lines
                {
                    // Update ports before drawing the line
                    while (pi < frame.VideoPortAccessList.Count && frame.VideoPortAccessList[pi].Clock < clk)
                    {
                        data2 = frame.VideoPortAccessList[pi].Value;

                        switch (frame.VideoPortAccessList[pi].Port)
                        {
                        case Port.LCDC:
                            bgDraw      = (data2 & 0x01) != 0;
                            bgMap       = frame.VideoMemorySnapshot.VideoMemory + ((data2 & 0x08) != 0 ? 0x1C00 : 0x1800);
                            winDraw     = (data2 & 0x20) != 0;
                            winMap      = frame.VideoMemorySnapshot.VideoMemory + ((data2 & 0x40) != 0 ? 0x1C00 : 0x1800);
                            objDraw     = (data2 & 0x02) != 0;
                            objHeight   = (data2 & 0x04) != 0 ? 16 : 8;
                            signedIndex = (data2 & 0x10) == 0;
                            bgTiles     = (ushort *)(signedIndex ? frame.VideoMemorySnapshot.VideoMemory + 0x1000 : frame.VideoMemorySnapshot.VideoMemory);
                            break;

                        case Port.SCX: scx = data2; break;

                        case Port.SCY: scy = data2; break;

                        case Port.WX: wx = data2 - 7; break;

                        case Port.BGP:
                            UpdatePalette(data2, tilePalette, backgroundPalette);
                            break;

                        case Port.OBP0:
                            UpdatePalette(data2, objPalettes[0], objectPalette1);
                            break;

                        case Port.OBP1:
                            UpdatePalette(data2, objPalettes[1], objectPalette2);
                            break;
                        }

                        pi++;
                    }

                    // Find valid sprites for the line, limited to 10 like on real GB
                    for (j = 0, objCount = 0; j < 40 && objCount < 10; j++)                     // Loop on OAM data
                    {
                        bgTile = frame.VideoMemorySnapshot.ObjectAttributeMemory + (j << 2);    // Obtain a pointer to the object data

                        // First byte is vertical position and that's exactly what we want to compare :)
                        data1 = *bgTile - 16;
                        if (data1 <= i && data1 + objHeight > i)                         // Check that the sprite is drawn on the current line
                        {
                            // Initialize the object data according to what we want
                            data2 = bgTile[1];                             // Second byte is the horizontal position, we store it somewhere
                            objectData[objCount].Left  = data2 - 8;
                            objectData[objCount].Right = data2;
                            data2 = bgTile[3];                                           // Fourth byte contain flags that we'll examine
                            objectData[objCount].Palette  = (data2 & 0x10) != 0 ? 1 : 0; // Set the palette index according to the flags
                            objectData[objCount].Priority = (data2 & 0x80) == 0;         // Store the priority information
                            // Now we check the Y flip flag, as we'll use it to calculate the tile line offset
                            if ((data2 & 0x40) != 0)
                            {
                                data1 = (objHeight + data1 - i - 1) << 1;
                            }
                            else
                            {
                                data1 = (i - data1) << 1;
                            }
                            // Now that we have the line offset, we add to it the tile offset
                            if (objHeight == 16)                             // Depending on the sprite size we'll have to mask bit 0 of the tile index
                            {
                                data1 += (bgTile[2] & 0xFE) << 4;            // Third byte is the tile index
                            }
                            else
                            {
                                data1 += bgTile[2] << 4;                                 // A tile is 16 bytes wide
                            }
                            // No all that is left is to fetch the tile data :)
                            bgTile = frame.VideoMemorySnapshot.VideoMemory + data1;                             // Calculate the full tile line address for VRAM Bank 0
                            // Depending on the X flip flag, we will load the flipped pixel data or the regular one
                            if ((data2 & 0x20) != 0)
                            {
                                objectData[objCount].PixelData = flippedPaletteIndexTable[*(ushort *)bgTile];
                            }
                            else
                            {
                                objectData[objCount].PixelData = paletteIndexTable[*(ushort *)bgTile];
                            }
                            objCount++;                             // Increment the object counter
                        }
                    }

                    // Initialize the background and window with new parameters
                    bgTileIndex  = scx >> 3;
                    pixelIndex   = scx & 7;
                    data1        = (scy + i) >> 3;       // Background Line Index
                    bgLineOffset = (scy + i) & 7;
                    if (data1 >= 32)                     // Tile the background vertically
                    {
                        data1 -= 32;
                    }
                    bgTile        = bgMap + (data1 << 5) + bgTileIndex;
                    winTile       = winMap + (((i - wy) << 2) & ~0x1F);
                    winLineOffset = (i - wy) & 7;

                    winDraw2 = winDraw && i >= wy;

                    // Adjust the current pixel to the current line
                    bufferPixel = (uint *)bufferLine;

                    clk += GameBoyMemoryBus.Mode2Duration;                     // Increment the clock by the duration of mode 2. (This is an approximation…)

                    // Do the actual drawing
                    for (j = 0; j < 160; j++)          // Loop on line pixels
                    {
                        clk++;                         // The clock will be incremented by 1 for every pixel, 160 in total. Together with the hardcoded mode 2, this will put the clock to 240 in the end.

                        // Update ports before drawing the line
                        while (pi < frame.VideoPortAccessList.Count && frame.VideoPortAccessList[pi].Clock < clk)
                        {
                            data2 = frame.VideoPortAccessList[pi].Value;

                            switch (frame.VideoPortAccessList[pi].Port)
                            {
                            case Port.LCDC:
                                bgDraw      = (data2 & 0x01) != 0;
                                bgMap       = frame.VideoMemorySnapshot.VideoMemory + ((data2 & 0x08) != 0 ? 0x1C00 : 0x1800);
                                winDraw     = (data2 & 0x20) != 0;
                                winMap      = frame.VideoMemorySnapshot.VideoMemory + ((data2 & 0x40) != 0 ? 0x1C00 : 0x1800);
                                objDraw     = (data2 & 0x02) != 0;
                                objHeight   = (data2 & 0x04) != 0 ? 16 : 8;
                                signedIndex = (data2 & 0x10) == 0;
                                bgTiles     = (ushort *)(signedIndex ? frame.VideoMemorySnapshot.VideoMemory + 0x1000 : frame.VideoMemorySnapshot.VideoMemory);
                                break;

                            case Port.SCX: scx = data2; break;

                            case Port.SCY: scy = data2; break;

                            case Port.WX: wx = data2 - 7; break;

                            case Port.BGP:
                                UpdatePalette(data2, tilePalette, backgroundPalette);
                                break;

                            case Port.OBP0:
                                UpdatePalette(data2, objPalettes[0], objectPalette1);
                                break;

                            case Port.OBP1:
                                UpdatePalette(data2, objPalettes[1], objectPalette2);
                                break;
                            }

                            pi++;
                        }

                        objDrawn = 0;                         // Draw no object by default

                        if (objDraw && objCount > 0)
                        {
                            for (data2 = 0; data2 < objCount; data2++)
                            {
                                if (objectData[data2].Left <= j && objectData[data2].Right > j)
                                {
                                    objColor = (uint)(objectData[data2].PixelData >> ((j - objectData[data2].Left) << 1)) & 3;
                                    if ((objDrawn = (byte)(objColor != 0 ? objectData[data2].Priority ? 2 : 1 : 0)) != 0)
                                    {
                                        objColor = objPalettes[objectData[data2].Palette][objColor];
                                        break;
                                    }
                                }
                            }
                        }
                        if (winDraw2 && j >= wx)
                        {
                            if (pixelIndex >= 8 || j == 0 || j == wx)
                            {
                                data1 = winLineOffset + (signedIndex ? (sbyte)*winTile++ << 3 : *winTile++ << 3);

                                data1 = paletteIndexTable[bgTiles[data1]];

                                if (j == 0 && wx < 0)
                                {
                                    pixelIndex = -wx;
                                    data1    >>= pixelIndex << 1;
                                }
                                else
                                {
                                    pixelIndex = 0;
                                }
                            }

                            *bufferPixel++ = objDrawn != 0 && (objDrawn == 2 || (data1 & 0x3) == 0) ? objColor : tilePalette[data1 & 0x3];

                            data1 >>= 2;
                            pixelIndex++;
                        }
                        else if (bgDraw)
                        {
                            if (pixelIndex >= 8 || j == 0)
                            {
                                if (bgTileIndex++ >= 32)                                 // Tile the background horizontally
                                {
                                    bgTile     -= 32;
                                    bgTileIndex = 0;
                                }

                                data1 = bgLineOffset + (signedIndex ? (sbyte)*bgTile++ << 3 : *bgTile++ << 3);

                                data1 = paletteIndexTable[bgTiles[data1]];

                                if (j == 0 && pixelIndex > 0)
                                {
                                    data1 >>= pixelIndex << 1;
                                }
                                else
                                {
                                    pixelIndex = 0;
                                }
                            }

                            *bufferPixel++ = objDrawn != 0 && (objDrawn == 2 || (data1 & 0x3) == 0) ? objColor : tilePalette[data1 & 0x3];
                            data1 >>= 2;
                            pixelIndex++;
                        }
                        else
                        {
                            *bufferPixel++ = objDrawn != 0 ? objColor : LookupTables.GrayPalette[0];
                        }
                    }

                    clk += 216;

                    bufferLine += stride;
                }
            }
        }
Пример #5
0
        /// <summary>Draws the SGB border into a 32 BPP buffer.</summary>
        /// <param name="frame">The frame for which to draw the border.</param>
        /// <param name="buffer">Destination pixel buffer.</param>
        /// <param name="stride">Buffer line stride.</param>
        private unsafe void DrawBorder32(VideoFrameData frame, byte *buffer, int stride)
        {
            uint[] paletteData  = new uint[8 << 4];
            int    mapRowOffset = -32;

            // Fill only the 4 border palettes… Just ignore the others
            for (int i = 0x40; i < paletteData.Length; i++)
            {
                paletteData[i] = LookupTables.StandardColorLookupTable32[frame.SgbBorderMapData[0x400 - 0x40 + i]];
            }

            for (int i = 0; i < 224; i++)
            {
                uint *pixelPointer      = (uint *)buffer;
                int   tileBaseRowOffset = (i & 0x7) << 1;               // Tiles are stored in a weird planar way…
                int   mapTileOffset     = tileBaseRowOffset != 0 ? mapRowOffset : mapRowOffset += 32;

                for (int j = 32; j-- != 0; mapTileOffset++)
                {
                    ushort tileInformation = frame.SgbBorderMapData[mapTileOffset];
                    int    tileRowOffset   = ((tileInformation & 0xFF) << 5) + ((tileInformation & 0x8000) != 0 ? 0xE - tileBaseRowOffset : tileBaseRowOffset);
                    int    paletteOffset   = ((tileInformation >> 10) & 0x7) << 4;

                    byte tileValue0 = frame.SgbCharacterData[tileRowOffset];
                    byte tileValue1 = frame.SgbCharacterData[tileRowOffset + 1];
                    byte tileValue2 = frame.SgbCharacterData[tileRowOffset + 16];
                    byte tileValue3 = frame.SgbCharacterData[tileRowOffset + 17];

                    if ((tileInformation & 0x4000) != 0)
                    {
                        for (byte k = 0x01; k != 0; k <<= 1)
                        {
                            byte color = (tileValue0 & k) != 0 ? (byte)1 : (byte)0;
                            if ((tileValue1 & k) != 0)
                            {
                                color |= 2;
                            }
                            if ((tileValue2 & k) != 0)
                            {
                                color |= 4;
                            }
                            if ((tileValue3 & k) != 0)
                            {
                                color |= 8;
                            }
                            *pixelPointer++ = color != 0 ? paletteData[paletteOffset + color] : 0;
                        }
                    }
                    else
                    {
                        for (byte k = 0x80; k != 0; k >>= 1)
                        {
                            byte color = (tileValue0 & k) != 0 ? (byte)1 : (byte)0;
                            if ((tileValue1 & k) != 0)
                            {
                                color |= 2;
                            }
                            if ((tileValue2 & k) != 0)
                            {
                                color |= 4;
                            }
                            if ((tileValue3 & k) != 0)
                            {
                                color |= 8;
                            }
                            *pixelPointer++ = color != 0 ? paletteData[paletteOffset + color] : 0;
                        }
                    }
                }

                buffer += stride;
            }
        }