VOGONS


Reply 100 of 121, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie

Double post, please delete me.

Last edited by ElTentakel on 2026-11-08, 15:04. Edited 2 times in total.

Reply 101 of 121, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie

Unreal, UT99, Diablo II, NFS5 and Undying are pretty stable and fast now. So the next release will also contain a Glide3 dll. Also I fixed some issues with the second TMU and the texture palettes. But as I stated I want to improve overall compatibility further, so it will take a bit for the next release. Quake III is another story though, it is completely broken. But I don't see a point to fix that, since it isn't a Glide game and the visual experience is always worse, due to the 256x256 pixel texture limit. Maybe someone can convince me?

Last edited by ElTentakel on 2026-11-08, 14:59. Edited 1 time in total.

Reply 102 of 121, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
myne wrote on 2026-11-07, 14:41:
ElTentakel this might be of some interest to you. https://github.com/nand2mario/zSST linked in the post I linked a few pages ag […]
Show full quote

ElTentakel this might be of some interest to you.
https://github.com/nand2mario/zSST linked in the post I linked a few pages ago.

Well, I ran it through verilator.
https://verilator.org/
After more than a few fails, I got source with the following command:

..\verilator -Wno-WIDTHEXPAND -Wno-WIDTHtrunc -cc .\sst1_pkg.sv .\generated\sst1_regs_pkg.sv .\generated\sst1_tmu_tables_pkg.sv .\frontend\sst1_addr_decode.sv .\frontend\sst1_float_to_fixed.sv .\frontend\sst1_pci_fifo.sv .\frontend\sst1_regfile.sv .\frontend\sst1_frontend.sv .\tmu\sst1_texture_layout.sv .\tmu\sst1_tmu_front_staged.sv .\tmu\sst1_texture_cache_ram.sv .\tmu\sst1_texture_cache.sv .\tmu\sst1_texture_address.sv .\tmu\sst1_texel_decode.sv .\tmu\sst1_texture_combine.sv .\tmu\sst1_tmu.sv .\fbi\sst1_pixel_pipeline.sv .\fbi\sst1_fb_write_combine.sv .\fbi\sst1_fb_read_cache.sv .\fbi\sst1_fbi.sv .\video\sst1_video_control.sv .\sst1_perf_counters.sv .\sst1_core.sv .\sst1_device.sv

-Wno-WIDTHEXPAND -Wno-WIDTHtrunc ignore word size changes. -cc outputs to c++

I will have a look at it. I also have ordered a VGA->HDMI converter to be able to compare the visual outcome with one of my voodoo2.

Reply 103 of 121, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
Kahenraz wrote on 2026-11-07, 17:06:

Am I reading this right? It implements native multi-core support in pure DOS, as long as the system is real-mode?

Yes, sound insane, but one gets used to it 😁. The hardest part is to get the memory management working, so you can safely access the memory of other cores without corruption due to cache or paging. Especially in Ring 3 things get very tricky. In Windows a temporary Ring 0 Call gate is used inside the separate VXD driver. Maybe it is possible to adapt that temporary call gate to DOS. This would massively improve compatibility with SBEMU and would fix that MTRRs are not working in that scenario. I really want that for Redguard, since this is the most challenging DOS game I know performance wise.

Reply 104 of 121, by RayeR

User metadata
Rank Oldbie
Rank
Oldbie
ElTentakel wrote on 2026-11-08, 14:25:

How much RAM do you have? I can not get VDPMI+Glidulator running on my Hasswell machine with 32GB of RAM.

I have 8GB, did you used the latest version from last crazii sbemu thread? I don't use other XMM and VDPMI provides then cca 380MB of XMS. I used default vdpmi.cfg

Gigabyte GA-P67-DS3-B3, Core i7-2600K @4,5GHz, 8GB DDR3, 128GB SSD, GTX970(GF7900GT), SB Audigy + YMF724F + DreamBlaster combo + LPC2ISA

Reply 105 of 121, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
willow wrote on 2026-10-11, 21:03:
lands of lore 2 is a 3dfx dos game if you want test. […]
Show full quote

lands of lore 2 is a 3dfx dos game if you want test.

nglide and dgvoodoo 2 have some problems with this game and glide emulation.

Is it possible to use it with dosbox staging or dosbox-x ?

If you can add high resolution rendering support like dgvoodoo 2 and nglide, it would be amazing. i don't know if it is possible.

It's on discount on GOG now, so I bought it. Unfortunately its sound doesn't work with SBEMU, so it only runs with a real SB or clone. The AI has tested it in a VM, but I don't know if it really works and how well. You have to set the following before starting it (with the next release):

SET DOS4GVM=VirtualMemory:ON,VirtualSize:48M,SwapFileName:lolgswap.swp,SwapMin:20M,SwapInc:0

