/*
 * 86Box    A hypervisor and IBM PC system emulator that specializes in
 *          running old operating systems and software designed for IBM
 *          PC systems and compatibles from 1981 through fairly recent
 *          system designs based on the PCI bus.
 *
 *          This file is part of the 86Box distribution.
 *
 *          3DFX Voodoo emulation.
 *
 * Authors: Sarah Walker, <https://pcem-emulator.co.uk/>
 *
 *          Copyright 2008-2020 Sarah Walker.
 */
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#include <stddef.h>
#include <wchar.h>
#include <math.h>
#define HAVE_STDARG_H
#include <86box/86box.h>
#include "cpu.h"
#include <86box/machine.h>
#include <86box/device.h>
#include <86box/mem.h>
#include <86box/timer.h>
#include <86box/device.h>
#include <86box/plat.h>
#include <86box/thread.h>
#include <86box/video.h>
#include <86box/vid_svga.h>
#include <86box/vid_voodoo_common.h>
#include <86box/vid_voodoo_dither.h>
#include <86box/vid_voodoo_regs.h>
#include <86box/vid_voodoo_render.h>
#include <86box/vid_voodoo_texture.h>

#ifdef ENABLE_VOODOO_TEXTURE_LOG
int voodoo_texture_do_log = ENABLE_VOODOO_TEXTURE_LOG;

static void
voodoo_texture_log(const char *fmt, ...)
{
    va_list ap;

    if (voodoo_texture_do_log) {
        va_start(ap, fmt);
        pclog_ex(fmt, ap);
        va_end(ap);
    }
}
#else
#    define voodoo_texture_log(fmt, ...)
#endif

void
voodoo_recalc_tex12(voodoo_t *voodoo, int tmu)
{
    int      aspect  = (voodoo->params.tLOD[tmu] >> 21) & 3;
    int      width   = 256;
    int      height  = 256;
    int      shift   = 8;
    uint32_t base    = voodoo->params.texBaseAddr[tmu];
    uint32_t offset  = 0;
    int      tex_lod = 0;

    if (voodoo->params.tLOD[tmu] & LOD_S_IS_WIDER)
        height >>= aspect;
    else {
        width >>= aspect;
        shift -= aspect;
    }

    if ((voodoo->params.tLOD[tmu] & LOD_SPLIT) && (voodoo->params.tLOD[tmu] & LOD_ODD)) {
        width >>= 1;
        height >>= 1;
        shift--;
        tex_lod++;
        if (voodoo->params.tLOD[tmu] & LOD_TMULTIBASEADDR)
            base = voodoo->params.texBaseAddr1[tmu];
    }

    for (uint8_t lod = 0; lod <= LOD_MAX + 1; lod++) {
        if (!width)
            width = 1;
        if (!height)
            height = 1;
        if (shift < 0)
            shift = 0;
        voodoo->params.tex_base[tmu][lod] = base + offset;
        if (voodoo->params.tformat[tmu] & 8)
            voodoo->params.tex_end[tmu][lod] = base + offset + (width * height * 2);
        else
            voodoo->params.tex_end[tmu][lod] = base + offset + (width * height);
        voodoo->params.tex_w_mask[tmu][lod]  = width - 1;
        voodoo->params.tex_w_nmask[tmu][lod] = ~(width - 1);
        voodoo->params.tex_h_mask[tmu][lod]  = height - 1;
        voodoo->params.tex_shift[tmu][lod]   = shift;
        voodoo->params.tex_lod[tmu][lod]     = tex_lod;

        if (!(voodoo->params.tLOD[tmu] & LOD_SPLIT) || ((lod & 1) && (voodoo->params.tLOD[tmu] & LOD_ODD)) || (!(lod & 1) && !(voodoo->params.tLOD[tmu] & LOD_ODD))) {
            if (!(voodoo->params.tLOD[tmu] & LOD_ODD) || lod != 0) {
                if (voodoo->params.tformat[tmu] & 8)
                    offset += width * height * 2;
                else
                    offset += width * height;

                if (voodoo->params.tLOD[tmu] & LOD_SPLIT) {
                    width >>= 2;
                    height >>= 2;
                    shift -= 2;
                    tex_lod += 2;
                } else {
                    width >>= 1;
                    height >>= 1;
                    shift--;
                    tex_lod++;
                }

                if (voodoo->params.tLOD[tmu] & LOD_TMULTIBASEADDR) {
                    switch (tex_lod) {
                        case 0:
                            base = voodoo->params.texBaseAddr[tmu];
                            break;
                        case 1:
                            base = voodoo->params.texBaseAddr1[tmu];
                            break;
                        case 2:
                            base = voodoo->params.texBaseAddr2[tmu];
                            break;
                        default:
                            base = voodoo->params.texBaseAddr38[tmu];
                            break;
                    }
                }
            }
        }
    }

    voodoo->params.tex_width[tmu] = width;
}

