/*******************************************************************************
 * NewOswan
 * gpu.c:
 * Based on the original Oswan-unix
 * Copyright (c) 2014-2021 986-Studio. All rights reserved.
 *
 ******************************************************************************/

////////////////////////////////////////////////////////////////////////////////
// GPU
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
// 7.04.2002: Fixed sprites order
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <fcntl.h>
#include <time.h>
#include "log.h"
#include "rom.h"
#include "./nec/nec.h"
#include "io.h"
#include "gpu.h"
#include "ws.h"

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
extern uint8_t *internalRam;

enum VideoModes
{
    DISPLAY_MODE_GRAY = 0, DISPLAY_MODE_2BPP = 4, DISPLAY_MODE_P_4BPP = 7, DISPLAY_MODE_L_4BPP = 6,
};

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
#define RGB555(R, G, B) ((((int)(R))<<10)|(((int)(G))<<5)|((int)(B)))

uint8_t ws_gpu_operatingInColor = 0;
uint8_t ws_videoMode = DISPLAY_MODE_GRAY;
uint8_t ws_gpu_scanline = 0;
int16_t ws_palette[16 * 4];
int8_t ws_paletteColors[8];
int16_t wsc_palette[16 * 16];

// white
#define SHADE_COLOR_RED        1.00
#define SHADE_COLOR_GREEN    1.00
#define SHADE_COLOR_BLUE    1.00

int16_t ws_shades[16] = {
    RGB555(SHADE_COLOR_RED * 30, SHADE_COLOR_GREEN * 30, SHADE_COLOR_BLUE * 30),
    RGB555(SHADE_COLOR_RED * 28, SHADE_COLOR_GREEN * 28, SHADE_COLOR_BLUE * 28),
    RGB555(SHADE_COLOR_RED * 26, SHADE_COLOR_GREEN * 26, SHADE_COLOR_BLUE * 26),
    RGB555(SHADE_COLOR_RED * 24, SHADE_COLOR_GREEN * 24, SHADE_COLOR_BLUE * 24),
    RGB555(SHADE_COLOR_RED * 22, SHADE_COLOR_GREEN * 22, SHADE_COLOR_BLUE * 22),
    RGB555(SHADE_COLOR_RED * 20, SHADE_COLOR_GREEN * 20, SHADE_COLOR_BLUE * 20),
    RGB555(SHADE_COLOR_RED * 18, SHADE_COLOR_GREEN * 18, SHADE_COLOR_BLUE * 18),
    RGB555(SHADE_COLOR_RED * 16, SHADE_COLOR_GREEN * 16, SHADE_COLOR_BLUE * 16),
    RGB555(SHADE_COLOR_RED * 14, SHADE_COLOR_GREEN * 14, SHADE_COLOR_BLUE * 14),
    RGB555(SHADE_COLOR_RED * 12, SHADE_COLOR_GREEN * 12, SHADE_COLOR_BLUE * 12),
    RGB555(SHADE_COLOR_RED * 10, SHADE_COLOR_GREEN * 10, SHADE_COLOR_BLUE * 10),
    RGB555(SHADE_COLOR_RED * 8, SHADE_COLOR_GREEN * 8, SHADE_COLOR_BLUE * 8),
    RGB555(SHADE_COLOR_RED * 6, SHADE_COLOR_GREEN * 6, SHADE_COLOR_BLUE * 6),
    RGB555(SHADE_COLOR_RED * 4, SHADE_COLOR_GREEN * 4, SHADE_COLOR_BLUE * 4),
    RGB555(SHADE_COLOR_RED * 2, SHADE_COLOR_GREEN * 2, SHADE_COLOR_BLUE * 2),
    RGB555(SHADE_COLOR_RED * 0, SHADE_COLOR_GREEN * 0, SHADE_COLOR_BLUE * 0)
};

uint8_t *ws_tile_cache;
uint8_t *ws_hflipped_tile_cache;

uint8_t *wsc_tile_cache;
uint8_t *wsc_hflipped_tile_cache;