This is because the game limits the virtual memory of its DOS/4GW to 24 MB, so Glidulator doesn't have enough. I even reduced the amount of RAM Glidulator uses, but it didn't help. But by reducing the RAM further, I could use the second TMU in Pył. But it turns out that it does not benefit from it. So on paper compatibility is improved, but for now we don't know for what exactly 😁. Any further suggestions for testing?

Reply 106 of 121, by myne

User metadata
Rank l33t
Rank
l33t

Can you do a test branch that's 64bit and reserves worker memory at say... 4.5gb?
Or wherever the "new 64bit standard" is for pci/whatever.
Obviously keep the dos > worker io where it needs to be.

I built:
Convert old ASUS ASC boardviews to KICAD PCB!
Re: A comprehensive guide to install and play MechWarrior 2 on new versions on Windows.
Dos+Windows 3.11+tcp+vbe_svga auto-install iso template
Script to backup Win9x\ME drivers from a working install
Re: The thing no one asked for: KICAD 440bx reference schematic

Reply 107 of 121, by myne

User metadata
Rank l33t
Rank
l33t
ElTentakel wrote on 2026-11-08, 14:18:

Here are the requested hot paths with the relating runs:

Pixel pipeline

The pixel pipeline is the hottest path:

Which c file/function(s) is this?
render.c? function?

I built:
Convert old ASUS ASC boardviews to KICAD PCB!
Re: A comprehensive guide to install and play MechWarrior 2 on new versions on Windows.
Dos+Windows 3.11+tcp+vbe_svga auto-install iso template
Script to backup Win9x\ME drivers from a working install
Re: The thing no one asked for: KICAD 440bx reference schematic

Reply 108 of 121, by myne

User metadata
Rank l33t
Rank
l33t

Github's copilot says it's this:

