VOGONS


First post, by Qbix

User metadata
Rank DOSBox Author
Rank
DOSBox Author

While investigating bug 515 https://sourceforge.net/p/dosbox/bugs/515/
I found that the bottom triangle of the quad is under certain conditions misaligned with the top one.
However, so far it seems to be specific to NVIDIA and only for certain resolutions.

The game in question is Rise of the Triad
The B on the title screen

rott-base.png
Filename
rott-base.png
File size
95.52 KiB
Views
1026 views
File license
Fair use/fair dealing exception

When it is wrong, it is either:

rott-bug1.png
Filename
rott-bug1.png
File size
2 KiB
Views
1026 views
File license
Fair use/fair dealing exception

or

rott-bug2.png
Filename
rott-bug2.png
File size
2.87 KiB
Views
1026 views
File license
Fair use/fair dealing exception

(the triangles are joined on the line bottom left to top right, I assume)

It happens for:
output=openglnb
aspect=false
scaler=none
windowresolution=1680x1050 (also with scaler=normal2x and normal3x)
or
windowresolution=1440x900 (both none and normal2x)
or
windowresolution=1200x750 (normal3x)
or
windowresolution=720x450 (none)


Can somebody try it on an AMD card ?

Does anybody have an idea on what is happening ?

I have a fix, but it slightly changes the output and I am not sure if I should do it always or if there is some pattern and if it should be done for bilinear mode as well ?

I am not that familiar with opengl, so feel free to suggest other ways to fix it
patch:

Index: src/gui/sdlmain.cpp
===================================================================
--- src/gui/sdlmain.cpp (revision 4259)
+++ src/gui/sdlmain.cpp (working copy)
@@ -745,8 +745,10 @@
glMatrixMode (GL_MODELVIEW);
glLoadIdentity ();

- GLfloat tex_width=((GLfloat)(width)/(GLfloat)texsize);
- GLfloat tex_height=((GLfloat)(height)/(GLfloat)texsize);
+ GLfloat dummy = 0.001f;
+ GLfloat tex_width=((GLfloat)(width-dummy)/(GLfloat)texsize);
+ GLfloat tex_height=((GLfloat)(height-dummy)/(GLfloat)texsize);
+ dummy/=(GLfloat)texsize;

if (glIsList(sdl.opengl.displaylist)) glDeleteLists(sdl.opengl.displaylist, 1);
sdl.opengl.displaylist = glGenLists(1);
@@ -754,13 +756,13 @@
glBindTexture(GL_TEXTURE_2D, sdl.opengl.texture);
glBegin(GL_QUADS);
// lower left
- glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f);
+ glTexCoord2f(dummy,tex_height); glVertex2f(-1.0f,-1.0f);
// lower right
glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f);
// upper right
- glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f);
+ glTexCoord2f(tex_width,dummy); glVertex2f(1.0f, 1.0f);
// upper left
- glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
+ glTexCoord2f(dummy,dummy); glVertex2f(-1.0f, 1.0f);
glEnd();
glEndList();
sdl.desktop.type=SCREEN_OPENGL;

Water flows down the stream
How to ask questions the smart way!

Reply 1 of 8, by spiroyster

User metadata
Rank Oldbie
Rank
Oldbie

Have you tried with other nV GPU's? Is it for all nV? I don't have an AMD card to try this I'm afraid, nor rise of the triad, but it does like look it could be due to FP precision. I certinaly haven't noticed this with DosBox, but then againt not really paid much attention to minute details like this 😲 . GL doesn't give any power over evaluation quality/precision afaik (other than passing in floats or doubles to the API), only evaluation type for differing interpolation techniques (Linear, Nearest neightbour etc). It could be a texture lookup issue (wrong texel queried, despite correct coord), or a mapping interpolation issue (coord evaluated incorrectly).

If it is a driver-ism, out of your hands I'm afraid. Offsetting the texture mapping to attempt to resolve the correct answer may certainly fix for this situation, however I suspect similar artefacts would manifest in different locations as other texture coordinates would now evaluate slightly differently. Maybe try different offsets, smaller values might be better than larger ones.... unfortunately this is all driver/GPU implementation details which I am unaware of and which may vary from vendor to vendor, version to version or GPU to GPU etc.

It might be better to switch to doubles rather than floats if it is a precision issue (ymmv) although most GPU's would still only use floats under the hood anyways (so glXXXd calls would ultimately convert their arguments from doubles to floats for the GPU to use anyhows, most graphics pipelines are 32-bit afaik, compute may allow higher precision), but certainly newer hardware might use doubles at GPU level? Only they would know the details, so we are left to blackbox try stuff.... Replace all the gl 'f' calls with equivalent 'd' (i.e glTexCoord2f with glTexCoord2d)?

If switching to doubles doesn't yield any difference (it might imply that the GPU hardware is in fact using 32-bit graphics pipelines anyway), you could also try some other things from within the float domain though.

