VOGONS


Reply 120 of 128, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
DoZator wrote on Yesterday, 12:24:

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.

Previously the registers were not cleaned up properly, so this explains it completly. And no, I hadn't thought about RAM disks, thank you for pointing that out.

Reply 121 of 128, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
shadowmage wrote on 2026-11-09, 20:33:
Glidulator GL-1 works not only on Windows 98, but also on Windows NT 3.51 and NT 4.0. […]
Show full quote

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.

I've added a glide.dll target and glide3x.dll is already in the last release. With the next release Scorched Planet demo should render fine with the new target. I also added Windows NT 3.51/4.0 to the test machinery, to not lose compatibility on further development.

Reply 122 of 128, by RayeR

User metadata
Rank Oldbie
Rank
Oldbie

Yes, Glidulator runs under NT4 too, I made some comparison but due to different Windows VGA drivers it's not fully comparable.
BTW is there some small Glide 3 test?

MSDOS 4/4 - 425 FPS @800x600
Win98SE 4/4 - 390 FPS @800x600, Velocity98 0.46 software driver
WinNT4 4/4 - 102 FPS @800x600, WinVBE 1.2 software driver
WinXP 4/4 - 440 FPS @800x600, nVidia accelerated driver

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

Reply 123 of 128, by myne

User metadata
Rank l33t
Rank
l33t

Btw, here's the work I slopped on the payload loader.
It's supposed to be a generic core loader that takes a mask - and only works on 64bit cpus with more than 1 core.

Naturally that doesn't really fit your version.
I'm still liking the idea of loading in the autoexec something like this:
gloader /core 011 glidulator.bin
gloader /core 0001 soundemu.bin

To me, it would be most ideal to implement it as a full "fake pci card" but bypass the registers where possible - and of course, have the workers as pure 64bit number crunchers with SSE and AVX paths chosen based on availability.

Personally, I think it would be better in some ways to just be able to run it with original drivers/ovl's.

I realise there is a fair complexity to faking a pnp pci card, trapping writes, and having the monitors check the right memory ranges.

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 124 of 128, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
RayeR wrote on Today, 03:30:
Yes, Glidulator runs under NT4 too, I made some comparison but due to different Windows VGA drivers it's not fully comparable. B […]
Show full quote

Yes, Glidulator runs under NT4 too, I made some comparison but due to different Windows VGA drivers it's not fully comparable.
BTW is there some small Glide 3 test?

MSDOS 4/4 - 425 FPS @800x600
Win98SE 4/4 - 390 FPS @800x600, Velocity98 0.46 software driver
WinNT4 4/4 - 102 FPS @800x600, WinVBE 1.2 software driver
WinXP 4/4 - 440 FPS @800x600, nVidia accelerated driver

> WinNT4 4/4 - 102 FPS @800x600, WinVBE 1.2 software driver
I’m afraid that is to be expected and hard to change.

I’ll add some tests for Glide3. So far I’ve only implemented (and tested) it against the NFSV driver, which may not be sufficient.

Reply 125 of 128, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
myne wrote on Today, 05:39:
Btw, here's the work I slopped on the payload loader. It's supposed to be a generic core loader that takes a mask - and only wor […]
Show full quote

Btw, here's the work I slopped on the payload loader.
It's supposed to be a generic core loader that takes a mask - and only works on 64bit cpus with more than 1 core.

Naturally that doesn't really fit your version.
I'm still liking the idea of loading in the autoexec something like this:
gloader /core 011 glidulator.bin
gloader /core 0001 soundemu.bin

To me, it would be most ideal to implement it as a full "fake pci card" but bypass the registers where possible - and of course, have the workers as pure 64bit number crunchers with SSE and AVX paths chosen based on availability.

Personally, I think it would be better in some ways to just be able to run it with original drivers/ovl's.

I realise there is a fair complexity to faking a pnp pci card, trapping writes, and having the monitors check the right memory ranges.

Very cool. The loader has to be part of CONFIG.SYS so it can load device drivers for the loaded "device"-binaries.

One disadvantage though, is that you inherit the limitations and constraints of the drivers/overlays and the PCI interface and you can’t fix issues from within the application. For example, Glidulator isn’t bound to the data types and structures of the real device, so it doesn’t need to convert anything to match that interface. Also it can compensate the misbehaviour older titles on newer hardware.

Reply 126 of 128, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
myne wrote on 2026-11-09, 04:17:
Google's ai spits out the usual crack addict wow such a great idea! and this code […]
Show full quote

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);
}

Claude agrees with you, that Google's ai doesn't know what it's talking about.

Reply 127 of 128, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
myne wrote on 2026-11-09, 02:44:
Might have found a simple one. […]
Show full quote

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!

The Compiler is (and should be) clever enough to convert this into a multiplication. Also dividing by 2^32 isn't a shift. dSdX is 32.32 fixed point, so the division keeps the fractional part. Thus every gradient below 1 would then become 0 and get the wrong LOD .

Reply 128 of 128, by ElTentakel

User metadata
Rank Newbie
Rank
Newbie
myne wrote on 2026-11-08, 23:26:
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?

Yes, your following post is based on a wrong assumption, since this is the triangle setup. voodoo_half_triangle() in vid_voodoo_render.c walks the spans and calls JIT-generated x86 code once per span emitted by voodoo_generate().

When my tokens respawn, I can continue with performance optimizations.