void
voodoo_triangle(voodoo_t *voodoo, voodoo_params_t *params, int odd_even)
{
if (voodoo->render_threads > 1 && !SLI_ENABLED && !voodoo_triangle_has_lines(voodoo, params, odd_even)) {
/* nothing to draw here: only what voodoo_half_triangle() does at
* its end for the texture cache */
voodoo->texture_cache[0][params->tex_entry[0]].refcount_r[odd_even]++;
voodoo->texture_cache[1][params->tex_entry[1]].refcount_r[odd_even]++;
return;
}
voodoo_state_t state = { 0 };
int vertexAy_adjusted;
int vertexCy_adjusted;
int dx;
int dy;

int LOD;
int lodbias;

state.dx1 = state.dx2 = 0;

voodoo->tri_count++;

dx = 8 - (params->vertexAx & 0xf);
if ((params->vertexAx & 0xf) > 8)
dx += 16;
dy = 8 - (params->vertexAy & 0xf);
if ((params->vertexAy & 0xf) > 8)
dy += 16;

#if 0
voodoo_render_log("voodoo_triangle %i %i %i : vA %f, %f vB %f, %f vC %f, %f f %i,%i %08x %08x %08x,%08x tex=%i,%i fogMode=%08x\n",
odd_even, PARAMS_READ_IDX(voodoo, odd_even), PARAMS_READ_IDX(voodoo, odd_even) & PARAM_MASK, (float)params->vertexAx / 16.0, (float)params->vertexAy / 16.0,
(float)params->vertexBx / 16.0, (float)params->vertexBy / 16.0,
(float)params->vertexCx / 16.0, (float)params->vertexCy / 16.0,
(params->fbzColorPath & FBZCP_TEXTURE_ENABLED) ? params->tformat[0] : 0,
(params->fbzColorPath & FBZCP_TEXTURE_ENABLED) ? params->tformat[1] : 0, params->fbzColorPath, params->alphaMode, params->textureMode[0],params->textureMode[1], params->tex_entry[0],params->tex_entry[1], params->fogMode);
#endif

state.base_r = params->startR;
state.base_g = params->startG;
state.base_b = params->startB;
state.base_a = params->startA;
state.base_z = params->startZ;
state.tmu[0].base_s = params->tmu[0].startS;
state.tmu[0].base_t = params->tmu[0].startT;
state.tmu[0].base_w = params->tmu[0].startW;
state.tmu[1].base_s = params->tmu[1].startS;
state.tmu[1].base_t = params->tmu[1].startT;
state.tmu[1].base_w = params->tmu[1].startW;
state.base_w = params->startW;

if (params->fbzColorPath & FBZ_PARAM_ADJUST) {
state.base_r += (dx * params->dRdX + dy * params->dRdY) >> 4;
state.base_g += (dx * params->dGdX + dy * params->dGdY) >> 4;
state.base_b += (dx * params->dBdX + dy * params->dBdY) >> 4;
state.base_a += (dx * params->dAdX + dy * params->dAdY) >> 4;
state.base_z += (dx * params->dZdX + dy * params->dZdY) >> 4;
state.tmu[0].base_s += (dx * params->tmu[0].dSdX + dy * params->tmu[0].dSdY) >> 4;
state.tmu[0].base_t += (dx * params->tmu[0].dTdX + dy * params->tmu[0].dTdY) >> 4;
…Show last 81 lines
        state.tmu[0].base_w += (dx * params->tmu[0].dWdX + dy * params->tmu[0].dWdY) >> 4;
state.tmu[1].base_s += (dx * params->tmu[1].dSdX + dy * params->tmu[1].dSdY) >> 4;
state.tmu[1].base_t += (dx * params->tmu[1].dTdX + dy * params->tmu[1].dTdY) >> 4;
state.tmu[1].base_w += (dx * params->tmu[1].dWdX + dy * params->tmu[1].dWdY) >> 4;
state.base_w += (dx * params->dWdX + dy * params->dWdY) >> 4;
}

tris++;

state.vertexAy = params->vertexAy & ~0xffff0000;
if (state.vertexAy & 0x8000)
state.vertexAy |= 0xffff0000;
state.vertexBy = params->vertexBy & ~0xffff0000;
if (state.vertexBy & 0x8000)
state.vertexBy |= 0xffff0000;
state.vertexCy = params->vertexCy & ~0xffff0000;
if (state.vertexCy & 0x8000)
state.vertexCy |= 0xffff0000;

state.vertexAx = params->vertexAx & ~0xffff0000;
if (state.vertexAx & 0x8000)
state.vertexAx |= 0xffff0000;
state.vertexBx = params->vertexBx & ~0xffff0000;
if (state.vertexBx & 0x8000)
state.vertexBx |= 0xffff0000;
state.vertexCx = params->vertexCx & ~0xffff0000;
if (state.vertexCx & 0x8000)
state.vertexCx |= 0xffff0000;

vertexAy_adjusted = (state.vertexAy + 7) >> 4;
vertexCy_adjusted = (state.vertexCy + 7) >> 4;

if (state.vertexBy - state.vertexAy)
state.dxAB = (int) ((((int64_t) state.vertexBx << 12) - ((int64_t) state.vertexAx << 12)) << 4) / (state.vertexBy - state.vertexAy);
else
state.dxAB = 0;
if (state.vertexCy - state.vertexAy)
state.dxAC = (int) ((((int64_t) state.vertexCx << 12) - ((int64_t) state.vertexAx << 12)) << 4) / (state.vertexCy - state.vertexAy);
else
state.dxAC = 0;
if (state.vertexCy - state.vertexBy)
state.dxBC = (int) ((((int64_t) state.vertexCx << 12) - ((int64_t) state.vertexBx << 12)) << 4) / (state.vertexCy - state.vertexBy);
else
state.dxBC = 0;

state.lod_min[0] = (params->tLOD[0] & 0x3f) << 6;
state.lod_max[0] = ((params->tLOD[0] >> 6) & 0x3f) << 6;
if (state.lod_max[0] > 0x800)
state.lod_max[0] = 0x800;
state.lod_min[1] = (params->tLOD[1] & 0x3f) << 6;
state.lod_max[1] = ((params->tLOD[1] >> 6) & 0x3f) << 6;
if (state.lod_max[1] > 0x800)
state.lod_max[1] = 0x800;

state.xstart = state.xend = state.vertexAx << 8;
state.xdir = params->sign ? -1 : 1;

state.y = (state.vertexAy + 8) >> 4;
state.ydir = 1;

LOD = voodoo_triangle_lod_base(params, 0);
lodbias = voodoo_lod_bias(params, 0);

state.tmu[0].lod = LOD + lodbias;

LOD = voodoo_triangle_lod_base(params, 1);
lodbias = voodoo_lod_bias(params, 1);

state.tmu[1].lod = LOD + lodbias;

/*GLIDULATOR: per-part LOD only where it can matter (voodoo_lod_always_min())*/
if (params->fbzColorPath & FBZCP_TEXTURE_ENABLED) {
for (int tmu = 0; tmu < (voodoo->dual_tmus ? 2 : 1); tmu++) {
if ((params->textureMode[tmu] & TEXTUREMODE_TPERSP_ST) && state.lod_min[tmu] != state.lod_max[tmu])
state.tmu[tmu].lod_at_min = voodoo_lod_always_min(params, &state, tmu, voodoo_lod_bias(params, tmu), &state.tmu[tmu].lod);
}
}
state.stipple = params->stipple;

voodoo_half_triangle(voodoo, params, &state, vertexAy_adjusted, vertexCy_adjusted, odd_even);
}

If so, I think there are definitely ways to reduce the branches here.

Ideally you want all of your decisions outside of the function.

    if (voodoo->render_threads > 1 && !SLI_ENABLED && !voodoo_triangle_has_lines(voodoo, params, odd_even)) {
/* nothing to draw here: only what voodoo_half_triangle() does at
* its end for the texture cache */
voodoo->texture_cache[0][params->tex_entry[0]].refcount_r[odd_even]++;
voodoo->texture_cache[1][params->tex_entry[1]].refcount_r[odd_even]++;
return;
}