void
voodoo_recalc_tex3(voodoo_t *voodoo, int tmu)
{
    int      aspect = (voodoo->params.tLOD[tmu] >> 21) & 3;
    int      width = 256;
    int      height = 256;
    int      shift = 8;
    int      lod;
    uint32_t base    = voodoo->params.texBaseAddr[tmu];
    uint32_t offset  = 0;
    int      tex_lod = 0;
    uint32_t offsets[LOD_MAX + 3];
    int      widths[LOD_MAX + 3];
    int      heights[LOD_MAX + 3];
    int      shifts[LOD_MAX + 3];

    if (voodoo->params.tLOD[tmu] & LOD_S_IS_WIDER)
        height >>= aspect;
    else {
        width >>= aspect;
        shift -= aspect;
    }

    for (lod = 0; lod <= LOD_MAX + 2; lod++) {
        offsets[lod] = offset;
        widths[lod]  = width >> lod;
        heights[lod] = height >> lod;
        shifts[lod]  = shift - lod;

        if (!widths[lod])
            widths[lod] = 1;
        if (!heights[lod])
            heights[lod] = 1;
        if (shifts[lod] < 0)
            shifts[lod] = 0;

        if (!(voodoo->params.tLOD[tmu] & LOD_SPLIT) || ((lod & 1) && (voodoo->params.tLOD[tmu] & LOD_ODD)) || (!(lod & 1) && !(voodoo->params.tLOD[tmu] & LOD_ODD))) {
            if (voodoo->params.tformat[tmu] & 8)
                offset += (width >> lod) * (height >> lod) * 2;
            else
                offset += (width >> lod) * (height >> lod);
        }
    }

    if ((voodoo->params.textureMode[tmu] & TEXTUREMODE_TRILINEAR) && (voodoo->params.tLOD[tmu] & LOD_ODD))
        tex_lod++; /*Skip LOD 0*/

#if 0
    voodoo_texture_log("TMU %i:    %08x\n", tmu, voodoo->params.textureMode[tmu]);
#endif
    for (lod = 0; lod <= LOD_MAX + 1; lod++) {
        if (voodoo->params.tLOD[tmu] & LOD_TMULTIBASEADDR) {
            switch (tex_lod) {
                case 0:
                    base = voodoo->params.texBaseAddr[tmu];
                    break;
                case 1:
                    base = voodoo->params.texBaseAddr1[tmu];
                    break;
                case 2:
                    base = voodoo->params.texBaseAddr2[tmu];
                    break;
                default:
                    base = voodoo->params.texBaseAddr38[tmu];
                    break;
            }
        }

        voodoo->params.tex_base[tmu][lod] = base + offsets[tex_lod];
        if (voodoo->params.tformat[tmu] & 8)
            voodoo->params.tex_end[tmu][lod] = base + offsets[tex_lod] + (widths[tex_lod] * heights[tex_lod] * 2);
        else
            voodoo->params.tex_end[tmu][lod] = base + offsets[tex_lod] + (widths[tex_lod] * heights[tex_lod]);
        voodoo->params.tex_w_mask[tmu][lod]  = widths[tex_lod] - 1;
        voodoo->params.tex_w_nmask[tmu][lod] = ~(widths[tex_lod] - 1);
        voodoo->params.tex_h_mask[tmu][lod]  = heights[tex_lod] - 1;
        voodoo->params.tex_shift[tmu][lod]   = shifts[tex_lod];
        voodoo->params.tex_lod[tmu][lod]     = tex_lod;

        if (!(voodoo->params.textureMode[tmu] & TEXTUREMODE_TRILINEAR) || ((lod & 1) && (voodoo->params.tLOD[tmu] & LOD_ODD)) || (!(lod & 1) && !(voodoo->params.tLOD[tmu] & LOD_ODD))) {
            if (!(voodoo->params.tLOD[tmu] & LOD_ODD) || lod != 0) {
                if (voodoo->params.textureMode[tmu] & TEXTUREMODE_TRILINEAR)
                    tex_lod += 2;
                else
                    tex_lod++;
            }
        }
    }

    voodoo->params.tex_width[tmu] = width;
}