Out of curiosity, do you get a similar artefact in the equivalent region of the top right (as opposed to bottom left like the current issue)?. [EDIT: Ah yes, just read the issue]
Try flipping the ordering of the vertices and see if you get this artefact in the top right (and see if it disappears from the bottom left)?

glBegin(GL_QUADS);
// upper right
glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// lower left
glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f);
// lower right
glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f);
glEnd();

Another thing to check is to use GL_TRIANGLES instead of GL_QUADS (quads have been deprecated for a while, and spec doesn't say how they should be handled so it is down to implementation, ultimately driver is going to convert to two triangles to render, and I suspect it would be the same mechanism as GL_TRIANGLES since quad vertex ordering is essentially the same as triangle ordering (without the extra two vertices defined).

To assert this theory, render with triangles and see if you get the same artefact as original

glBegin(GL_TRIANGLES);
// lower left
glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f);
// lower right
glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// upper right
glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// lower right
glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f);
glEnd();

Something else to try is to use a different primitive (GL_TRIANGLE_STRIP) instead of quad. This may evaluate the mapping slightly differently to GL_TRIANGLES/GL_QUAD.

glBegin(GL_TRIANGLE_STRIP);
// lower left
glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f);
// lower right
glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// upper right
glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f);
glEnd();

Another thing to try is to split into more triangles/quads (so rather than 1 quad, use 4 quads) for the screen. Anything to fudge the interpolation to evaluate to the desired result. It's all rather kludgey.

I do suspect this is specific driver version and or specific GPU so different GPU, vendor or driver might not manifest this? Only wider testing would say.

Failing all this, since it is orthographic projection, using shaders to cross-reference evaluated texCoords with screen locations to ensure the correct value might work (but FP precision for larger displays might be an issue still), although there would need to be some extra logic for the different scaling/aspect ratio etc so not 1:1 mapping of normalised screen location to texCoord. In this sense the shader could fix texCoords based on some toher criteria rather what just pops out of the buffer. Don't know how this would affect other functionality within the program or even fit in to the dosbox's display engine though?

I will try to get a hold of ROTT to recreate or look for this in other titles, then I might be able to experiment myself at some point.
Good luck. Rather you than me 😀

Reply 2 of 8, by Qbix

User metadata
Rank DOSBox Author
Rank
DOSBox Author

Thank you for the suggestions!

spiroyster wrote:

Have you tried with other nV GPU's? Is it for all nV?

1060 and 850M, so at least 2 different cards.

spiroyster wrote:

It might be better to switch to doubles rather than floats if it is a precision issue (ymmv) although most GPU's would still only use floats under the hood anyways (so glXXXd calls would ultimately convert their arguments from doubles to floats for the GPU to use anyhows, most graphics pipelines are 32-bit afaik, compute may allow higher precision), but certainly newer hardware might use doubles at GPU level? Only they would know the details, so we are left to blackbox try stuff.... Replace all the gl 'f' calls with equivalent 'd' (i.e glTexCoord2f with glTexCoord2d)?

Made no difference unfortunately.

spiroyster wrote:
[…]
Show full quote
glBegin(GL_QUADS);
// upper right
glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// lower left
glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f);
// lower right
glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f);
glEnd();

The same distortion.

spiroyster wrote:
Another thing to check is to use GL_TRIANGLES instead of GL_QUADS (quads have been deprecated for a while, and spec doesn't say […]
Show full quote

Another thing to check is to use GL_TRIANGLES instead of GL_QUADS (quads have been deprecated for a while, and spec doesn't say how they should be handled so it is down to implementation, ultimately driver is going to convert to two triangles to render, and I suspect it would be the same mechanism as GL_TRIANGLES since quad vertex ordering is essentially the same as triangle ordering (without the extra two vertices defined).

To assert this theory, render with triangles and see if you get the same artefact as original

glBegin(GL_TRIANGLES);
// lower left
glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f);
// lower right
glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// upper right
glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// lower right
glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f);
glEnd();

This gave distortion on the other diagonal (see screenshot, the red lines show the 2 diagonals and are drawn by me, not the game 😉 )

triangles-swapped.png
Filename
triangles-swapped.png
File size
326.78 KiB
Views
917 views
File license
Fair use/fair dealing exception

If I swap it to

		// upper right
glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// lower left
glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f);

// lower left
glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f);
// lower right
glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f);
// upper right
glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f);

I get the same distortion as with the quads.

spiroyster wrote:

Something else to try is to use a different primitive (GL_TRIANGLE_STRIP) instead of quad. This may evaluate the mapping slightly differently to GL_TRIANGLES/GL_QUAD.

Made no difference

However this weird "trick" does work!:

		glBegin(GL_TRIANGLES);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// lower left
glTexCoord2f(0,tex_height*2); glVertex2f(-1.0f,-3.0f);
// upper right
glTexCoord2f(tex_width*2,0); glVertex2f(3.0f, 1.0f);
glEnd();