You should already know if you have sli. This should be decided on start or periodically as your worker manager checks they're alive.
The draw function shouldn't need to care. Ideally it's a dumb math box that doesn't make decisions.

    dx = 8 - (params->vertexAx & 0xf);
if ((params->vertexAx & 0xf) > 8)
dx += 16;
dy = 8 - (params->vertexAy & 0xf);
if ((params->vertexAy & 0xf) > 8)
dy += 16;

Not quite sure what these are outside of x/y coords, but can the decision be made before the draw?

#if 0
voodoo_render_log("voodoo_triangle %i %i %i : vA %f, %f vB %f, %f vC %f, %f f %i,%i %08x %08x %08x,%08x tex=%i,%i fogMode=%08x\n",
odd_even, PARAMS_READ_IDX(voodoo, odd_even), PARAMS_READ_IDX(voodoo, odd_even) & PARAM_MASK, (float)params->vertexAx / 16.0, (float)params->vertexAy / 16.0,
(float)params->vertexBx / 16.0, (float)params->vertexBy / 16.0,
(float)params->vertexCx / 16.0, (float)params->vertexCy / 16.0,
(params->fbzColorPath & FBZCP_TEXTURE_ENABLED) ? params->tformat[0] : 0,
(params->fbzColorPath & FBZCP_TEXTURE_ENABLED) ? params->tformat[1] : 0, params->fbzColorPath, params->alphaMode, params->textureMode[0],params->textureMode[1], params->tex_entry[0],params->tex_entry[1], params->fogMode);
#endif

I assume this is debugging. Consider having 2 otherwise identical functions one with debugging, one without. You should know on start if you are debugging. Branch removed.

    if (params->fbzColorPath & FBZ_PARAM_ADJUST) {
state.base_r += (dx * params->dRdX + dy * params->dRdY) >> 4;
state.base_g += (dx * params->dGdX + dy * params->dGdY) >> 4;
state.base_b += (dx * params->dBdX + dy * params->dBdY) >> 4;
state.base_a += (dx * params->dAdX + dy * params->dAdY) >> 4;
state.base_z += (dx * params->dZdX + dy * params->dZdY) >> 4;
state.tmu[0].base_s += (dx * params->tmu[0].dSdX + dy * params->tmu[0].dSdY) >> 4;
state.tmu[0].base_t += (dx * params->tmu[0].dTdX + dy * params->tmu[0].dTdY) >> 4;
state.tmu[0].base_w += (dx * params->tmu[0].dWdX + dy * params->tmu[0].dWdY) >> 4;
state.tmu[1].base_s += (dx * params->tmu[1].dSdX + dy * params->tmu[1].dSdY) >> 4;
state.tmu[1].base_t += (dx * params->tmu[1].dTdX + dy * params->tmu[1].dTdY) >> 4;
state.tmu[1].base_w += (dx * params->tmu[1].dWdX + dy * params->tmu[1].dWdY) >> 4;
state.base_w += (dx * params->dWdX + dy * params->dWdY) >> 4;
}

Best I can understand here is that you're getting the rgb values, and the coordinates, and bitshifting.
And something to do with the TMUs.

    state.vertexAy = params->vertexAy & ~0xffff0000;
if (state.vertexAy & 0x8000)
state.vertexAy |= 0xffff0000;
state.vertexBy = params->vertexBy & ~0xffff0000;
if (state.vertexBy & 0x8000)
state.vertexBy |= 0xffff0000;
state.vertexCy = params->vertexCy & ~0xffff0000;
if (state.vertexCy & 0x8000)
state.vertexCy |= 0xffff0000;

state.vertexAx = params->vertexAx & ~0xffff0000;
if (state.vertexAx & 0x8000)
state.vertexAx |= 0xffff0000;
state.vertexBx = params->vertexBx & ~0xffff0000;
if (state.vertexBx & 0x8000)
state.vertexBx |= 0xffff0000;
state.vertexCx = params->vertexCx & ~0xffff0000;
if (state.vertexCx & 0x8000)
state.vertexCx |= 0xffff0000
vertexAy_adjusted = (state.vertexAy + 7) >> 4;
vertexCy_adjusted = (state.vertexCy + 7) >> 4;

;

Not sure what this is doing tbh.

    if (state.vertexBy - state.vertexAy)
state.dxAB = (int) ((((int64_t) state.vertexBx << 12) - ((int64_t) state.vertexAx << 12)) << 4) / (state.vertexBy - state.vertexAy);
else
state.dxAB = 0;
if (state.vertexCy - state.vertexAy)
state.dxAC = (int) ((((int64_t) state.vertexCx << 12) - ((int64_t) state.vertexAx << 12)) << 4) / (state.vertexCy - state.vertexAy);
else
state.dxAC = 0;
if (state.vertexCy - state.vertexBy)
state.dxBC = (int) ((((int64_t) state.vertexCx << 12) - ((int64_t) state.vertexBx << 12)) << 4) / (state.vertexCy - state.vertexBy);
else
state.dxBC = 0;