#define makergba(r, g, b, a) ((b) | ((g) << 8) | ((r) << 16) | ((a) << 24))

void
voodoo_use_texture(voodoo_t *voodoo, voodoo_params_t *params, int tmu)
{
    int      c;
    int      lod_min;
    int      lod_max;
    uint32_t addr = 0;
    uint32_t addr_end;
    uint32_t palette_checksum;

    lod_min = (params->tLOD[tmu] >> 2) & 15;
    lod_max = (params->tLOD[tmu] >> 8) & 15;

    if (params->tformat[tmu] == TEX_PAL8 || params->tformat[tmu] == TEX_APAL8 || params->tformat[tmu] == TEX_APAL88) {
        if (voodoo->palette_dirty[tmu]) {
            palette_checksum = 0;

            for (c = 0; c < 256; c++)
                palette_checksum ^= voodoo->palette[tmu][c].u;

            voodoo->palette_checksum[tmu] = palette_checksum;
            voodoo->palette_dirty[tmu]    = 0;
        } else
            palette_checksum = voodoo->palette_checksum[tmu];
    } else
        palette_checksum = 0;

    if ((voodoo->params.tLOD[tmu] & LOD_SPLIT) && (voodoo->params.tLOD[tmu] & LOD_ODD) && (voodoo->params.tLOD[tmu] & LOD_TMULTIBASEADDR))
        addr = params->texBaseAddr1[tmu];
    else
        addr = params->texBaseAddr[tmu];

    /*Try to find texture in cache*/
    for (c = 0; c < TEX_CACHE_MAX; c++) {
        if (voodoo->texture_cache[tmu][c].base == addr && voodoo->texture_cache[tmu][c].tLOD == (params->tLOD[tmu] & 0xf00fff) && voodoo->texture_cache[tmu][c].palette_checksum == palette_checksum) {
            params->tex_entry[tmu] = c;
            voodoo->texture_cache[tmu][c].refcount++;
            return;
        }
    }

    /*Texture not found, search for unused texture*/
    do {
        for (c = 0; c < TEX_CACHE_MAX; c++) {
            voodoo->texture_last_removed++;
            voodoo->texture_last_removed &= (TEX_CACHE_MAX - 1);
            if (voodoo->texture_cache[tmu][voodoo->texture_last_removed].refcount == voodoo->texture_cache[tmu][voodoo->texture_last_removed].refcount_r[0] && (voodoo->render_threads == 1 || voodoo->texture_cache[tmu][voodoo->texture_last_removed].refcount == voodoo->texture_cache[tmu][voodoo->texture_last_removed].refcount_r[1]))
                break;
        }
        if (c == TEX_CACHE_MAX)
            voodoo_wait_for_render_thread_idle(voodoo);
    } while (c == TEX_CACHE_MAX);
    if (c == TEX_CACHE_MAX)
        fatal("Texture cache full!\n");

    c = voodoo->texture_last_removed;

    if ((voodoo->params.tLOD[tmu] & LOD_SPLIT) && (voodoo->params.tLOD[tmu] & LOD_ODD) && (voodoo->params.tLOD[tmu] & LOD_TMULTIBASEADDR))
        voodoo->texture_cache[tmu][c].base = params->texBaseAddr1[tmu];
    else
        voodoo->texture_cache[tmu][c].base = params->texBaseAddr[tmu];
    voodoo->texture_cache[tmu][c].tLOD = params->tLOD[tmu] & 0xf00fff;

    lod_min = (params->tLOD[tmu] >> 2) & 15;
    lod_max = (params->tLOD[tmu] >> 8) & 15;
#if 0
    voodoo_texture_log("  add new texture to %i tformat=%i %08x LOD=%i-%i tmu=%i\n", c, voodoo->params.tformat[tmu], params->texBaseAddr[tmu], lod_min, lod_max, tmu);
#endif
    lod_min = MIN(lod_min, 8);
    lod_max = MIN(lod_max, 8);
    for (int lod = lod_min; lod <= lod_max; lod++) {
        uint32_t     *base     = &voodoo->texture_cache[tmu][c].data[texture_offset[lod]];
        uint32_t      tex_addr = params->tex_base[tmu][lod] & voodoo->texture_mask;
        int           x;
        int           y;
        int           shift = 8 - params->tex_lod[tmu][lod];
        const rgba_u *pal;

#if 0
        voodoo_texture_log("  LOD %i : %08x - %08x %i %i,%i\n", lod, params->tex_base[tmu][lod] & voodoo->texture_mask, addr, voodoo->params.tformat[tmu], voodoo->params.tex_w_mask[tmu][lod],voodoo->params.tex_h_mask[tmu][lod]);
#endif

        switch (params->tformat[tmu]) {
            case TEX_RGB332:
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint8_t dat = voodoo->tex_mem[tmu][(tex_addr + x) & voodoo->texture_mask];

                        base[x] = makergba(rgb332[dat].r, rgb332[dat].g, rgb332[dat].b, 0xff);
                    }
                    tex_addr += (1 << voodoo->params.tex_shift[tmu][lod]);
                    base += (1 << shift);
                }
                break;

            case TEX_Y4I2Q2:
                pal = voodoo->ncc_lookup[tmu][(voodoo->params.textureMode[tmu] & TEXTUREMODE_NCC_SEL) ? 1 : 0];
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint8_t dat = voodoo->tex_mem[tmu][(tex_addr + x) & voodoo->texture_mask];

                        base[x] = makergba(pal[dat].rgba.r, pal[dat].rgba.g, pal[dat].rgba.b, 0xff);
                    }
                    tex_addr += (1 << voodoo->params.tex_shift[tmu][lod]);
                    base += (1 << shift);
                }
                break;

            case TEX_A8:
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint8_t dat = voodoo->tex_mem[tmu][(tex_addr + x) & voodoo->texture_mask];

                        base[x] = makergba(dat, dat, dat, dat);
                    }
                    tex_addr += (1 << voodoo->params.tex_shift[tmu][lod]);
                    base += (1 << shift);
                }
                break;

            case TEX_I8:
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint8_t dat = voodoo->tex_mem[tmu][(tex_addr + x) & voodoo->texture_mask];

                        base[x] = makergba(dat, dat, dat, 0xff);
                    }
                    tex_addr += (1 << voodoo->params.tex_shift[tmu][lod]);
                    base += (1 << shift);
                }
                break;

            case TEX_AI8:
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint8_t dat = voodoo->tex_mem[tmu][(tex_addr + x) & voodoo->texture_mask];

                        base[x] = makergba((dat & 0x0f) | ((dat << 4) & 0xf0), (dat & 0x0f) | ((dat << 4) & 0xf0), (dat & 0x0f) | ((dat << 4) & 0xf0), (dat & 0xf0) | ((dat >> 4) & 0x0f));
                    }
                    tex_addr += (1 << voodoo->params.tex_shift[tmu][lod]);
                    base += (1 << shift);
                }
                break;

            case TEX_PAL8:
                pal = voodoo->palette[tmu];
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint8_t dat = voodoo->tex_mem[tmu][(tex_addr + x) & voodoo->texture_mask];

                        base[x] = makergba(pal[dat].rgba.r, pal[dat].rgba.g, pal[dat].rgba.b, 0xff);
                    }
                    tex_addr += (1 << voodoo->params.tex_shift[tmu][lod]);
                    base += (1 << shift);
                }
                break;

            case TEX_APAL8:
                pal = voodoo->palette[tmu];
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint8_t dat = voodoo->tex_mem[tmu][(tex_addr + x) & voodoo->texture_mask];

                        int r = ((pal[dat].rgba.r & 3) << 6) | ((pal[dat].rgba.g & 0xf0) >> 2) | (pal[dat].rgba.r & 3);
                        int g = ((pal[dat].rgba.g & 0xf) << 4) | ((pal[dat].rgba.b & 0xc0) >> 4) | ((pal[dat].rgba.g & 0xf) >> 2);
                        int b = ((pal[dat].rgba.b & 0x3f) << 2) | ((pal[dat].rgba.b & 0x30) >> 4);
                        int a = (pal[dat].rgba.r & 0xfc) | ((pal[dat].rgba.r & 0xc0) >> 6);

                        base[x] = makergba(r, g, b, a);
                    }
                    tex_addr += (1 << voodoo->params.tex_shift[tmu][lod]);
                    base += (1 << shift);
                }
                break;

            case TEX_ARGB8332:
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint16_t dat = *(uint16_t *) &voodoo->tex_mem[tmu][(tex_addr + x * 2) & voodoo->texture_mask];

                        base[x] = makergba(rgb332[dat & 0xff].r, rgb332[dat & 0xff].g, rgb332[dat & 0xff].b, dat >> 8);
                    }
                    tex_addr += (1 << (voodoo->params.tex_shift[tmu][lod] + 1));
                    base += (1 << shift);
                }
                break;

            case TEX_A8Y4I2Q2:
                pal = voodoo->ncc_lookup[tmu][(voodoo->params.textureMode[tmu] & TEXTUREMODE_NCC_SEL) ? 1 : 0];
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint16_t dat = *(uint16_t *) &voodoo->tex_mem[tmu][(tex_addr + x * 2) & voodoo->texture_mask];

                        base[x] = makergba(pal[dat & 0xff].rgba.r, pal[dat & 0xff].rgba.g, pal[dat & 0xff].rgba.b, dat >> 8);
                    }
                    tex_addr += (1 << (voodoo->params.tex_shift[tmu][lod] + 1));
                    base += (1 << shift);
                }
                break;

            case TEX_R5G6B5:
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint16_t dat = *(uint16_t *) &voodoo->tex_mem[tmu][(tex_addr + x * 2) & voodoo->texture_mask];

                        base[x] = makergba(rgb565[dat].r, rgb565[dat].g, rgb565[dat].b, 0xff);
                    }
                    tex_addr += (1 << (voodoo->params.tex_shift[tmu][lod] + 1));
                    base += (1 << shift);
                }
                break;

            case TEX_ARGB1555:
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint16_t dat = *(uint16_t *) &voodoo->tex_mem[tmu][(tex_addr + x * 2) & voodoo->texture_mask];

                        base[x] = makergba(argb1555[dat].r, argb1555[dat].g, argb1555[dat].b, argb1555[dat].a);
                    }
                    tex_addr += (1 << (voodoo->params.tex_shift[tmu][lod] + 1));
                    base += (1 << shift);
                }
                break;

            case TEX_ARGB4444:
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint16_t dat = *(uint16_t *) &voodoo->tex_mem[tmu][(tex_addr + x * 2) & voodoo->texture_mask];

                        base[x] = makergba(argb4444[dat].r, argb4444[dat].g, argb4444[dat].b, argb4444[dat].a);
                    }
                    tex_addr += (1 << (voodoo->params.tex_shift[tmu][lod] + 1));
                    base += (1 << shift);
                }
                break;

            case TEX_A8I8:
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint16_t dat = *(uint16_t *) &voodoo->tex_mem[tmu][(tex_addr + x * 2) & voodoo->texture_mask];

                        base[x] = makergba(dat & 0xff, dat & 0xff, dat & 0xff, dat >> 8);
                    }
                    tex_addr += (1 << (voodoo->params.tex_shift[tmu][lod] + 1));
                    base += (1 << shift);
                }
                break;

            case TEX_APAL88:
                pal = voodoo->palette[tmu];
                for (y = 0; y < voodoo->params.tex_h_mask[tmu][lod] + 1; y++) {
                    for (x = 0; x < voodoo->params.tex_w_mask[tmu][lod] + 1; x++) {
                        uint16_t dat = *(uint16_t *) &voodoo->tex_mem[tmu][(tex_addr + x * 2) & voodoo->texture_mask];

                        base[x] = makergba(pal[dat & 0xff].rgba.r, pal[dat & 0xff].rgba.g, pal[dat & 0xff].rgba.b, dat >> 8);
                    }
                    tex_addr += (1 << (voodoo->params.tex_shift[tmu][lod] + 1));
                    base += (1 << shift);
                }
                break;

            default:
                fatal("Unknown texture format %i\n", params->tformat[tmu]);
        }
    }

    voodoo->texture_cache[tmu][c].is16 = voodoo->params.tformat[tmu] & 8;

    if (params->tformat[tmu] == TEX_PAL8 || params->tformat[tmu] == TEX_APAL8 || params->tformat[tmu] == TEX_APAL88)
        voodoo->texture_cache[tmu][c].palette_checksum = palette_checksum;
    else
        voodoo->texture_cache[tmu][c].palette_checksum = 0;

    if (lod_min == 0) {
        voodoo->texture_cache[tmu][c].addr_start[0] = voodoo->params.tex_base[tmu][0];
        voodoo->texture_cache[tmu][c].addr_end[0]   = voodoo->params.tex_end[tmu][0];
    } else
        voodoo->texture_cache[tmu][c].addr_start[0] = voodoo->texture_cache[tmu][c].addr_end[0] = 0;

    if (lod_min <= 1 && lod_max >= 1) {
        voodoo->texture_cache[tmu][c].addr_start[1] = voodoo->params.tex_base[tmu][1];
        voodoo->texture_cache[tmu][c].addr_end[1]   = voodoo->params.tex_end[tmu][1];
    } else
        voodoo->texture_cache[tmu][c].addr_start[1] = voodoo->texture_cache[tmu][c].addr_end[1] = 0;

    if (lod_min <= 2 && lod_max >= 2) {
        voodoo->texture_cache[tmu][c].addr_start[2] = voodoo->params.tex_base[tmu][2];
        voodoo->texture_cache[tmu][c].addr_end[2]   = voodoo->params.tex_end[tmu][2];
    } else
        voodoo->texture_cache[tmu][c].addr_start[2] = voodoo->texture_cache[tmu][c].addr_end[2] = 0;

    if (lod_max >= 3) {
        voodoo->texture_cache[tmu][c].addr_start[3] = voodoo->params.tex_base[tmu][(lod_min > 3) ? lod_min : 3];
        voodoo->texture_cache[tmu][c].addr_end[3]   = voodoo->params.tex_end[tmu][(lod_max < 8) ? lod_max : 8];
    } else
        voodoo->texture_cache[tmu][c].addr_start[3] = voodoo->texture_cache[tmu][c].addr_end[3] = 0;

    for (uint8_t d = 0; d < 4; d++) {
        addr     = voodoo->texture_cache[tmu][c].addr_start[d];
        addr_end = voodoo->texture_cache[tmu][c].addr_end[d];

        if (addr_end != 0) {
            for (; addr <= addr_end; addr += (1 << TEX_DIRTY_SHIFT))
                voodoo->texture_present[tmu][(addr & voodoo->texture_mask) >> TEX_DIRTY_SHIFT] = 1;
        }
    }

    params->tex_entry[tmu] = c;
    voodoo->texture_cache[tmu][c].refcount++;
}

