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
When it is wrong, it is either:
or
(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:
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)?
1glBegin(GL_QUADS); 2// upper right 3glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f); 4// upper left 5glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f); 6// lower left 7glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f); 8// lower right 9glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f); 10glEnd();
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
1glBegin(GL_TRIANGLES); 2// lower left 3glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f); 4// lower right 5glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f); 6// upper left 7glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f); 8// upper right 9glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f); 10// upper left 11glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f); 12// lower right 13glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f); 14glEnd();
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.
1glBegin(GL_TRIANGLE_STRIP); 2// lower left 3glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f); 4// lower right 5glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f); 6// upper left 7glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f); 8// upper right 9glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f); 10glEnd();
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 😀
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
1glBegin(GL_QUADS); 2// upper right 3glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f); 4// upper left 5glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f); 6// lower left 7glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f); 8// lower right 9glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f); 10glEnd();
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
1glBegin(GL_TRIANGLES); 2// lower left 3glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f); 4// lower right 5glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f); 6// upper left 7glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f); 8// upper right 9glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f); 10// upper left 11glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f); 12// lower right 13glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f); 14glEnd();
This gave distortion on the other diagonal (see screenshot, the red lines show the 2 diagonals and are drawn by me, not the game 😉 )
If I swap it to
1 // upper right 2 glTexCoord2f(tex_width,0); glVertex2f(1.0f, 1.0f); 3 // upper left 4 glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f); 5 // lower left 6 glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f); 7 8 // lower left 9 glTexCoord2f(0,tex_height); glVertex2f(-1.0f,-1.0f); 10 // lower right 11 glTexCoord2f(tex_width,tex_height); glVertex2f(1.0f, -1.0f); 12 // upper right 13 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!:
1 glBegin(GL_TRIANGLES); 2 // upper left 3 glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f); 4 // lower left 5 glTexCoord2f(0,tex_height*2); glVertex2f(-1.0f,-3.0f); 6 // upper right 7 glTexCoord2f(tex_width*2,0); glVertex2f(3.0f, 1.0f); 8 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
Qbix wrote:However this weird "trick" does work!: […] Show full quote
However this weird "trick" does work!:
1 glBegin(GL_TRIANGLES); 2 // upper left 3 glTexCoord2f(0,0); glVertex2f(-1.0f, 1.0f); 4 // lower left 5 glTexCoord2f(0,tex_height*2); glVertex2f(-1.0f,-3.0f); 6 // upper right 7 glTexCoord2f(tex_width*2,0); glVertex2f(3.0f, 1.0f); 8 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.
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:
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..
... 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:
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?
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.