ValhallaTeam / angleproject

Automatically exported from code.google.com/p/angleproject
Other
0 stars 0 forks source link

Adjustment for half-pixel offset in Y broken #432

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
What steps will reproduce the problem?
1. Unzip the attached sample
2. Copy ANGLE's libEGL.{lib,dll} and libGLESv2.{lib,dll} to the 
other_libs/win32/Angle/lib folder (or modify the project file's link and Post 
Build event commands and to find the .libs and .dlls elsewhere.
3. Build and run the sample.

What is the expected output? What do you see instead?
The sample draws a grid at the XZ plane at y=0. The front line of the grid is 
missing.

The first attached image shows the broken state. The second shows it working 
using the d3dproto branch with the attached patch applied. Please ignore the 
spheres and the point. I have removed that code from the attached sample. Those 
with sharp eyes may notice the point missing from the broken sample. That was 
an application bug.

What version of the product are you using? On what operating system?
The bug is in trunk and dx11proto branches (the latter only when using 
Renderer9).

Please provide any additional information below.

When adjusting for the half-pixel offset in Y the offset must be subtracted 
from Y *after* Y has been inverted for the top-left origin.

The code in trunk does

vc.halfPixelSize[1] = -1.0f / dxViewport.Height;

then in the shader epilogue

output.gl_Position.y = -(gl_Position.y + dx_HalfPixelSize.y * gl_Position.w);\n"

As half pixel size has been negated, this is equivalent to

 -gl_Position.y + dx_HalfPixelSize.y * gl_Position.w

The fix is to remove the negation when computing vc.halfPixelSize. (BTW, this 
variable name is very confusing. The value calculated is NOT half the pixel 
size. It is one full pixel in size.)

The fix in dx11proto is given in the attached patch. It just changes from "- 1" 
to the "+ 1" shown in the following line from Renderer9.cpp.

vc.viewAdjust[1] = (float)((actualViewport.height - (int)dxViewport.Height) + 2 
* (actualViewport.y - (int)dxViewport.Y) + 1) / dxViewport.Height;

Original issue reported on code.google.com by callow.m...@artspark.co.jp on 7 Jun 2013 at 6:21

Attachments:

GoogleCodeExporter commented 9 years ago
Your patch causes lots of WebGL 1.0.2 regressions 
(https://www.khronos.org/registry/webgl/conformance-suites/1.0.2/webgl-conforman
ce-tests.html).

halfPixelSize is not a full pixel in size. Remember that OpenGL's NDC space 
ranges from -1 to 1 in each dimension.

Perhaps your line is right at the edge of the clipping volume, and DX11 rounds 
the other way?

Original comment by nico...@transgaming.com on 10 Jun 2013 at 6:58

GoogleCodeExporter commented 9 years ago
The test sample works on all other OpenGL ES implementations we have tested on, 
except ANGLE. Yes the line is at the edge of the clipping volume. Do the tests 
that regress with my patch, when run without my patch give the same results on 
OpenGL and ANGLE?

Where this half-pixel difference applied in the pipeline? The only 
documentation of the D3D 9 viewport transformation that I could find - at 
http://msdn.microsoft.com/en-us/library/windows/desktop/bb206341%28v=vs.85%29.as
px - shows an identical transformation to OpenGL (for x and y) except for 
scaling y by -height/2 instead of height/2.

There are many mentions of the half-pixel difference in words and even one 
diagram but the above was the only mathematical expression of the viewport 
transform that I could find.

Original comment by callow.m...@artspark.co.jp on 11 Jun 2013 at 2:28

GoogleCodeExporter commented 9 years ago
The transformed endpoints of the missing line prior to division by w are (-40, 
-40, 38.039016, 40) and (40, -40, 38.039016, 40).

The original GL viewport settings are x=0, y=80, width=624, height=381. The 
resulting dx viewport settings are x=0, y=80, width=624, height=341. The 
drawable size is 624 x 421.

Original comment by callow.m...@artspark.co.jp on 11 Jun 2013 at 2:42

GoogleCodeExporter commented 9 years ago
You shouldn't rely on a line that's right at the edge of the clipping volume to 
be visible (or not). A slight difference in floating-point rounding can change 
the result, and OpenGL ES 2.0 does not specify how rounding is handled. Even if 
the line is not actually clipped, the OpenGL ES 2.0 rasterization rules are not 
very strict and the pixels may still fall outside the viewport. The best 
guarantee for consistent results across different GL implementations is to have 
endpoints that are close to pixel centers.

Of course we don't deliberately deviate from what most implementations would 
produce,  but it doesn't seem to me that the math in ANGLE's GL to DX 
transformations is incorrect. You can read more about it in the ANGLE article 
in the OpenGL Insights book, which happens to be freely available: 
http://openglinsights.com/

Original comment by nico...@transgaming.com on 11 Jun 2013 at 5:08

GoogleCodeExporter commented 9 years ago
There shouldn't be any rounding in this case. The actual point values are 
({+,-}40.000000, -40.000000, 38.039016, 40.000000). Obviously the line is not 
being clipped, since it appears with my change to the viewport adjustment. 

I looked at the article. It states that

"One last interesting effect of the different viewport trans
formations is that it also affects the fill convention."

Maybe I'm just being thick but I don't see how multiplying y by -1 affects the 
fill convention. That is set by the specification of how rasterization works.

It also says

"OpenGL does not require a specific fill convention, only that a
well-defined convention is used consistently"

but the rasterization section of OpenGL ES 2.0 spec. says that fragment centers 
that lie inside the (triangle) are produced by rasterization. Earlier it 
defined the fragment center as being offset by (1/2, 1/2) from its lower-left 
corner. That sounds like a fill convention to me. The only bit left unspecified 
is which (triangle) rasterizes a fragment whose center is on a common edge.

I will try more OpenGL ES implementations and see if any of the ones we have 
not yet tried, fail to emit this line.

Original comment by callow.m...@artspark.co.jp on 11 Jun 2013 at 6:38

GoogleCodeExporter commented 9 years ago
I agree that my proposed fix is incorrect but there is a real problem here.

According to the OpenGL spec rules a line at y_d = -1, y_w = 0, (_d: NDC, _w: 
window) will be rasterized. I not aware of any OpenGL implementation that fails 
to do this. The missing line in this sample is at y_d=-1. Therefore ANGLE is 
not reproducing the same result at OpenGL.

When ANGLE applies the half-pixel offset, y_w becomes -0.5 and D3D9 does not 
rasterize the line. D3D9 line rasterization follows the same rules as GDI and 
"GDI does not round to the nearest integer." Therefore I believe the half-pixel 
offset is unnecessary and should not be applied when drawing aliased lines.

One way to fix this is to compute a second viewAdjust value in setViewport and 
prior to line drawing set the dx_ViewAdjust uniforms to the non-adjusted value.

Original comment by callow.m...@artspark.co.jp on 12 Jun 2013 at 6:52

GoogleCodeExporter commented 9 years ago
I'm sorry but I couldn't find any such guarantee in the OpenGL spec (neither in 
ES 2.0 nor in GL 4.3). In fact it says the following in the 'Basic Line Segment 
Rasterization' section: "The coordinates of a fragment produced by the 
algorithm may not deviate by more than one unit in either x or y window 
coordinates from a corresponding fragment produced by the diamond-exit rule." 
In other words, it may deviate by a full pixel.

But even when adhering to the diamond-exit rule, there is no guarantee that a 
line at the edge of your viewport will get rasterized. It is equivalent to 
rasterizing the six-sided polygon formed by the convex hull of two pixel sized 
diamond shapes centered at the endpoints. When your endpoints are on the edge 
of the viewport, the edges of this polygon intersect the centers of the edge 
pixels. But whether these are considered covered by the polygon or not depends 
on the fill convention.

The fill convention isn't about defining what point in the pixel is used for 
determining coverage. It's about if a polygon edge intersects this point, 
whether it's considered covered or not. It must prevent gaps and overlap 
between polygons that share an edge. And this is affected by flipping the y 
direction. ANGLE effectively renders things 'upside down', and then flips is 
back on present, to deal with the difference in texture orientation as detailed 
in the OpenGL Insights article.

So it's not as if we're making use of the GL spec's pretty massive 1 pixel 
leeway in line rasterization to justify the difference you're observing. It's 
caused by a minute difference in rounding determined by the fill convention, 
which is fully consistent and therefore conforms to the strict GL spec. It is 
only by chance that you haven't encountered any other OpenGL implementation 
where the line isn't visible.

The same problem occurs when people render a rectangle that's aligned to pixel 
centers. There's no guarantee whether the top or bottom row of pixels gets 
rasterized, or the left or right column. Rectangles like that should be aligned 
to pixel edges instead for predictable results. For lines to show up 
consistently you should align the endpoints to pixel centers.

For your case moving the line up a fraction of a pixel should make it visible. 
If it has to be moved up half a pixel or more, that would be an actual bug.

Original comment by nico...@transgaming.com on 12 Jun 2013 at 2:48

GoogleCodeExporter commented 9 years ago
It has to be moved up half a pixel.

In init3D_GLLog after the call to glViewport, add the following line to move 
the line up half a pixel.

    vtxatt_IDX_1_ID_3006[1] = vtxatt_IDX_1_ID_3006[4] += 10.0f/(m_height - 40);

You will also need to remove "const" from the declaration of the array in 
angle/log?gles_pointer.txt.

This gives an NDC position for Y of -0.997375325. To verify the calculation, 
the half-pixel height in NDC is 1.0f/(m_height - 40) = 0.0026246719. 
Subtracting this from -1.0 and you get the above value. Viewport height is set 
to (m_height - 40). With this adjustment, the line will be drawn.

If you change 10.0 in the adjustment to 9.999 the line will disappear.

Original comment by callow.m...@artspark.co.jp on 13 Jun 2013 at 4:22

GoogleCodeExporter commented 9 years ago
Here is a partial patch based on avoiding the half-pixel viewport adjustment 
when drawing lines. The patch is only partial in that it does not differentiate 
anti-aliased lines which are rasterized differently and probably still need the 
viewport adjustment.

Original comment by callow.m...@artspark.co.jp on 13 Jun 2013 at 4:40

GoogleCodeExporter commented 9 years ago
RE comment #8, I should have noted that m_height = 421.

Original comment by callow.m...@artspark.co.jp on 13 Jun 2013 at 5:52

GoogleCodeExporter commented 9 years ago
On my system I merely have to move the vertices up by 0.0395269413f/(m_height - 
40) to make the line appear. Considering that z is at 10.0, this is just 
1/506th of a pixel. This is most probably a rounding error by itself of 1/512th 
of a pixel, which is what you need to make a card with 8-bit subpixel accuracy 
round to the next value. So this perfectly supports my thesis that this is just 
a fill convention difference.

Original comment by nico...@transgaming.com on 13 Jun 2013 at 2:51

GoogleCodeExporter commented 9 years ago
I wonder why get such different results than you.

I have tried on 2 different systems and for any value less than 9.999, i.e, a 
half-pixel adjustment, the line does not appear. Both systems have AMD cards, 
one mobile & 1 desktop. The desktop one has a much newer driver but I see there 
is an even newer driver available. I'll try that.

Original comment by callow.m...@artspark.co.jp on 14 Jun 2013 at 7:07

GoogleCodeExporter commented 9 years ago
There is no change in behavior with the latest driver.

The required adjustment seems too large for line to be missing simply due to a 
rounding issue.

Original comment by callow.m...@artspark.co.jp on 14 Jun 2013 at 7:38

GoogleCodeExporter commented 9 years ago
This still looks like a valid variation in rasterization rules for lines near 
the edge of the viewport to me. Please file a new bug if you have additional 
indications that this might be an ANGLE bug instead.

Original comment by c...@chromium.org on 2 Dec 2013 at 6:58