Making one big triangle and draw in the square 0,0;0,width; height,width;height,0 (so 2 parts of the triangle are unused)
The height,width point is on the diagonal of the big triangle.

Not sure if it is better or worse than the workaround of the first post.
Edit: Somebody tested with an AMD card and could not reproduce it there

Water flows down the stream
How to ask questions the smart way!

Reply 3 of 8, by DosFreak

User metadata
Rank l33t++
Rank
l33t++

What about with MESA?

Re: DOSBox on Windows NT 3.51

Grab GLU32 and OPENGL32.dll from the package in the first post. Still works with 0.75b1.

Haven't tried the later versions of Mesa3D work with DOSBox.

http://www.vogonsdrivers.com/wrappers/files/OpenGL/GDI/Mesa/

How To Ask Questions The Smart Way
Make your games work offline

Reply 4 of 8, by BearOso

User metadata
Rank Newbie
Rank
Newbie
Qbix wrote:
However this weird "trick" does work!: […]
Show full quote

However this weird "trick" does work!:

		glBegin(GL_TRIANGLES);
// upper left
glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f);
// lower left
glTexCoord2f(0,tex_height*2); glVertex2f(-1.0f,-3.0f);
// upper right
glTexCoord2f(tex_width*2,0); glVertex2f(3.0f, 1.0f);
glEnd();

Making one big triangle and draw in the square 0,0;0,width; height,width;height,0 (so 2 parts of the triangle are unused)
The height,width point is on the diagonal of the big triangle.

Not sure if it is better or worse than the workaround of the first post.

Actually, one oversized triangle is the recommended method for overlaid effects, so this is the way to go.

Reply 5 of 8, by spiroyster

User metadata
Rank Oldbie
Rank
Oldbie

Sounds weird fix, but actually makes sense if it is a precision issue 😀

UV mapping is done via barycentric interpolation in which values tend to 0 along the edges of triangles and certainly would reach 'this or that' situations when interpolating along non horizontal/vertical boundaries without filtering (i.e the diagonal). Keeping the interpolation away from the diagonal would help reduce the possibility of this manifesting. The dummy method would just be kicking the can down the road imo, as it sounds like this issue isn't going away for nV and there is still the risk of certain resolution/viewport/scaling combinations producing it with the dummy.

Since it appears to be an nV issue, if you wanted to diverge the code paths so you don't have to implement a universal fix you can check for nVidia:

std::string vendor((const char*)glGetString(GL_VENDOR));
if (vendor == "NVIDIA Corporation")
{
...
}

Probably best to test the oversized triangle method with different aspect ratios as letterboxing and pillarboxing would bring the unused portions of the triangle into view and since clamping is active..

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

... this will leak the texture into the unused portions of the triangle which would then be visible.

You might need to ammend the framebuffer texture to have a border of a single black texel to use that GL_CLAMP parameter. I'm not familiar with SDL however it looks like it is using an old (pre GL3 version) as GL_CLAMP was deprecated then.

Alternatively, you can use GL_CLAMP_TO_BORDER instead of GL_CLAMP, and supply a border colour of black:

float color[] = { 0.0f, 0.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, color);
DosFreak wrote:

What about with MESA?

MESA as a software renderer might work, as it sounds like it’s a precision issue at the hardware level. MESA with an nV GPU may produce it again though?

BearOso wrote:

Actually, one oversized triangle is the recommended method for overlaid effects, so this is the way to go.

While I agree it is recommended in this case, why is this recommended for overlays?

Personally I would have thought it is much more intuitive to work with a quad (2 triangles) whose uv's are {0,0} {0,1} etc? It is easy enough to discard fragments with uv's outside the range 0...1 with a shader, it's not so easy without shaders (GL2.x, what SDL appears to using) as you need to use RGBA textures (border alpha 0) and GL_DECAL/GL_CLAMP. Or GL_CLAMP_TO_BORDER with border colour alpha 0, and a bit more maths/GLisms?

Reply 6 of 8, by jmarsh

User metadata
Rank Oldbie
Rank
Oldbie

Note that because this is early OpenGL the texture is larger than what is rendered (texture dimensions must be a power-of-2), hence the co-ords.
The easiest way to hide the unwanted portions of the triangle would be using a scissor box but I don't think it's required anyway; the triangle will already be clipped by the projection matrix, further steps would only be needed if we were drawing other fixed-size primitives like lines or points.

Reply 8 of 8, by Srandista

User metadata
Rank Oldbie
Rank
Oldbie

I have AMD card, on which DOSBox release I should test it?

Socket 775 - ASRock 4CoreDual-VSTA, Pentium E6500K, 4GB RAM, Radeon 9800XT, ESS Solo-1, Win 98/XP
Socket A - Chaintech CT-7AIA, AMD Athlon XP 2400+, 1GB RAM, Radeon 9600XT, ESS ES1869F, Win 98