void
flush_texture_cache(voodoo_t *voodoo, uint32_t dirty_addr, int tmu)
{
    int wait_for_idle = 0;

    memset(voodoo->texture_present[tmu], 0, sizeof(voodoo->texture_present[0]));
#if 0
    voodoo_texture_log("Evict %08x %i\n", dirty_addr, sizeof(voodoo->texture_present));
#endif
    for (uint8_t c = 0; c < TEX_CACHE_MAX; c++) {
        if (voodoo->texture_cache[tmu][c].base != -1) {
            for (uint8_t d = 0; d < 4; d++) {
                int addr_start = voodoo->texture_cache[tmu][c].addr_start[d];
                int addr_end   = voodoo->texture_cache[tmu][c].addr_end[d];

                if (addr_end != 0) {
                    int addr_start_masked = addr_start & voodoo->texture_mask & ~0x3ff;
                    int addr_end_masked   = ((addr_end & voodoo->texture_mask) + 0x3ff) & ~0x3ff;

                    if (addr_end_masked < addr_start_masked)
                        addr_end_masked = voodoo->texture_mask + 1;
                    if (dirty_addr >= addr_start_masked && dirty_addr < addr_end_masked) {
#if 0
                        voodoo_texture_log("  Evict texture %i %08x\n", c, voodoo->texture_cache[tmu][c].base);
#endif

                        if (voodoo->texture_cache[tmu][c].refcount != voodoo->texture_cache[tmu][c].refcount_r[0] || (voodoo->render_threads == 2 && voodoo->texture_cache[tmu][c].refcount != voodoo->texture_cache[tmu][c].refcount_r[1]))
                            wait_for_idle = 1;

                        voodoo->texture_cache[tmu][c].base = -1;
                    } else {
                        for (; addr_start <= addr_end; addr_start += (1 << TEX_DIRTY_SHIFT))
                            voodoo->texture_present[tmu][(addr_start & voodoo->texture_mask) >> TEX_DIRTY_SHIFT] = 1;
                    }
                }
            }
        }
    }
    if (wait_for_idle)
        voodoo_wait_for_render_thread_idle(voodoo);
}