[/code];

Or here.

    state.lod_min[0] = (params->tLOD[0] & 0x3f) << 6;
state.lod_max[0] = ((params->tLOD[0] >> 6) & 0x3f) << 6;
if (state.lod_max[0] > 0x800)
state.lod_max[0] = 0x800;
state.lod_min[1] = (params->tLOD[1] & 0x3f) << 6;
state.lod_max[1] = ((params->tLOD[1] >> 6) & 0x3f) << 6;
if (state.lod_max[1] > 0x800)
state.lod_max[1] = 0x800;

state.xstart = state.xend = state.vertexAx << 8;
state.xdir = params->sign ? -1 : 1;

state.y = (state.vertexAy + 8) >> 4;
state.ydir = 1;

LOD = voodoo_triangle_lod_base(params, 0);
lodbias = voodoo_lod_bias(params, 0);

state.tmu[0].lod = LOD + lodbias;

LOD = voodoo_triangle_lod_base(params, 1);
lodbias = voodoo_lod_bias(params, 1);

state.tmu[1].lod = LOD + lodbias;

/*GLIDULATOR: per-part LOD only where it can matter (voodoo_lod_always_min())*/
if (params->fbzColorPath & FBZCP_TEXTURE_ENABLED) {
for (int tmu = 0; tmu < (voodoo->dual_tmus ? 2 : 1); tmu++) {
if ((params->textureMode[tmu] & TEXTUREMODE_TPERSP_ST) && state.lod_min[tmu] != state.lod_max[tmu])
state.tmu[tmu].lod_at_min = voodoo_lod_always_min(params, &state, tmu, voodoo_lod_bias(params, tmu), &state.tmu[tmu].lod);
}
}
state.stipple = params->stipple;

I don't understand why a pixel drawing thing is updating lod settings or both "tmus" for every pixel.
This doesn't seem to belong inside here. But I have no idea.

General comment. There's a lot of bitshifting, and I know that's how the voodoo worked, but this isn't a voodoo.
Might be faster to keep all the internals as float and especially if you can use AVX. Convert to RGB at the end.

I built:
Convert old ASUS ASC boardviews to KICAD PCB!
Re: A comprehensive guide to install and play MechWarrior 2 on new versions on Windows.
Dos+Windows 3.11+tcp+vbe_svga auto-install iso template
Script to backup Win9x\ME drivers from a working install
Re: The thing no one asked for: KICAD 440bx reference schematic

Reply 109 of 121, by myne

User metadata
Rank l33t
Rank
l33t

Might have found a simple one.

vid_voodoo_render.c

#define VOODOO_STW_FIXED_POINT_ONE 4294967296.0
voodoo_triangle_lod_base(const voodoo_params_t *params, int tmu)
{
double dsdx = (double) params->tmu[tmu].dSdX / VOODOO_STW_FIXED_POINT_ONE;
double dtdx = (double) params->tmu[tmu].dTdX / VOODOO_STW_FIXED_POINT_ONE;
double dsdy = (double) params->tmu[tmu].dSdY / VOODOO_STW_FIXED_POINT_ONE;
double dtdy = (double) params->tmu[tmu].dTdY / VOODOO_STW_FIXED_POINT_ONE;

return voodoo_lod_from_gradients(dsdx, dtdx, dsdy, dtdy);
}

4 294 967 296 = 0b1 00000000 00000000 00000000 00000000
So, unless I'm wrong, you're effectively shifting the top half of a 64 bit integer into the bottom half.

Bitshift >>32 should be equivalent.

Searching also reveals the instruction
SHRD (Shift Right Double Precision)
shrd eax, edx, 32 ; Copies the top 32 bits of RDX into EAX

Though it's entirely possible the compiler is already figuring this out and directly using SHRD in the code. They're pretty smart.

Alternately, SSE2 has PSHUFD (128bit), and avx has VPSHUFD (256bit)

Option 2: PSHUFD xmm, xmm, imm8 (The Shuffle Method)

If you want to extract the top 32 bits but don't want to wait for a shift operation, you can use a Doubleword Shuffle. This inst […]
Show full quote

If you want to extract the top 32 bits but don't want to wait for a shift operation, you can use a Doubleword Shuffle. This instruction treats the 128-bit register as four 32-bit pieces (slots 0, 1, 2, 3) and lets you rearrange them instantly.
To take the top half of both 64-bit lanes and put them in the bottom halves, you change the layout via the immediate byte (imm8).
• Behavior: It duplicates the top 32-bit halves into the bottom halves.
• Result Layout: [ Top QW1 | Top QW1 | Top QW0 | Top QW0 ]
• Pros: Extremely fast on modern CPUs because it bypasses the shift execution unit.

