#include <math.h>
#include <stddef.h>
#include "ibm.h"
#include "device.h"
#include "mem.h"
#include "thread.h"
#include "video.h"
#include "vid_svga.h"
#include "vid_voodoo.h"
#include "vid_voodoo_common.h"
#include "vid_voodoo_dither.h"
#include "vid_voodoo_regs.h"
#include "vid_voodoo_render.h"
#include "vid_voodoo_texture.h"
#include "viewer.h"
#include "viewer_voodoo.h"

void voodoo_recalc_tex(voodoo_t *voodoo, int tmu) {
        int aspect = (voodoo->params.tLOD[tmu] >> 21) & 3;
        int width = 256, 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], heights[LOD_MAX + 3], 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*/

        //        pclog("TMU %i:    %08x\n", tmu, voodoo->params.textureMode[tmu]);
        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, d;
        int lod;
        int lod_min, lod_max;
        uint32_t addr = 0, 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++;
                        if (voodoo->viewer_active)
                                viewer_call(&viewer_voodoo, voodoo, voodoo_viewer_use_texture, (void *)(uintptr_t)tmu);
                        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;
        //        pclog("  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);
        lod_min = MIN(lod_min, 8);
        lod_max = MIN(lod_max, 8);
        for (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, y;
                int shift = 8 - params->tex_lod[tmu][lod];
                rgba_u *pal;

                // pclog("  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]);

                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 (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++;

        if (voodoo->viewer_active)
                viewer_call(&viewer_voodoo, voodoo, voodoo_viewer_use_texture, (void *)(uintptr_t)tmu);
}

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

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

                        for (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) {
                                                //                                pclog("  Evict texture %i %08x\n", c,
                                                //                                voodoo->texture_cache[tmu][c].base);

                                                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 *p) {
        int lod, s, t;
        voodoo_t *voodoo = (voodoo_t *)p;
        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 (addr >= 0x200000)
                //                        return;

                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]) {
                //                pclog("texture_present at %08x %i\n", addr, (addr & voodoo->texture_mask) >> TEX_DIRTY_SHIFT);
                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]) {
                //                pclog("texture_present at %08x %i\n", addr, (addr & voodoo->texture_mask) >> TEX_DIRTY_SHIFT);
                flush_texture_cache(voodoo, addr & voodoo->texture_mask, tmu ^ 1);
        }
        *(uint32_t *)(&voodoo->tex_mem[tmu][addr & voodoo->texture_mask]) = val;
}