uint8_t *ws_modified_tile;
uint8_t *wsc_modified_tile;

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void ws_gpu_init(void)
{
    ws_tile_cache = (uint8_t *)malloc(1024 * 8 * 8);
    wsc_tile_cache = (uint8_t *)malloc(1024 * 8 * 8);

    ws_hflipped_tile_cache = (uint8_t *)malloc(1024 * 8 * 8);
    wsc_hflipped_tile_cache = (uint8_t *)malloc(1024 * 8 * 8);

    ws_modified_tile = (uint8_t *)malloc(1024);
    wsc_modified_tile = (uint8_t *)malloc(1024);

    memset(ws_tile_cache, 0x00, 1024 * 8 * 8);
    memset(wsc_tile_cache, 0x00, 1024 * 8 * 8);

    memset(ws_hflipped_tile_cache, 0x00, 1024 * 8 * 8);
    memset(wsc_hflipped_tile_cache, 0x00, 1024 * 8 * 8);

    memset(ws_modified_tile, 0x01, 1024);
    memset(wsc_modified_tile, 0x01, 1024);
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void ws_gpu_done(void)
{
    free(ws_tile_cache);
    free(wsc_tile_cache);
    free(ws_hflipped_tile_cache);
    free(wsc_hflipped_tile_cache);
    free(ws_modified_tile);
    free(wsc_modified_tile);
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void ws_gpu_changeVideoMode(uint8_t value)
{
    if (ws_videoMode != (value >> 5))
    {
        ws_videoMode = value >> 5;
        memset(ws_modified_tile, 0x01, 1024);
    }
    ws_gpu_operatingInColor = 0;
    if (value & 0xC0)
    {
        ws_gpu_operatingInColor = 1;
    }

    Log(TLOG_DEBUG, "GPU", "New video mode: %02X (Color: %c, %cbpp, F: %s",
        value,
        (value & 0x80)?'Y':'N',
        (value & 0x40)?'4':'2',
        (value & 0x20)?"Packed":"Planar");

}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void ws_gpu_reset(void)
{
    memset(ws_modified_tile, 0x01, 1024);
    ws_gpu_scanline = 0;
    ws_gpu_changeVideoMode(0x00);
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void ws_gpu_clearCache(void)
{
    memset(ws_modified_tile, 0x01, 1024);
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
uint8_t *ws_tileCache_getTileRow(uint32_t tileIndex, uint32_t line, uint32_t vFlip, uint32_t hFlip, uint32_t bank)
{
    if (ws_gpu_operatingInColor)
    {
        if (bank)
        {
            tileIndex += 512;
        }

        // need to update tile cache ?
        // 4 colors tiles
        if ((ws_videoMode == DISPLAY_MODE_2BPP) && (ws_modified_tile[tileIndex]))
        {
            uint8_t *tileInCachePtr = &wsc_tile_cache[tileIndex << 6];
            uint8_t *hflippedTileInCachePtr = &wsc_hflipped_tile_cache[tileIndex << 6];
            uint16_t *tileInRamPtr = (uint16_t *)&internalRam[0x2000 + (tileIndex << 4)];
            uint16_t tileLine;

            for (int line = 0 ; line < 8 ; line++)
            {
                tileLine = *tileInRamPtr++;

                tileInCachePtr[0] = ((tileLine & 0x80) >> 7) | ((tileLine & 0x8000) >> 14);
                hflippedTileInCachePtr[7] = tileInCachePtr[0];
                tileInCachePtr[1] = ((tileLine & 0x40) >> 6) | ((tileLine & 0x4000) >> 13);
                hflippedTileInCachePtr[6] = tileInCachePtr[1];
                tileInCachePtr[2] = ((tileLine & 0x20) >> 5) | ((tileLine & 0x2000) >> 12);
                hflippedTileInCachePtr[5] = tileInCachePtr[2];
                tileInCachePtr[3] = ((tileLine & 0x10) >> 4) | ((tileLine & 0x1000) >> 11);
                hflippedTileInCachePtr[4] = tileInCachePtr[3];
                tileInCachePtr[4] = ((tileLine & 0x08) >> 3) | ((tileLine & 0x0800) >> 10);
                hflippedTileInCachePtr[3] = tileInCachePtr[4];
                tileInCachePtr[5] = ((tileLine & 0x04) >> 2) | ((tileLine & 0x0400) >> 9);
                hflippedTileInCachePtr[2] = tileInCachePtr[5];
                tileInCachePtr[6] = ((tileLine & 0x02) >> 1) | ((tileLine & 0x0200) >> 8);
                hflippedTileInCachePtr[1] = tileInCachePtr[6];
                tileInCachePtr[7] = ((tileLine & 0x01) >> 0) | ((tileLine & 0x0100) >> 7);
                hflippedTileInCachePtr[0] = tileInCachePtr[7];

                tileInCachePtr += 8;
                hflippedTileInCachePtr += 8;
            }

            ws_modified_tile[tileIndex] = 0;
        }
        else if (wsc_modified_tile[tileIndex])
        {
            // 16 colors by tile layered mode
            if (ws_videoMode == DISPLAY_MODE_L_4BPP)
            {
                uint8_t *tileInCachePtr = &wsc_tile_cache[tileIndex << 6];
                uint8_t *hflippedTileInCachePtr = &wsc_hflipped_tile_cache[tileIndex << 6];
                uint32_t *tileInRamPtr = (uint32_t *)&internalRam[0x4000 + (tileIndex << 5)];
                uint32_t tileLine;

                for (int line = 0 ; line < 8 ; line++)
                {
                    tileLine = *tileInRamPtr++;

                    tileInCachePtr[0] = ((tileLine & 0x00000080) >> 7) | ((tileLine & 0x00008000) >> 14) |
                                        ((tileLine & 0x00800000) >> 21) | ((tileLine & 0x80000000) >> 28);
                    hflippedTileInCachePtr[7] = tileInCachePtr[0];

                    tileInCachePtr[1] = ((tileLine & 0x00000040) >> 6) | ((tileLine & 0x00004000) >> 13) |
                                        ((tileLine & 0x00400000) >> 20) | ((tileLine & 0x40000000) >> 27);
                    hflippedTileInCachePtr[6] = tileInCachePtr[1];

                    tileInCachePtr[2] = ((tileLine & 0x00000020) >> 5) | ((tileLine & 0x00002000) >> 12) |
                                        ((tileLine & 0x00200000) >> 19) | ((tileLine & 0x20000000) >> 26);
                    hflippedTileInCachePtr[5] = tileInCachePtr[2];

                    tileInCachePtr[3] = ((tileLine & 0x00000010) >> 4) | ((tileLine & 0x00001000) >> 11) |
                                        ((tileLine & 0x00100000) >> 18) | ((tileLine & 0x10000000) >> 25);
                    hflippedTileInCachePtr[4] = tileInCachePtr[3];

                    tileInCachePtr[4] = ((tileLine & 0x00000008) >> 3) | ((tileLine & 0x00000800) >> 10) |
                                        ((tileLine & 0x00080000) >> 17) | ((tileLine & 0x08000000) >> 24);
                    hflippedTileInCachePtr[3] = tileInCachePtr[4];

                    tileInCachePtr[5] = ((tileLine & 0x00000004) >> 2) | ((tileLine & 0x00000400) >> 9) |
                                        ((tileLine & 0x00040000) >> 16) | ((tileLine & 0x04000000) >> 23);
                    hflippedTileInCachePtr[2] = tileInCachePtr[5];

                    tileInCachePtr[6] = ((tileLine & 0x00000002) >> 1) | ((tileLine & 0x00000200) >> 8) |
                                        ((tileLine & 0x00020000) >> 15) | ((tileLine & 0x02000000) >> 22);
                    hflippedTileInCachePtr[1] = tileInCachePtr[6];

                    tileInCachePtr[7] = ((tileLine & 0x00000001) >> 0) | ((tileLine & 0x00000100) >> 7) |
                                        ((tileLine & 0x00010000) >> 14) | ((tileLine & 0x01000000) >> 21);
                    hflippedTileInCachePtr[0] = tileInCachePtr[7];

                    tileInCachePtr += 8;
                    hflippedTileInCachePtr += 8;
                }
            }
            else

                // 16 colors by tile packed mode
            if (ws_videoMode == DISPLAY_MODE_P_4BPP)
            {
                uint8_t *tileInCachePtr = &wsc_tile_cache[tileIndex << 6];
                uint8_t *hflippedTileInCachePtr = &wsc_hflipped_tile_cache[tileIndex << 6];
                uint32_t *tileInRamPtr = (uint32_t *)&internalRam[0x4000 + (tileIndex << 5)];
                uint32_t tileLine;

                for (int line = 0 ; line < 8 ; line++)
                {
                    tileLine = *tileInRamPtr++;

                    tileInCachePtr[0] = (tileLine >> 4) & 0x0f;
                    hflippedTileInCachePtr[7] = tileInCachePtr[0];
                    tileInCachePtr[1] = (tileLine >> 0) & 0x0f;
                    hflippedTileInCachePtr[6] = tileInCachePtr[1];
                    tileInCachePtr[2] = (tileLine >> 12) & 0x0f;
                    hflippedTileInCachePtr[5] = tileInCachePtr[2];
                    tileInCachePtr[3] = (tileLine >> 8) & 0x0f;
                    hflippedTileInCachePtr[4] = tileInCachePtr[3];
                    tileInCachePtr[4] = (tileLine >> 20) & 0x0f;
                    hflippedTileInCachePtr[3] = tileInCachePtr[4];
                    tileInCachePtr[5] = (tileLine >> 16) & 0x0f;
                    hflippedTileInCachePtr[2] = tileInCachePtr[5];
                    tileInCachePtr[6] = (tileLine >> 28) & 0x0f;
                    hflippedTileInCachePtr[1] = tileInCachePtr[6];
                    tileInCachePtr[7] = (tileLine >> 24) & 0x0f;
                    hflippedTileInCachePtr[0] = tileInCachePtr[7];

                    tileInCachePtr += 8;
                    hflippedTileInCachePtr += 8;

                }
            }
            else
            {
                // unknown mode
            }

            // tile cache updated
            wsc_modified_tile[tileIndex] = 0;
        }

        if (vFlip)
        {
            line = 7 - line;
        }

        if (hFlip)
        {
            return (&wsc_hflipped_tile_cache[(tileIndex << 6) + (line << 3)]);
        }
        else
        {
            return (&wsc_tile_cache[(tileIndex << 6) + (line << 3)]);
        }

    }
    else
    {
        // need to update tile cache ?
        if (ws_modified_tile[tileIndex])
        {
            uint8_t *tileInCachePtr = &ws_tile_cache[tileIndex << 6];
            uint8_t *hflippedTileInCachePtr = &ws_hflipped_tile_cache[(tileIndex << 6) + 7];
            uint32_t *tileInRamPtr = (uint32_t *)&internalRam[0x2000 + (tileIndex << 4)];
            uint32_t tileLine;

            for (int line = 0 ; line < 4 ; line++)
            {
                tileLine = *tileInRamPtr++;

                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x80) >> 7) | ((tileLine & 0x8000) >> 14);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x40) >> 6) | ((tileLine & 0x4000) >> 13);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x20) >> 5) | ((tileLine & 0x2000) >> 12);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x10) >> 4) | ((tileLine & 0x1000) >> 11);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x08) >> 3) | ((tileLine & 0x0800) >> 10);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x04) >> 2) | ((tileLine & 0x0400) >> 9);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x02) >> 1) | ((tileLine & 0x0200) >> 8);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x01) >> 0) | ((tileLine & 0x0100) >> 7);
                hflippedTileInCachePtr += 16;
                tileLine >>= 16;
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x80) >> 7) | ((tileLine & 0x8000) >> 14);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x40) >> 6) | ((tileLine & 0x4000) >> 13);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x20) >> 5) | ((tileLine & 0x2000) >> 12);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x10) >> 4) | ((tileLine & 0x1000) >> 11);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x08) >> 3) | ((tileLine & 0x0800) >> 10);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x04) >> 2) | ((tileLine & 0x0400) >> 9);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x02) >> 1) | ((tileLine & 0x0200) >> 8);
                *hflippedTileInCachePtr-- = *tileInCachePtr++ = ((tileLine & 0x01) >> 0) | ((tileLine & 0x0100) >> 7);
                hflippedTileInCachePtr += 16;
            }

            // tile cache updated
            ws_modified_tile[tileIndex] = 0;
        }

        if (vFlip)
        {
            line = 7 - line;
        }

        if (hFlip)
        {
            return (&ws_hflipped_tile_cache[(tileIndex << 6) + (line << 3)]);
        }
        else
        {
            return (&ws_tile_cache[(tileIndex << 6) + (line << 3)]);
        }
    }

    return (NULL);
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void ws_drawClippedSpriteLine(int16_t *framebuffer, uint16_t scanline, uint32_t x, uint32_t y, uint32_t tileIndex,
                              uint32_t paletteIndex, uint32_t vFlip, uint32_t hFlip, uint32_t clip_x0, uint32_t clip_y0,
                              uint32_t clip_x1, uint32_t clip_y1)
{

    if ((scanline < y) || (scanline > (y + 7)))
    {
        return;
    }

    if (((x + 7 < clip_x0) || (x >= clip_x1)))
    {
        return;
    }

    if (scanline > clip_y0 && scanline < clip_y1)
    {
        return;
    }

    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileIndex, (scanline - y) & 0x07, hFlip, vFlip, 0);
    uint16_t nbPixels = 8;

    if (x < clip_x0)
    {
        ws_tileRow += clip_x0 - x;
        nbPixels -= clip_x0 - x;
        x = clip_x0;
    }

    if (x + nbPixels > clip_x1)
    {
        nbPixels = (clip_x1 - x);
    }

    framebuffer += x;

    if (ws_gpu_operatingInColor)
    {
        int16_t *wsc_paletteAlias = &wsc_palette[paletteIndex << 4];

        while (nbPixels)
        {
            if (*ws_tileRow)
            {
                *framebuffer = wsc_paletteAlias[*ws_tileRow];
            }

            framebuffer++;
            ws_tileRow++;
            nbPixels--;
        }
    }
    else
    {
        int16_t *ws_paletteAlias = &ws_palette[paletteIndex << 2];

        if (paletteIndex & 0x04)
        {
            while (nbPixels)
            {
                if (*ws_tileRow)
                {
                    *framebuffer = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                }

                framebuffer++;
                ws_tileRow++;
                nbPixels--;
            }
        }
        else
        {
            while (nbPixels)
            {
                *framebuffer = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                framebuffer++;
                ws_tileRow++;
                nbPixels--;
            }
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void ws_gpu_renderScanline(int16_t *framebuffer)
{

    if (ws_gpu_scanline > 143)
    {
        return;
    }
    framebuffer += (224 * ws_gpu_scanline);

    // fill with background color
    int16_t backgroundColor;

    if (ws_gpu_operatingInColor)
    {
        backgroundColor = wsc_palette[ws_ioRam[0x01]];
    }
    else
    {
        backgroundColor = ws_shades[ws_paletteColors[ws_palette[((ws_ioRam[0x01] & 0xf0) >> 2) +
                                                                (ws_ioRam[0x01] & 0x03)]]];
    }

    for (int i = 0 ; i < 224 ; i++)
    {
        framebuffer[i] = backgroundColor;
    }

    // render background layer
    if (ws_ioRam[0x00] & 0x01)
    {
        int ws_bgScroll_x = ws_ioRam[0x10];
        int ws_bgScroll_y = ws_ioRam[0x11];

        // seek to the first tile
        ws_bgScroll_y = (ws_bgScroll_y + ws_gpu_scanline) & 0xff;

        // note: byte ordering assumptions!
        int ws_currentTile = (ws_bgScroll_x >> 3);
        uint16_t *ws_bgScrollRamBase = (uint16_t *)(internalRam + (((uint32_t)ws_ioRam[0x07] & 0x0f) << 11) +
                                                    ((ws_bgScroll_y & 0xfff8) << 3));

        int lineInTile = ws_bgScroll_y & 0x07;
        int columnInTile = ws_bgScroll_x & 0x07;

        int16_t *scanlinePtr = framebuffer;

        if (ws_gpu_operatingInColor)
        {
            // render the first clipped tile
            if (columnInTile)
            {
                uint16_t tileInfo = ws_bgScrollRamBase[ws_currentTile & 0x1f];
                ws_currentTile++;
                uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                              tileInfo & 0x4000, tileInfo & 0x2000);
                int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];
                ws_tileRow += columnInTile;

                for (int i = columnInTile ; i < 8 ; i++)
                {
                    if (*ws_tileRow)
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                }
            }

            // render the tiles between them
            int nbTiles = 28;

            if (columnInTile)
            {
                nbTiles = 27;
            }

            for (int i = 0 ; i < nbTiles ; i++)
            {
                uint16_t tileInfo = ws_bgScrollRamBase[ws_currentTile & 0x1f];
                ws_currentTile++;
                uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                              tileInfo & 0x4000, tileInfo & 0x2000);
                int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];

                if (*ws_tileRow)
                {
                    *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                }

                scanlinePtr++;
                ws_tileRow++;

                if (*ws_tileRow)
                {
                    *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                }

                scanlinePtr++;
                ws_tileRow++;

                if (*ws_tileRow)
                {
                    *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                }

                scanlinePtr++;
                ws_tileRow++;

                if (*ws_tileRow)
                {
                    *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                }

                scanlinePtr++;
                ws_tileRow++;

                if (*ws_tileRow)
                {
                    *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                }

                scanlinePtr++;
                ws_tileRow++;

                if (*ws_tileRow)
                {
                    *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                }

                scanlinePtr++;
                ws_tileRow++;

                if (*ws_tileRow)
                {
                    *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                }

                scanlinePtr++;
                ws_tileRow++;

                if (*ws_tileRow)
                {
                    *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                }

                scanlinePtr++;
                ws_tileRow++;
            }

            // render the last clipped tile
            if (columnInTile)
            {
                uint16_t tileInfo = ws_bgScrollRamBase[ws_currentTile & 0x1f];
                ws_currentTile++;
                uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                              tileInfo & 0x4000, tileInfo & 0x2000);
                int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];


                for (int i = 0 ; i < columnInTile ; i++)
                {
                    if (*ws_tileRow)
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                }
            }
        }
        else
        {
            // render the first clipped tile
            if (columnInTile)
            {
                uint16_t tileInfo = ws_bgScrollRamBase[ws_currentTile & 0x1f];
                ws_currentTile++;
                uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                              tileInfo & 0x4000, tileInfo & 0x2000);

                int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];
                ws_tileRow += columnInTile;

                if ((tileInfo >> 9) & 0x04)
                {
                    for (int i = columnInTile ; i < 8 ; i++)
                    {
                        if (*ws_tileRow)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }
                else
                {
                    for (int i = columnInTile ; i < 8 ; i++)
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }
            }

            int nbTiles = 28;

            if (columnInTile)
            {
                nbTiles = 27;
            }

            for (int i = 0 ; i < nbTiles ; i++)
            {
                uint16_t tileInfo = ws_bgScrollRamBase[ws_currentTile & 0x1f];
                ws_currentTile++;
                uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                              tileInfo & 0x4000, tileInfo & 0x2000);
                int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];

                if ((tileInfo >> 9) & 0x04)
                {
                    if (*ws_tileRow)
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                }
                else
                {
                    *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    scanlinePtr++;
                    ws_tileRow++;
                    *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    scanlinePtr++;
                    ws_tileRow++;
                    *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    scanlinePtr++;
                    ws_tileRow++;
                    *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    scanlinePtr++;
                    ws_tileRow++;
                    *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    scanlinePtr++;
                    ws_tileRow++;
                    *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    scanlinePtr++;
                    ws_tileRow++;
                    *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    scanlinePtr++;
                    ws_tileRow++;
                    *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    scanlinePtr++;
                    ws_tileRow++;
                }

            }

            // render the last clipped tile
            if (columnInTile)
            {
                uint16_t tileInfo = ws_bgScrollRamBase[ws_currentTile & 0x1f];
                ws_currentTile++;
                uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                              tileInfo & 0x4000, tileInfo & 0x2000);
                int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];


                if ((tileInfo >> 9) & 0x04)
                {
                    for (int i = 0 ; i < columnInTile ; i++)
                    {
                        if (*ws_tileRow)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }
                else
                {
                    for (int i = 0 ; i < columnInTile ; i++)
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }
            }
        }
    }

    // render sprites which are between both layers
    if (ws_ioRam[0x00] & 0x04)
    {
        int ws_sprWindow_x0 = ws_ioRam[0x0c];
        int ws_sprWindow_y0 = ws_ioRam[0x0d];
        int ws_sprWindow_x1 = ws_sprWindow_x0 + ws_ioRam[0x0e];
        int ws_sprWindow_y1 = ws_sprWindow_y0 + ws_ioRam[0x0f];
        uint32_t *ws_sprRamBase = (uint32_t *)(internalRam + (((uint32_t)ws_ioRam[0x04]) << 9));

        // seek to first sprite
        ws_sprRamBase += ws_ioRam[0x06] - 1;

        for (int i = ws_ioRam[0x06] ; i > ws_ioRam[0x05] ; i--)
        {
            uint32_t spr = *ws_sprRamBase--;

            if (!(spr & 0x2000))
            {
                // sprite window on ?
                if ((ws_ioRam[0x00] & 0x08) && (spr & 0x1000) && (ws_sprWindow_x0 != ws_sprWindow_x1))
                {
                    ws_drawClippedSpriteLine(framebuffer, ws_gpu_scanline, (spr & 0xff000000) >> 24,
                                             (spr & 0x00ff0000) >> 16, spr & 0x1ff, 8 + ((spr & 0xe00) >> 9),
                                             spr & 0x4000, spr & 0x8000, ws_sprWindow_x0, ws_sprWindow_y0,
                                             ws_sprWindow_x1, ws_sprWindow_y1);
                }
                else
                {
                    ws_drawClippedSpriteLine(framebuffer, ws_gpu_scanline, (spr & 0xff000000) >> 24,
                                             (spr & 0x00ff0000) >> 16, spr & 0x1ff, 8 + ((spr & 0xe00) >> 9),
                                             spr & 0x4000, spr & 0x8000,
                        //0,0,224,144);
                                             0, 0, 224, 0);
                }
            }
        }
    }

    // render foreground layer
    if (ws_ioRam[0x00] & 0x02)
    {
        int ws_fgWindow_x0 = ws_ioRam[0x08];
        int ws_fgWindow_x1 = ws_ioRam[0x0a];
        int ws_fgScroll_x = ws_ioRam[0x12];
        int ws_fgScroll_y = ws_ioRam[0x13];

        int windowMode = ws_ioRam[0x00] & 0x30;

        // seek to the first tile
        ws_fgScroll_y = (ws_fgScroll_y + ws_gpu_scanline) & 0xff;

        // note: byte ordering assumptions!
        int ws_currentTile = (ws_fgScroll_x >> 3);
        uint16_t *ws_fgScrollRamBase = (uint16_t *)(internalRam + (((uint32_t)ws_ioRam[0x07] & 0xf0) << 7) +
                                                    ((ws_fgScroll_y & 0xfff8) << 3));

        int lineInTile = ws_fgScroll_y & 0x07;
        int columnInTile = ws_fgScroll_x & 0x07;

        int16_t *scanlinePtr = framebuffer;


        // window disabled
        if (!windowMode)
        {
            if (ws_gpu_operatingInColor)
            {
                // render the first clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);

                    int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];

                    ws_tileRow += columnInTile;

                    for (int i = columnInTile ; i < 8 ; i++)
                    {
                        if (*ws_tileRow)
                        {
                            *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                        }

                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }

                // render the tiles between them
                int nbTiles = 28;

                if (columnInTile)
                {
                    nbTiles = 27;
                }

                for (int i = 0 ; i < nbTiles ; i++)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];


                    if (*ws_tileRow)
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;

                    if (*ws_tileRow)
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                }

                // render the last clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];


                    for (int i = 0 ; i < columnInTile ; i++)
                    {
                        if (*ws_tileRow)
                        {
                            *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                        }

                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }
            }
            else
            {
                // render the first clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);

                    int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];
                    ws_tileRow += columnInTile;

                    if ((tileInfo >> 9) & 0x04)
                    {
                        for (int i = columnInTile ; i < 8 ; i++)
                        {
                            if (*ws_tileRow)
                            {
                                *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                            }

                            scanlinePtr++;
                            ws_tileRow++;
                        }
                    }
                    else
                    {
                        for (int i = columnInTile ; i < 8 ; i++)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                            scanlinePtr++;
                            ws_tileRow++;
                        }
                    }
                }

                int nbTiles = 28;

                if (columnInTile)
                {
                    nbTiles = 27;
                }

                for (int i = 0 ; i < nbTiles ; i++)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];

                    if ((tileInfo >> 9) & 0x04)
                    {
                        if (*ws_tileRow)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        scanlinePtr++;
                        ws_tileRow++;

                        if (*ws_tileRow)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        scanlinePtr++;
                        ws_tileRow++;

                        if (*ws_tileRow)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        scanlinePtr++;
                        ws_tileRow++;

                        if (*ws_tileRow)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        scanlinePtr++;
                        ws_tileRow++;

                        if (*ws_tileRow)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        scanlinePtr++;
                        ws_tileRow++;

                        if (*ws_tileRow)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        scanlinePtr++;
                        ws_tileRow++;

                        if (*ws_tileRow)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        scanlinePtr++;
                        ws_tileRow++;

                        if (*ws_tileRow)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        scanlinePtr++;
                        ws_tileRow++;
                    }
                    else
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        scanlinePtr++;
                        ws_tileRow++;
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        scanlinePtr++;
                        ws_tileRow++;
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        scanlinePtr++;
                        ws_tileRow++;
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        scanlinePtr++;
                        ws_tileRow++;
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        scanlinePtr++;
                        ws_tileRow++;
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        scanlinePtr++;
                        ws_tileRow++;
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        scanlinePtr++;
                        ws_tileRow++;
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }

                // render the last clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];


                    if ((tileInfo >> 9) & 0x04)
                    {
                        for (int i = 0 ; i < columnInTile ; i++)
                        {
                            if (*ws_tileRow)
                            {
                                *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                            }

                            scanlinePtr++;
                            ws_tileRow++;
                        }
                    }
                    else
                    {
                        for (int i = 0 ; i < columnInTile ; i++)
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                            scanlinePtr++;
                            ws_tileRow++;
                        }
                    }
                }
            }
        }
        else

            // foreground layer displayed only inside the window
        if (windowMode == 0x20)
        {
            int column = 0;

            if (ws_gpu_operatingInColor)
            {
                // render the first clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];

                    ws_tileRow += columnInTile;

                    for (int i = columnInTile ; i < 8 ; i++)
                    {
                        if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                        {
                            *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                        }

                        column++;
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }

                // render the tiles between them
                int nbTiles = 28;

                if (columnInTile)
                {
                    nbTiles = 27;
                }

                for (int i = 0 ; i < nbTiles ; i++)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];


                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;
                }

                // render the last clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];


                    for (int i = 0 ; i < columnInTile ; i++)
                    {
                        if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                        {
                            *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                        }

                        column++;
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }
            }
            else
            {
                // render the first clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);

                    int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];
                    ws_tileRow += columnInTile;

                    for (int i = columnInTile ; i < 8 ; i++)
                    {
                        if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        column++;
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }

                int nbTiles = 28;

                if (columnInTile)
                {
                    nbTiles = 27;
                }

                for (int i = 0 ; i < nbTiles ; i++)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];


                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;
                }

                // render the last clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];


                    for (int i = 0 ; i < columnInTile ; i++)
                    {
                        if ((*ws_tileRow) && (column >= ws_fgWindow_x0) && (column <= ws_fgWindow_x1))
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        column++;
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }
            }
        }
        else

            // foreground layer displayed only outside the window
        if (windowMode == 0x30)
        {
            int column = 0;

            if (ws_gpu_operatingInColor)
            {
                // render the first clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];

                    ws_tileRow += columnInTile;

                    for (int i = columnInTile ; i < 8 ; i++)
                    {
                        if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                        {
                            *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                        }

                        column++;
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }

                // render the tiles between them
                int nbTiles = 28;

                if (columnInTile)
                {
                    nbTiles = 27;
                }

                for (int i = 0 ; i < nbTiles ; i++)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];


                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;
                }

                // render the last clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *wsc_paletteAlias = &wsc_palette[((tileInfo >> 9) & 0x0f) << 4];


                    for (int i = 0 ; i < columnInTile ; i++)
                    {
                        if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                        {
                            *scanlinePtr = wsc_paletteAlias[*ws_tileRow];
                        }

                        column++;
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }
            }
            else
            {
                // render the first clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);

                    int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];
                    ws_tileRow += columnInTile;

                    for (int i = columnInTile ; i < 8 ; i++)
                    {
                        if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        column++;
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }

                int nbTiles = 28;

                if (columnInTile)
                {
                    nbTiles = 27;
                }

                for (int i = 0 ; i < nbTiles ; i++)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;

                    if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                    {
                        *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                    }

                    scanlinePtr++;
                    ws_tileRow++;
                    column++;
                }

                // render the last clipped tile
                if (columnInTile)
                {
                    uint16_t tileInfo = ws_fgScrollRamBase[ws_currentTile & 0x1f];
                    ws_currentTile++;
                    uint8_t *ws_tileRow = ws_tileCache_getTileRow(tileInfo & 0x1ff, lineInTile, tileInfo & 0x8000,
                                                                  tileInfo & 0x4000, tileInfo & 0x2000);
                    int16_t *ws_paletteAlias = &ws_palette[((tileInfo >> 9) & 0x0f) << 2];


                    for (int i = 0 ; i < columnInTile ; i++)
                    {
                        if ((*ws_tileRow) && ((column < ws_fgWindow_x0) || (column > ws_fgWindow_x1)))
                        {
                            *scanlinePtr = ws_shades[ws_paletteColors[ws_paletteAlias[*ws_tileRow]]];
                        }

                        column++;
                        scanlinePtr++;
                        ws_tileRow++;
                    }
                }
            }
        }
        else
        {
            // unknown
        }
    }

    // render sprites
    if (ws_ioRam[0x00] & 0x04)
    {
        int ws_sprWindow_x0 = ws_ioRam[0x0c];
        int ws_sprWindow_y0 = ws_ioRam[0x0d];
        int ws_sprWindow_x1 = ws_ioRam[0x0e];
        int ws_sprWindow_y1 = ws_ioRam[0x0f];
        uint32_t *ws_sprRamBase = (uint32_t *)(internalRam + (((uint32_t)ws_ioRam[0x04]) << 9));

        // seek to first sprite
        ws_sprRamBase += ws_ioRam[0x06] - 1;

        for (int i = ws_ioRam[0x06] ; i > ws_ioRam[0x05] ; i--)
        {
            uint32_t spr = *ws_sprRamBase--;

            if (spr & 0x2000)
            {
                // sprite window on ?
                if ((ws_ioRam[0x00] & 0x08) && (spr & 0x1000) && (ws_sprWindow_x0 != ws_sprWindow_x1))
                {
                    ws_drawClippedSpriteLine(framebuffer, ws_gpu_scanline, (spr & 0xff000000) >> 24,
                                             (spr & 0x00ff0000) >> 16, spr & 0x1ff, 8 + ((spr & 0xe00) >> 9),
                                             spr & 0x4000, spr & 0x8000, ws_sprWindow_x0, ws_sprWindow_y0,
                                             ws_sprWindow_x1, ws_sprWindow_y1);
                }
                else
                {
                    ws_drawClippedSpriteLine(framebuffer, ws_gpu_scanline, (spr & 0xff000000) >> 24,
                                             (spr & 0x00ff0000) >> 16, spr & 0x1ff, 8 + ((spr & 0xe00) >> 9),
                                             spr & 0x4000, spr & 0x8000,
                        //						 0,0,224,144);
                                             0, 0, 224, 0);
                }
            }
        }
    }
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
void ws_gpu_write_byte(uint32_t offset, uint8_t value)
{
    // ws 4 color tiles
    if ((offset >= 0x2000) && (offset < 0x4000))
    {
        if (internalRam[offset] != value)
        {
            ws_modified_tile[(offset & 0x1fff) >> 4] = 1;
        }

        // update the ram
        internalRam[offset] = value;
    }

    if (ws_gpu_operatingInColor)
    {
        // wsc 16 color tiles bank 0
        if ((offset >= 0x4000) && (offset < 0x8000))
        {
            if (internalRam[offset] != value)
            {
                wsc_modified_tile[(offset & 0x3fff) >> 5] = 1;
            }
        }
        else

            // wsc 16 color tiles bank 1
        if ((offset >= 0x8000) && (offset < 0xC000))
        {
            if (internalRam[offset] != value)
            {
                wsc_modified_tile[512 + ((offset & 0x3fff) >> 5)] = 1;
            }
        }

        // update the ram
        internalRam[offset] = value;

        // palette ram
        if (offset >= 0xfe00)
        {
            // RGB444 format
            uint16_t color = (internalRam[(offset & 0xfffe) + 1]);
            color <<= 8;
            color |= (internalRam[(offset & 0xfffe)]);

            wsc_palette[(offset & 0x1ff) >> 1] = RGB555(((color >> 8) & 0x0f) << 1, ((color >> 4) & 0x0f) << 1,
                                                        (color & 0x0f) << 1);
        }
    }
    else if (offset < 0x4000)
    {
        internalRam[offset] = value;
    }
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
unsigned int ws_gpu_unknownPort;

int ws_gpu_port_write(uint32_t port, uint8_t value)
{
    ws_gpu_unknownPort = 0;

    switch (port)
    {
    case 0x60:
        if (ws_get_system() != WS_SYSTEM_MONO)
        {
            ws_gpu_changeVideoMode(value);
        }
        return 0;

    case 0x1C:
        ws_paletteColors[0] = value & 0xf;
        ws_paletteColors[1] = (value >> 4) & 0xf;
        return 0;

    case 0x1D:
        ws_paletteColors[2] = value & 0xf;
        ws_paletteColors[3] = (value >> 4) & 0xf;
        return 0;

    case 0x1E:
        ws_paletteColors[4] = value & 0xf;
        ws_paletteColors[5] = (value >> 4) & 0xf;
        return 0;

    case 0x1F:
        ws_paletteColors[6] = value & 0xf;
        ws_paletteColors[7] = (value >> 4) & 0xf;
        return 0;

    default:ws_gpu_unknownPort = 1;
    }

    if ((port >= 0x20) && (port <= 0x3f))
    {
        ws_gpu_unknownPort = 0;
        port -= 0x20;
        int paletteIndex = port >> 1;

        if (port & 0x01)
        {
            ws_palette[(paletteIndex << 2) + 2] = value & 0x7;
            ws_palette[(paletteIndex << 2) + 3] = (value >> 4) & 0x7;
        }
        else
        {
            ws_palette[(paletteIndex << 2) + 0] = value & 0x7;
            ws_palette[(paletteIndex << 2) + 1] = (value >> 4) & 0x7;
        }
    }

    return ws_gpu_unknownPort;
}

////////////////////////////////////////////////////////////////////////////////
//
////////////////////////////////////////////////////////////////////////////////
//
//
//
//
//
//
//
////////////////////////////////////////////////////////////////////////////////
uint8_t ws_gpu_port_read(uint8_t port)
{
    switch (port)
    {
    case 0xa0:
        ws_ioRam[0xA0] |= 0x80;
        if (ws_get_system() == WS_SYSTEM_MONO)
        {
            return ws_ioRam[0xa0] & (~0x2);
        }
        else
        {
            return ws_ioRam[0xa0] | 2;
        }
        break;

    case 0xaa:
        return vblank_count & 0xff;

    case 0xab:
        return (vblank_count >> 8) & 0xff;

    case 0xac:
        return (vblank_count >> 16) & 0xff;

    case 0xad:
        return (vblank_count >> 24) & 0xff;
    }

    return (ws_ioRam[port]);
}
