/* * 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, * * Copyright 2008-2020 Sarah Walker. */ #include #include #include #include #include #include #include #include #include #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_display.h> #include <86box/vid_voodoo_regs.h> #include <86box/vid_voodoo_render.h> #ifdef ENABLE_VOODOODISP_LOG int voodoodisp_do_log = ENABLE_VOODOODISP_LOG; static void voodoodisp_log(const char *fmt, ...) { va_list ap; if (voodoodisp_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define voodoodisp_log(fmt, ...) #endif void voodoo_update_ncc(voodoo_t *voodoo, int tmu) { for (uint8_t tbl = 0; tbl < 2; tbl++) { for (uint16_t col = 0; col < 256; col++) { int y = (col >> 4); int i = (col >> 2) & 3; int q = col & 3; int i_r; int i_g; int i_b; int q_r; int q_g; int q_b; y = (voodoo->nccTable[tmu][tbl].y[y >> 2] >> ((y & 3) * 8)) & 0xff; i_r = (voodoo->nccTable[tmu][tbl].i[i] >> 18) & 0x1ff; if (i_r & 0x100) i_r |= 0xfffffe00; i_g = (voodoo->nccTable[tmu][tbl].i[i] >> 9) & 0x1ff; if (i_g & 0x100) i_g |= 0xfffffe00; i_b = voodoo->nccTable[tmu][tbl].i[i] & 0x1ff; if (i_b & 0x100) i_b |= 0xfffffe00; q_r = (voodoo->nccTable[tmu][tbl].q[q] >> 18) & 0x1ff; if (q_r & 0x100) q_r |= 0xfffffe00; q_g = (voodoo->nccTable[tmu][tbl].q[q] >> 9) & 0x1ff; if (q_g & 0x100) q_g |= 0xfffffe00; q_b = voodoo->nccTable[tmu][tbl].q[q] & 0x1ff; if (q_b & 0x100) q_b |= 0xfffffe00; voodoo->ncc_lookup[tmu][tbl][col].rgba.r = CLAMP(y + i_r + q_r); voodoo->ncc_lookup[tmu][tbl][col].rgba.g = CLAMP(y + i_g + q_g); voodoo->ncc_lookup[tmu][tbl][col].rgba.b = CLAMP(y + i_b + q_b); voodoo->ncc_lookup[tmu][tbl][col].rgba.a = 0xff; } } } void voodoo_pixelclock_update(voodoo_t *voodoo) { int m = (voodoo->dac_pll_regs[0] & 0x7f) + 2; int n1 = ((voodoo->dac_pll_regs[0] >> 8) & 0x1f) + 2; int n2 = ((voodoo->dac_pll_regs[0] >> 13) & 0x07); float t = (14318184.0 * ((float) m / (float) n1)) / (float) (1 << n2); double clock_const; int line_length; if ((voodoo->dac_data[6] & 0xf0) == 0x20 || (voodoo->dac_data[6] & 0xf0) == 0x60 || (voodoo->dac_data[6] & 0xf0) == 0x70) t /= 2.0f; line_length = (voodoo->hSync & 0xff) + ((voodoo->hSync >> 16) & 0x3ff); // voodoodisp_log("Pixel clock %f MHz hsync %08x line_length %d\n", t, voodoo->hSync, line_length); voodoo->pixel_clock = t; clock_const = cpuclock / t; voodoo->line_time = (uint64_t) ((double) line_length * clock_const * (double) (1ULL << 32)); } static void voodoo_calc_clutData(voodoo_t *voodoo) { int c; for (c = 0; c < 256; c++) { voodoo->clutData256[c].r = (voodoo->clutData[c >> 3].r * (8 - (c & 7)) + voodoo->clutData[(c >> 3) + 1].r * (c & 7)) >> 3; voodoo->clutData256[c].g = (voodoo->clutData[c >> 3].g * (8 - (c & 7)) + voodoo->clutData[(c >> 3) + 1].g * (c & 7)) >> 3; voodoo->clutData256[c].b = (voodoo->clutData[c >> 3].b * (8 - (c & 7)) + voodoo->clutData[(c >> 3) + 1].b * (c & 7)) >> 3; } for (c = 0; c < 65536; c++) { int r = (c >> 8) & 0xf8; int g = (c >> 3) & 0xfc; int b = (c << 3) & 0xf8; #if 0 r |= (r >> 5); g |= (g >> 6); b |= (b >> 5); #endif voodoo->video_16to32[c] = (voodoo->clutData256[r].r << 16) | (voodoo->clutData256[g].g << 8) | voodoo->clutData256[b].b; } } #define FILTDIV 256 static int FILTCAP; static int FILTCAPG; static int FILTCAPB = 0; /* color filter threshold values */ void voodoo_generate_filter_v1(voodoo_t *voodoo) { float difference; float diffg; float diffb; float thiscol; float thiscolg; float thiscolb; float lined; float fcr; float fcg; float fcb; fcr = FILTCAP * 5; fcg = FILTCAPG * 6; fcb = FILTCAPB * 5; for (uint16_t g = 0; g < FILTDIV; g++) // pixel 1 { for (uint16_t h = 0; h < FILTDIV; h++) // pixel 2 { difference = (float) (h - g); diffg = difference; diffb = difference; thiscol = thiscolg = thiscolb = g; if (difference > FILTCAP) difference = FILTCAP; if (difference < -FILTCAP) difference = -FILTCAP; if (diffg > FILTCAPG) diffg = FILTCAPG; if (diffg < -FILTCAPG) diffg = -FILTCAPG; if (diffb > FILTCAPB) diffb = FILTCAPB; if (diffb < -FILTCAPB) diffb = -FILTCAPB; // hack - to make it not bleed onto black // if (g == 0){ // difference = diffg = diffb = 0; //} if ((difference < fcr) || (-difference > -fcr)) thiscol = g + (difference / 2); if ((diffg < fcg) || (-diffg > -fcg)) thiscolg = g + (diffg / 2); /* need these divides so we can actually undither! */ if ((diffb < fcb) || (-diffb > -fcb)) thiscolb = g + (diffb / 2); if (thiscol < 0) thiscol = 0; if (thiscol > FILTDIV - 1) thiscol = FILTDIV - 1; if (thiscolg < 0) thiscolg = 0; if (thiscolg > FILTDIV - 1) thiscolg = FILTDIV - 1; if (thiscolb < 0) thiscolb = 0; if (thiscolb > FILTDIV - 1) thiscolb = FILTDIV - 1; voodoo->thefilter[g][h] = thiscol; voodoo->thefilterg[g][h] = thiscolg; voodoo->thefilterb[g][h] = thiscolb; } lined = g + 4; if (lined > 255) lined = 255; voodoo->purpleline[g][0] = lined; voodoo->purpleline[g][2] = lined; lined = g + 0; if (lined > 255) lined = 255; voodoo->purpleline[g][1] = lined; } } void voodoo_generate_filter_v2(voodoo_t *voodoo) { float difference; float thiscol; float thiscolg; float thiscolb; float clr; float clg; float clb = 0; float fcr; float fcg; float fcb = 0; // pre-clamping fcr = FILTCAP; fcg = FILTCAPG; fcb = FILTCAPB; if (fcr > 32) fcr = 32; if (fcg > 32) fcg = 32; if (fcb > 32) fcb = 32; for (uint16_t g = 0; g < 256; g++) // pixel 1 - our target pixel we want to bleed into { for (uint16_t h = 0; h < 256; h++) // pixel 2 - our main pixel { float avg; float avgdiff; difference = (float) (g - h); avg = (float) ((g + g + g + g + h) / 5); avgdiff = avg - (float) ((g + h + h + h + h) / 5); if (avgdiff < 0) avgdiff *= -1; if (difference < 0) difference *= -1; thiscol = thiscolg = thiscolb = g; // try lighten if (h > g) { clr = clg = clb = avgdiff; if (clr > fcr) clr = fcr; if (clg > fcg) clg = fcg; if (clb > fcb) clb = fcb; thiscol = g + clr; thiscolg = g + clg; thiscolb = g + clb; if (thiscol > g + FILTCAP) thiscol = g + FILTCAP; if (thiscolg > g + FILTCAPG) thiscolg = g + FILTCAPG; if (thiscolb > g + FILTCAPB) thiscolb = g + FILTCAPB; if (thiscol > g + avgdiff) thiscol = g + avgdiff; if (thiscolg > g + avgdiff) thiscolg = g + avgdiff; if (thiscolb > g + avgdiff) thiscolb = g + avgdiff; } if (difference > FILTCAP) thiscol = g; if (difference > FILTCAPG) thiscolg = g; if (difference > FILTCAPB) thiscolb = g; // clamp if (thiscol < 0) thiscol = 0; if (thiscolg < 0) thiscolg = 0; if (thiscolb < 0) thiscolb = 0; if (thiscol > 255) thiscol = 255; if (thiscolg > 255) thiscolg = 255; if (thiscolb > 255) thiscolb = 255; // add to the table voodoo->thefilter[g][h] = thiscol; voodoo->thefilterg[g][h] = thiscolg; voodoo->thefilterb[g][h] = thiscolb; // debug the ones that don't give us much of a difference // if (difference < FILTCAP) // voodoodisp_log("Voodoofilter: %ix%i - %f difference, %f average difference, R=%f, G=%f, B=%f\n", g, h, difference, avgdiff, thiscol, thiscolg, thiscolb); } } } void voodoo_threshold_check(voodoo_t *voodoo) { int r; int g; int b; if (!voodoo->scrfilterEnabled) return; /* considered disabled; don't check and generate */ /* Check for changes, to generate anew table */ if (voodoo->scrfilterThreshold != voodoo->scrfilterThresholdOld) { r = (voodoo->scrfilterThreshold >> 16) & 0xFF; g = (voodoo->scrfilterThreshold >> 8) & 0xFF; b = voodoo->scrfilterThreshold & 0xFF; FILTCAP = r; FILTCAPG = g; FILTCAPB = b; voodoodisp_log("Voodoo Filter Threshold Check: %06x - RED %i GREEN %i BLUE %i\n", voodoo->scrfilterThreshold, r, g, b); voodoo->scrfilterThresholdOld = voodoo->scrfilterThreshold; if (voodoo->type == VOODOO_2) voodoo_generate_filter_v2(voodoo); else voodoo_generate_filter_v1(voodoo); if (voodoo->type >= VOODOO_BANSHEE) voodoo_generate_vb_filters(voodoo, FILTCAP, FILTCAPG); } } static void voodoo_filterline_v1(voodoo_t *voodoo, uint8_t *fil, int column, uint16_t *src, int line) { // Scratchpad for avoiding feedback streaks uint8_t fil3[4096 * 3]; assert(voodoo->h_disp <= 4096); /* 16 to 32-bit */ for (int x = 0; x < column; x++) { fil[x * 3] = ((src[x] & 31) << 3); fil[x * 3 + 1] = (((src[x] >> 5) & 63) << 2); fil[x * 3 + 2] = (((src[x] >> 11) & 31) << 3); // Copy to our scratchpads fil3[x * 3 + 0] = fil[x * 3 + 0]; fil3[x * 3 + 1] = fil[x * 3 + 1]; fil3[x * 3 + 2] = fil[x * 3 + 2]; } /* lines */ if (line & 1) { for (int x = 0; x < column; x++) { fil[x * 3] = voodoo->purpleline[fil[x * 3]][0]; fil[x * 3 + 1] = voodoo->purpleline[fil[x * 3 + 1]][1]; fil[x * 3 + 2] = voodoo->purpleline[fil[x * 3 + 2]][2]; } } /* filtering time */ for (int x = 1; x < column; x++) { fil3[x * 3] = voodoo->thefilterb[fil[x * 3]][fil[(x - 1) * 3]]; fil3[x * 3 + 1] = voodoo->thefilterg[fil[x * 3 + 1]][fil[(x - 1) * 3 + 1]]; fil3[x * 3 + 2] = voodoo->thefilter[fil[x * 3 + 2]][fil[(x - 1) * 3 + 2]]; } for (int x = 1; x < column; x++) { fil[x * 3] = voodoo->thefilterb[fil3[x * 3]][fil3[(x - 1) * 3]]; fil[x * 3 + 1] = voodoo->thefilterg[fil3[x * 3 + 1]][fil3[(x - 1) * 3 + 1]]; fil[x * 3 + 2] = voodoo->thefilter[fil3[x * 3 + 2]][fil3[(x - 1) * 3 + 2]]; } for (int x = 1; x < column; x++) { fil3[x * 3] = voodoo->thefilterb[fil[x * 3]][fil[(x - 1) * 3]]; fil3[x * 3 + 1] = voodoo->thefilterg[fil[x * 3 + 1]][fil[(x - 1) * 3 + 1]]; fil3[x * 3 + 2] = voodoo->thefilter[fil[x * 3 + 2]][fil[(x - 1) * 3 + 2]]; } for (int x = 0; x < column - 1; x++) { fil[x * 3] = voodoo->thefilterb[fil3[x * 3]][fil3[(x + 1) * 3]]; fil[x * 3 + 1] = voodoo->thefilterg[fil3[x * 3 + 1]][fil3[(x + 1) * 3 + 1]]; fil[x * 3 + 2] = voodoo->thefilter[fil3[x * 3 + 2]][fil3[(x + 1) * 3 + 2]]; } } static void voodoo_filterline_v2(voodoo_t *voodoo, uint8_t *fil, int column, uint16_t *src, UNUSED(int line)) { int x; // Scratchpad for blending filter uint8_t fil3[4096 * 3]; assert(voodoo->h_disp <= 4096); /* 16 to 32-bit */ for (x = 0; x < column; x++) { // Blank scratchpads fil3[x * 3 + 0] = fil[x * 3 + 0] = ((src[x] & 31) << 3); fil3[x * 3 + 1] = fil[x * 3 + 1] = (((src[x] >> 5) & 63) << 2); fil3[x * 3 + 2] = fil[x * 3 + 2] = (((src[x] >> 11) & 31) << 3); } /* filtering time */ for (x = 1; x < column - 3; x++) { fil3[(x + 3) * 3] = voodoo->thefilterb[(src[x + 3] & 31) << 3][(src[x] & 31) << 3]; fil3[(x + 3) * 3 + 1] = voodoo->thefilterg[((src[x + 3] >> 5) & 63) << 2][((src[x] >> 5) & 63) << 2]; fil3[(x + 3) * 3 + 2] = voodoo->thefilter[((src[x + 3] >> 11) & 31) << 3][((src[x] >> 11) & 31) << 3]; fil[(x + 2) * 3] = voodoo->thefilterb[fil3[(x + 2) * 3]][(src[x] & 31) << 3]; fil[(x + 2) * 3 + 1] = voodoo->thefilterg[fil3[(x + 2) * 3 + 1]][((src[x] >> 5) & 63) << 2]; fil[(x + 2) * 3 + 2] = voodoo->thefilter[fil3[(x + 2) * 3 + 2]][((src[x] >> 11) & 31) << 3]; fil3[(x + 1) * 3] = voodoo->thefilterb[fil[(x + 1) * 3]][(src[x] & 31) << 3]; fil3[(x + 1) * 3 + 1] = voodoo->thefilterg[fil[(x + 1) * 3 + 1]][((src[x] >> 5) & 63) << 2]; fil3[(x + 1) * 3 + 2] = voodoo->thefilter[fil[(x + 1) * 3 + 2]][((src[x] >> 11) & 31) << 3]; fil[(x - 1) * 3] = voodoo->thefilterb[fil3[(x - 1) * 3]][(src[x] & 31) << 3]; fil[(x - 1) * 3 + 1] = voodoo->thefilterg[fil3[(x - 1) * 3 + 1]][((src[x] >> 5) & 63) << 2]; fil[(x - 1) * 3 + 2] = voodoo->thefilter[fil3[(x - 1) * 3 + 2]][((src[x] >> 11) & 31) << 3]; } // unroll for edge cases fil3[(column - 3) * 3] = voodoo->thefilterb[(src[column - 3] & 31) << 3][(src[column] & 31) << 3]; fil3[(column - 3) * 3 + 1] = voodoo->thefilterg[((src[column - 3] >> 5) & 63) << 2][((src[column] >> 5) & 63) << 2]; fil3[(column - 3) * 3 + 2] = voodoo->thefilter[((src[column - 3] >> 11) & 31) << 3][((src[column] >> 11) & 31) << 3]; fil3[(column - 2) * 3] = voodoo->thefilterb[(src[column - 2] & 31) << 3][(src[column] & 31) << 3]; fil3[(column - 2) * 3 + 1] = voodoo->thefilterg[((src[column - 2] >> 5) & 63) << 2][((src[column] >> 5) & 63) << 2]; fil3[(column - 2) * 3 + 2] = voodoo->thefilter[((src[column - 2] >> 11) & 31) << 3][((src[column] >> 11) & 31) << 3]; fil3[(column - 1) * 3] = voodoo->thefilterb[(src[column - 1] & 31) << 3][(src[column] & 31) << 3]; fil3[(column - 1) * 3 + 1] = voodoo->thefilterg[((src[column - 1] >> 5) & 63) << 2][((src[column] >> 5) & 63) << 2]; fil3[(column - 1) * 3 + 2] = voodoo->thefilter[((src[column - 1] >> 11) & 31) << 3][((src[column] >> 11) & 31) << 3]; fil[(column - 2) * 3] = voodoo->thefilterb[fil3[(column - 2) * 3]][(src[column] & 31) << 3]; fil[(column - 2) * 3 + 1] = voodoo->thefilterg[fil3[(column - 2) * 3 + 1]][((src[column] >> 5) & 63) << 2]; fil[(column - 2) * 3 + 2] = voodoo->thefilter[fil3[(column - 2) * 3 + 2]][((src[column] >> 11) & 31) << 3]; fil[(column - 1) * 3] = voodoo->thefilterb[fil3[(column - 1) * 3]][(src[column] & 31) << 3]; fil[(column - 1) * 3 + 1] = voodoo->thefilterg[fil3[(column - 1) * 3 + 1]][((src[column] >> 5) & 63) << 2]; fil[(column - 1) * 3 + 2] = voodoo->thefilter[fil3[(column - 1) * 3 + 2]][((src[column] >> 11) & 31) << 3]; fil3[(column - 1) * 3] = voodoo->thefilterb[fil[(column - 1) * 3]][(src[column] & 31) << 3]; fil3[(column - 1) * 3 + 1] = voodoo->thefilterg[fil[(column - 1) * 3 + 1]][((src[column] >> 5) & 63) << 2]; fil3[(column - 1) * 3 + 2] = voodoo->thefilter[fil[(column - 1) * 3 + 2]][((src[column] >> 11) & 31) << 3]; } void voodoo_callback(void *priv) { voodoo_t *voodoo = (voodoo_t *) priv; const monitor_t *monitor = &monitors[voodoo->monitor_index]; int v_y_add = (monitor->mon_overscan_y >> 1); int v_x_add = (monitor->mon_overscan_x >> 1); if (voodoo->fbiInit0 & FBIINIT0_VGA_PASS) { if (voodoo->line < voodoo->v_disp) { voodoo_t *draw_voodoo; int draw_line; if (SLI_ENABLED) { if (voodoo == voodoo->set->voodoos[1]) goto skip_draw; if (((voodoo->initEnable & INITENABLE_SLI_MASTER_SLAVE) ? 1 : 0) == (voodoo->line & 1)) draw_voodoo = voodoo; else draw_voodoo = voodoo->set->voodoos[1]; draw_line = voodoo->line >> 1; } else { if (!(voodoo->fbiInit0 & 1)) goto skip_draw; draw_voodoo = voodoo; draw_line = voodoo->line; } if (draw_voodoo->dirty_line[draw_line]) { uint32_t *p = &monitor->target_buffer->line[voodoo->line + v_y_add][v_x_add]; uint16_t *src = (uint16_t *) &draw_voodoo->fb_mem[draw_voodoo->front_offset + draw_line * draw_voodoo->row_width]; int x; draw_voodoo->dirty_line[draw_line] = 0; if (voodoo->line < voodoo->dirty_line_low) { voodoo->dirty_line_low = voodoo->line; video_wait_for_buffer_monitor(voodoo->monitor_index); } if (voodoo->line > voodoo->dirty_line_high) voodoo->dirty_line_high = voodoo->line; /* Draw left overscan. */ for (x = 0; x < v_x_add; x++) monitor->target_buffer->line[voodoo->line + v_y_add][x] = 0x00000000; if (voodoo->scrfilter && voodoo->scrfilterEnabled) { uint8_t fil[4096 * 3]; /* interleaved 24-bit RGB */ assert(voodoo->h_disp <= 4096); if (voodoo->type == VOODOO_2) voodoo_filterline_v2(voodoo, fil, voodoo->h_disp, src, voodoo->line); else voodoo_filterline_v1(voodoo, fil, voodoo->h_disp, src, voodoo->line); for (x = 0; x < voodoo->h_disp; x++) { p[x] = (voodoo->clutData256[fil[x * 3]].b << 0 | voodoo->clutData256[fil[x * 3 + 1]].g << 8 | voodoo->clutData256[fil[x * 3 + 2]].r << 16); } } else { for (x = 0; x < voodoo->h_disp; x++) { p[x] = draw_voodoo->video_16to32[src[x]]; } } /* Draw right overscan. */ for (x = 0; x < v_x_add; x++) monitor->target_buffer->line[voodoo->line + v_y_add][voodoo->h_disp + x + v_x_add] = 0x00000000; } } } skip_draw: if (voodoo->line == voodoo->v_disp) { #if 0 voodoodisp_log("retrace %i %i %08x %i\n", voodoo->retrace_count, voodoo->swap_interval, voodoo->swap_offset, voodoo->swap_pending); #endif voodoo->retrace_count++; if (SLI_ENABLED && (voodoo->fbiInit2 & FBIINIT2_SWAP_ALGORITHM_MASK) == FBIINIT2_SWAP_ALGORITHM_SLI_SYNC) { if (voodoo == voodoo->set->voodoos[0]) { voodoo_t *voodoo_1 = voodoo->set->voodoos[1]; thread_wait_mutex(voodoo->swap_mutex); /*Only swap if both Voodoos are waiting for buffer swap*/ if (voodoo->swap_pending && (voodoo->retrace_count > voodoo->swap_interval) && voodoo_1->swap_pending && (voodoo_1->retrace_count > voodoo_1->swap_interval)) { memset(voodoo->dirty_line, 1, 1024); voodoo->retrace_count = 0; voodoo->front_offset = voodoo->swap_offset; if (voodoo->swap_count > 0) voodoo->swap_count--; voodoo->swap_pending = 0; memset(voodoo_1->dirty_line, 1, 1024); voodoo_1->retrace_count = 0; voodoo_1->front_offset = voodoo_1->swap_offset; if (voodoo_1->swap_count > 0) voodoo_1->swap_count--; voodoo_1->swap_pending = 0; thread_release_mutex(voodoo->swap_mutex); thread_set_event(voodoo->wake_fifo_thread); thread_set_event(voodoo_1->wake_fifo_thread); voodoo->frame_count++; voodoo_1->frame_count++; } else thread_release_mutex(voodoo->swap_mutex); } } else { thread_wait_mutex(voodoo->swap_mutex); if (voodoo->swap_pending && (voodoo->retrace_count > voodoo->swap_interval)) { voodoo->front_offset = voodoo->swap_offset; if (voodoo->swap_count > 0) voodoo->swap_count--; voodoo->swap_pending = 0; thread_release_mutex(voodoo->swap_mutex); memset(voodoo->dirty_line, 1, 1024); voodoo->retrace_count = 0; thread_set_event(voodoo->wake_fifo_thread); voodoo->frame_count++; } else thread_release_mutex(voodoo->swap_mutex); } voodoo->v_retrace = 1; } voodoo->line++; if (voodoo->fbiInit0 & FBIINIT0_VGA_PASS) { if (voodoo->line == voodoo->v_disp) { int force_blit = 0; thread_wait_mutex(voodoo->force_blit_mutex); if (voodoo->force_blit_count) { force_blit = 1; if (--voodoo->force_blit_count < 0) voodoo->force_blit_count = 0; } thread_release_mutex(voodoo->force_blit_mutex); if (voodoo->dirty_line_high > voodoo->dirty_line_low || force_blit) svga_doblit(voodoo->h_disp, voodoo->v_disp - 1, voodoo->svga); if (voodoo->clutData_dirty) { voodoo->clutData_dirty = 0; voodoo_calc_clutData(voodoo); } voodoo->dirty_line_high = -1; voodoo->dirty_line_low = 2000; } } if (voodoo->line >= voodoo->v_total) { voodoo->line = 0; voodoo->v_retrace = 0; } if (voodoo->line_time) timer_advance_u64(&voodoo->timer, voodoo->line_time); else timer_advance_u64(&voodoo->timer, TIMER_USEC * 32); }