So... You load 2x 64bit bytes, and then treat them as 4x32 and duplicate the top half to the bottom half in allegedly one clock cycle.

Which means
double dsdx = (double) params->tmu[tmu].dSdX / VOODOO_STW_FIXED_POINT_ONE;
double dtdx = (double) params->tmu[tmu].dTdX / VOODOO_STW_FIXED_POINT_ONE;
in one cylcle
and
double dsdy = (double) params->tmu[tmu].dSdY / VOODOO_STW_FIXED_POINT_ONE;
double dtdy = (double) params->tmu[tmu].dTdY / VOODOO_STW_FIXED_POINT_ONE;
in the second.

Or in avx VPSHUFD should do the whole thing in one clock.

Ie, you load 4x 64bit numbers (double) params->tmu[tmu].dSdX , (double) params->tmu[tmu].dTdX , (double) params->tmu[tmu].dSdY , (double) params->tmu[tmu].dTdY
You treat them as though they are 8x 32bit numbers
And it duplicates the top halves into the bottom.

Eg:
7306589901423982494 35011595422138759709 26759969611444839346 33243255413489919837 goes in.

730658990730658990 35011595423501159542 26759969612675996961 33243255413324325541 comes out.

From there you have done your 32bit bitshift in ONE CLOCK!

I built:
Convert old ASUS ASC boardviews to KICAD PCB!
Re: A comprehensive guide to install and play MechWarrior 2 on new versions on Windows.
Dos+Windows 3.11+tcp+vbe_svga auto-install iso template
Script to backup Win9x\ME drivers from a working install
Re: The thing no one asked for: KICAD 440bx reference schematic

Reply 110 of 121, by myne

User metadata
Rank l33t
Rank
l33t

Google's ai spits out the usual crack addict wow such a great idea! and this code

AVX

#include <immintrin.h>

// Vectorized Voodoo LOD setup
float optimized_voodoo_triangle_lod_base(const voodoo_params_t *params, int tmu)
{
// 1. Load the 4x 64-bit parameters into a 256-bit register
// Memory layout: [ dTdY | dSdY | dTdX | dSdX ]
__m256i src = _mm256_set_epi64x(
params->tmu[tmu].dTdY,
params->tmu[tmu].dSdY,
params->tmu[tmu].dTdX,
params->tmu[tmu].dSdX
);

// 2. Extract the top 32-bits (Integer parts) using the 1-cycle permute trick
// Top halves sit in slots 7, 5, 3, 1
__m256i mask = _mm256_set_epi32(0, 0, 0, 0, 7, 5, 3, 1);
__m256i permuted = _mm256_permutevar8x32_epi32(src, mask);

// Narrow down to a 128-bit XMM register (free cast)
__m128i integer_parts = _mm256_castsi256_si128(permuted);

// 3. Convert all 4 integer lanes to standard floats simultaneously (1 cycle)
// Result: [ dtdy | dsdy | dtdx | dsdx ]
__m128 gradients = _mm_cvtepi32_ps(integer_parts);

// 4. Pass the packed vector straight to your updated LOD calculation function
return vectorized_voodoo_lod_from_gradients(gradients);
}

And for SSE

#include <immintrin.h>

float sse_voodoo_triangle_lod_base(const voodoo_params_t *params, int tmu)
{
// 1. Load the X gradients into one XMM register, and Y into another
// Register layout: [ dTdX (64-bit) | dSdX (64-bit) ]
__m128i x_grads = _mm_set_epi64x(params->tmu[tmu].dTdX, params->tmu[tmu].dSdX);
// Register layout: [ dTdY (64-bit) | dSdY (64-bit) ]
__m128i y_grads = _mm_set_epi64x(params->tmu[tmu].dTdY, params->tmu[tmu].dSdY);

// 2. Use PSHUFD (1 cycle) on both to duplicate the top 32-bit halves into the bottom halves
// Control pattern 0xF5 copies slot 3 to slot 2, and slot 1 to slot 0
__m128i x_top_dupes = _mm_shuffle_epi32(x_grads, _MM_SHUFFLE(3, 3, 1, 1));
__m128i y_top_dupes = _mm_shuffle_epi32(y_grads, _MM_SHUFFLE(3, 3, 1, 1));

// 3. Pack them tightly together. We take the low halves of both shuffled registers
// and blend them into a single 128-bit space.
// Result: [ dTdY | dSdY | dTdX | dSdX ] (All 32-bit integers now)
__m128i integer_parts = _mm_shufps_epi32(x_top_dupes, y_top_dupes, _MM_SHUFFLE(1, 0, 1, 0));

// 4. Convert all 4 integer lanes to standard floats simultaneously (1 cycle)
// Result: [ dtdy | dsdy | dtdx | dsdx ]
__m128 gradients = _mm_cvtepi32_ps(integer_parts);

// 5. Pass to your SSE LOD function
return sse_voodoo_lod_from_gradients(gradients);
}