void
voodoo_tex_writel(uint32_t addr, uint32_t val, void *priv)
{
    int       lod;
    int       s;
    int       t;
    voodoo_t *voodoo = (voodoo_t *) priv;
    int       tmu;

    if (addr & 0x400000)
        return; /*TREX != 0*/

    tmu = (addr & 0x200000) ? 1 : 0;

    if (tmu && !voodoo->dual_tmus)
        return;

    if (voodoo->type < VOODOO_BANSHEE) {
        if (!(voodoo->params.tformat[tmu] & 8) && voodoo->type >= VOODOO_BANSHEE) {
            lod = (addr >> 16) & 0xf;
            t   = (addr >> 8) & 0xff;
        } else {
            lod = (addr >> 17) & 0xf;
            t   = (addr >> 9) & 0xff;
        }
        if (voodoo->params.tformat[tmu] & 8)
            s = (addr >> 1) & 0xfe;
        else {
            if ((voodoo->params.textureMode[tmu] & (1 << 31)) || voodoo->type >= VOODOO_BANSHEE)
                s = addr & 0xfc;
            else
                s = (addr >> 1) & 0xfc;
        }
        if (lod > LOD_MAX)
            return;

#if 0
        if (addr >= 0x200000)
            return;
#endif

        if (voodoo->params.tformat[tmu] & 8)
            addr = voodoo->params.tex_base[tmu][lod] + s * 2 + (t << voodoo->params.tex_shift[tmu][lod]) * 2;
        else
            addr = voodoo->params.tex_base[tmu][lod] + s + (t << voodoo->params.tex_shift[tmu][lod]);
    } else
        addr = (addr & 0x1ffffc) + voodoo->params.tex_base[tmu][0];

    if (voodoo->texture_present[tmu][(addr & voodoo->texture_mask) >> TEX_DIRTY_SHIFT]) {
#if 0
        voodoo_texture_log("texture_present at %08x %i\n", addr, (addr & voodoo->texture_mask) >> TEX_DIRTY_SHIFT);
#endif
        flush_texture_cache(voodoo, addr & voodoo->texture_mask, tmu);
    }
    if (voodoo->type == VOODOO_3 && voodoo->texture_present[tmu ^ 1][(addr & voodoo->texture_mask) >> TEX_DIRTY_SHIFT]) {
#if 0
        voodoo_texture_log("texture_present at %08x %i\n", addr, (addr & voodoo->texture_mask) >> TEX_DIRTY_SHIFT);
#endif
        flush_texture_cache(voodoo, addr & voodoo->texture_mask, tmu ^ 1);
    }
    *(uint32_t *) (&voodoo->tex_mem[tmu][addr & voodoo->texture_mask]) = val;
}