I built:
Convert old ASUS ASC boardviews to KICAD PCB!
Re: A comprehensive guide to install and play MechWarrior 2 on new versions on Windows.
Dos+Windows 3.11+tcp+vbe_svga auto-install iso template
Script to backup Win9x\ME drivers from a working install
Re: The thing no one asked for: KICAD 440bx reference schematic

Reply 111 of 121, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
myne wrote on 2026-11-08, 23:23:

Can you do a test branch that's 64bit and reserves worker memory at say... 4.5gb?
Or wherever the "new 64bit standard" is for pci/whatever.
Obviously keep the dos > worker io where it needs to be.

Maybe my understanding is wrong, but there are some points that do not match with this idea:

- We cannot access >4 GByte, since there is the 32-bit limit on CPU0. The process must deliver data to the workers and that is the limiting factor, since it must be inside the process (for DOS). There is plenty of memory <4 GB, which works totally fine within Windows even for communication, after figuring out that the workers may only start once all memory is allocated (stale TLB entries on the other cores). Under DOS the problem is getting the memory at all (see the last point).
- Even with plenty of memory in Windows, 64-bit workers would need their own copy of the Windows page tables in 64-bit format, kept in sync with every change Windows makes. Knowing the TLB issues, this is not a small task.
- The JIT generates 32-bit code anyway. All the code must be ported.
- The real issue comes from DOS/4GW + XMS. It is a 32-bit extender without PAE. We can only work with the memory it delivers and it is easily irritated by something a modern BIOS reports. Thus, in QEMU everything works fine, but on real hardware, it is a completely different story. I'm really happy that the current way works at all.
- We would need a second toolchain (Open Watcom cannot generate 64-bit code) and the 64-bit code would have to be loaded separately. Not a blocker, but it makes it even more complicated.

I will come to that later - maybe there a way to work around the behaviour of DOS/4GW. But first I want to have a mostly stable version which could play a bunch of games. Also I have no Idea if AMD Systems are working at all. Answering that question is very high on my todo list. /edit: turns out it works quite well in DOS on a Ryzen 5800x. I still have to test Windows.

Last but not least, thank you for your optimisations. I will take a look at it after finishing the next release

Last edited by ElTentakel on 2026-11-09, 20:00. Edited 1 time in total.

Reply 112 of 121, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
myne wrote on Yesterday, 00:49:

General comment. There's a lot of bitshifting, and I know that's how the voodoo worked, but this isn't a voodoo.
Might be faster to keep all the internals as float and especially if you can use AVX. Convert to RGB at the end.

I agree, but I it's part of my actual test strategy for now to compare the C and the JIT outcome to be able to detect differences. Maybe I should have two paths: one for speed and one for accuracy to test against with.

Reply 113 of 121, by shadowmage

User metadata
Rank Newbie
Rank
Newbie

Glidulator GL-1 works not only on Windows 98, but also on Windows NT 3.51 and NT 4.0.

There's also a project called g2xwarp, which allows Glide 3 (glide3x.dll) and Glide 2.1.1 (glide.dll) games to run using Glide 2 (glide2x.dll). This version even works on Windows NT 3.51: Fix for Pandemonium! on Voodoo 3

The ZIP file (download/file.php?id=174350) contains glide.dll, while glide3x.dll can be found inside the g2xwarp folder.

I tested the Need for Speed: Porsche Unleashed demo, and it works on Windows 98. I also managed to run the Quake III Arena 1.11 demo using glide3x.dll from g2xwarp together with MesaFX 7.4.4: https://github.com/NejcDolicanin/MesaFX-7.4.4

As for games that use Glide 2.1.1 (glide.dll), I managed to run Pandemonium!, MechWarrior 2: 31st Century Combat (3dfx OEM version), and CyberGladiators using g2xwarp.

I can also launch the Scorched Planet demo, but the graphics are displayed incorrectly due to color issues.

Reply 114 of 121, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie

That's very interesting. It is a funny coincidence that it works at all. Windows NT ends up on a combination of the Wine test path with Multi Threading and the Linux test path that was never tested like this. But this is only possible with the plain C code and without Write Combing I guess.

Last edited by ElTentakel on 2026-11-09, 21:23. Edited 1 time in total.

Reply 115 of 121, by myne

User metadata
Rank l33t
Rank
l33t
ElTentakel wrote on Yesterday, 16:47:
Maybe my understanding is wrong, but there are some points that do not match with this idea: […]
Show full quote
myne wrote on 2026-11-08, 23:23:

Can you do a test branch that's 64bit and reserves worker memory at say... 4.5gb?
Or wherever the "new 64bit standard" is for pci/whatever.
Obviously keep the dos > worker io where it needs to be.

Maybe my understanding is wrong, but there are some points that do not match with this idea:

- We cannot access >4 GByte, since there is the 32-bit limit on CPU0. The process must deliver data to the workers and that is the limiting factor, since it must be inside the process (for DOS). There is plenty of memory <4 GB, which works totally fine within Windows even for communication, after figuring out that the workers may only start once all memory is allocated (stale TLB entries on the other cores). Under DOS the problem is getting the memory at all (see the last point).
- Even with plenty of memory in Windows, 64-bit workers would need their own copy of the Windows page tables in 64-bit format, kept in sync with every change Windows makes. Knowing the TLB issues, this is not a small task.
- The JIT generates 32-bit code anyway. All the code must be ported.
- The real issue comes from DOS/4GW + XMS. It is a 32-bit extender without PAE. We can only work with the memory it delivers and it is easily irritated by something a modern BIOS reports. Thus, in QEMU everything works fine, but on real hardware, it is a completely different story. I'm really happy that the current way works at all.
- We would need a second toolchain (Open Watcom cannot generate 64-bit code) and the 64-bit code would have to be loaded separately. Not a blocker, but it makes it even more complicated.

I will come to that later - maybe there a way to work around the behaviour of DOS/4GW. But first I want to have a mostly stable version which could play a bunch of games. Also I have no Idea if AMD Systems are working at all. Answering that question is very high on my todo list. /edit: turns out it works quite well in DOS on a Ryzen 5800x. I still have to test Windows.

Last but not least, thank you for your optimisations. I will take a look at it after finishing the next release

I meant 4.5gb for the worker loop. Not for the io between the os and workers.
But it was just a thought to definitely keep it out of the way. Dos/9x can't address it, so they can't overwrite it.

Ie voodoo port at usual place (all I can think is serial right now so Eg 3fh) voodoo "Mimo" ram <4gb like a normal pci card, and worker loop 64bit >4gb.

You said there were memory issues, so it was a thought. There are probably other ways.

I built:
Convert old ASUS ASC boardviews to KICAD PCB!
Re: A comprehensive guide to install and play MechWarrior 2 on new versions on Windows.
Dos+Windows 3.11+tcp+vbe_svga auto-install iso template
Script to backup Win9x\ME drivers from a working install
Re: The thing no one asked for: KICAD 440bx reference schematic

Reply 116 of 121, by DoZator

User metadata
Rank Member
Rank
Member

If memory above 4G is used, it will be necessary to check whether there is any useful data in these ranges, since many users of such systems often use something like "RAMDRV4M" or "RamDsk64", and user data may easily be present there - data that is saved in memory above 4G even after switching to MS-DOS mode, as well as after a hardware reboot of the PC (in the case of "RAMDRV4M"); this data continues to be used under Windows. Could GLIDULATOR somehow fail to notice and damage this data if it does go beyond 4G and manages to reach it, or is such a thing ruled out?

Pył with EMM386 but without HDPMI32i crashes while loading a level on one tester's machine (HIMEM + EMM386, DOS/4GW directly). Without EMM386, or with HDPMI32i, it loads. Cause not known yet; please report the error text and a trace log if you see it.

In version 9.6, these crashes stopped. In MS-DOS modes, everything works stably - before and after Windows (only one core). HDPMI32I adds support for multithreading to EMM386 (in both cases). Everything works just as stably and smoothly. Performance has noticeably improved (it started improving rapidly back in version 0.9.5) and it’s now playable. On Windows, there’s only one core with glide2x.ovl (just as before). Thank you.

Reply 117 of 121, by myne

User metadata
Rank l33t
Rank
l33t

I had a crack at a fork. Ran out of tokens.

But it did make progress on a more generic launcher.
Basically, I was hoping to build a fairly generic dos autoexec run launcher that is capable of loading any payload onto a core with a bit mask.

It did at least mostly run the test.ovl or whatever it was. Unfortunately I'm not great with git either so I'm not sure if the current upload matches the current state without looking.

I built:
Convert old ASUS ASC boardviews to KICAD PCB!
Re: A comprehensive guide to install and play MechWarrior 2 on new versions on Windows.
Dos+Windows 3.11+tcp+vbe_svga auto-install iso template
Script to backup Win9x\ME drivers from a working install
Re: The thing no one asked for: KICAD 440bx reference schematic

Reply 118 of 121, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie

One of the ideas I have, when this project is "finished" enough, is to try generic workers with a RTOS on every core. This has the advantage, that there is a standard workflow that anyone can follow. This solves compatibility issues with other software, but will always be slower than the dedicated bare metal approach.

Reply 119 of 121, by myne

User metadata
Rank l33t
Rank
l33t

Yes, that is basically where it's at
Wait, no. I read that as leader

Last edited by myne on 2026-11-10, 13:35. Edited 1 time in total.

I built:
Convert old ASUS ASC boardviews to KICAD PCB!
Re: A comprehensive guide to install and play MechWarrior 2 on new versions on Windows.
Dos+Windows 3.11+tcp+vbe_svga auto-install iso template
Script to backup Win9x\ME drivers from a working install
Re: The thing no one asked for: KICAD 440bx reference schematic