JDK-5009033 : OGL: incorrect bilinear filtering for texture->surface transforms
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 5.0,6
  • Priority: P4
  • Status: Closed
  • Resolution: Won't Fix
  • OS: generic
  • CPU: generic
  • Submitted: 2004-03-06
  • Updated: 2007-03-08
  • Resolved: 2007-03-08
Related Reports
Duplicate :  
Relates :  
Description
Compile and run the attached testcase (HugeZoom.java).  Compare the middle
(bilinear) case when run with the default (software-based) pipeline, and when
run with -Dsun.java2d.opengl=true.  The results when run with OGL enabled
should look similar to the default case, but currently there is weird
coloration in the OGL/bilinear case.

Comments
EVALUATION A few changes have been made over time that make this bug less noticeable, and even non-existent on modern hardware. First, we have support for both the GL_ARB_texture_non_power_of_two (4931816, in JDK 5) and the GL_ARB_texture_rectangle (6298243, in JDK 6) extensions in the OpenGL-based Java 2D pipeline. The latter extension is available on all Nvidia hardware going back to the GeForce 2 series, and on ATI hardware going back to the R300 series (Radeon 9500 and above). The former extension is available on newer hardware like Nvidia GeForce 6xxx and above. Also, as of the fix for 6521533, we are now using GL_CLAMP_TO_EDGE as the default texture wrapping mode. What all of this means is that on modern hardware, this bug is no longer visible. On older hardware that do not support the extensions listed above, this bug will be only be visible for textures with non-pow2 dimensions, but even then the problem will only appear on the right/bottom edges of the texture (since 6521533, GL_CLAMP_TO_EDGE takes care of the left/top edges). While it would be nice in theory to fix this one remaining (minor) issue for old boards, I think any fix would clutter the code; we have bigger fish to fry and need to move forward fixing issues that are more likely to be visible on newer boards. We can revisit this later if we see customer demand. Closing as "will not fix".
08-03-2007

SUGGESTED FIX *** /tmp/geta25140 Wed Apr 13 13:27:22 2005 --- OGLBlitLoops.c Wed Apr 13 13:27:20 2005 *************** *** 100,105 **** --- 100,107 ---- j2d_glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); j2d_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, hint); j2d_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, hint); + j2d_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + j2d_glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); j2d_glBegin(GL_QUADS); j2d_glTexCoord2d(tx1, y1); j2d_glVertex2d(dx1, dy1); *************** *** 473,478 **** --- 475,497 ---- // Thus these casts are "safe" - no loss of precision. OGLBlitSwToTexture(&srcInfo, &pf, dstOps, (jint)dx1, (jint)dy1, (jint)dx2, (jint)dy2); + + if (dstOps->width != dstOps->textureWidth) { + j2d_glPixelStorei(GL_UNPACK_SKIP_PIXELS, sx2-1); + j2d_glPixelStorei(GL_UNPACK_SKIP_ROWS, sy1); + j2d_glTexSubImage2D(GL_TEXTURE_2D, 0, + dx2, dy1, 1, dy2-dy1, + pf.format, pf.type, + srcInfo.rasBase); + } + if (dstOps->height != dstOps->textureHeight) { + j2d_glPixelStorei(GL_UNPACK_SKIP_PIXELS, sx1); + j2d_glPixelStorei(GL_UNPACK_SKIP_ROWS, sy2-1); + j2d_glTexSubImage2D(GL_TEXTURE_2D, 0, + dx1, dy2, dx2-dx1, 1, + pf.format, pf.type, + srcInfo.rasBase); + } } else { if (!xform) { OGLBlitSwToSurface(&srcInfo, &pf, ###@###.### 2005-04-13 20:31:06 GMT
13-04-2005

EVALUATION The problem is in the OGLBlitTextureToSurface() method. The default texture wrapping mode is GL_REPEAT, which affects the edge conditions for OpenGL's filtering equations. With GL_REPEAT, OGL will use the wrapped pixels from outside the [0,1] texture coordinate range, which explains why the red edge has bits of green, for example. We should instead explicitly set the texture wrap mode to GL_CLAMP_TO_EDGE, which will produce results more in line with what our software (medialib-based) loops currently produce. ###@###.### 2004-03-05 The above solution should work well for textures with power-of-two dimensions, but unfortunately it breaks down for non-pow2 textures. The problem in that case is that GL_CLAMP_TO_EDGE uses the pixels at the edge of the texture, not at the edge of the source region, for use in edge conditions. For example, if you have a 50x50 BufferedImage, it will be cached in a 64x64 texture (due to the pow2 requirements in OGL, unless the GL_ARB_texture_non_power_of_two extension is present). So when transforming that texture with the texture filter set to GL_LINEAR, GL_CLAMP_TO_EDGE will use the pixels at the right and bottom edges of the texture for those edge conditions. But since only the region from (0,0) to (50,50) contains valid pixels, the texture transform code will end up using garbage pixels, and we will continue to see artifacts similar to those reported here. To work around this problem for non-pow2 images, when we upload a sysmem- based image to an OGL texture, we can replicate the right edge of the source image one pixel to the right, and replicate the bottom edge one pixel below. This should solve the problem described above, at the added expense of two extra calls to glTexSubImage2D() per sw->texture blit. This approach was alluded to in 4841762, and should work for that enhancement as well, although it would be better to avoid using texture borders, since using texture borders is known to cause performance problems on some drivers (see 6251468). ###@###.### 2005-04-12 03:04:33 GMT Another possibility is to emulate the behavior of GL_CLAMP_TO_EDGE by manually adjusting the texture coordinates slightly. GL_CLAMP_TO_EDGE basically clamps the incoming texcoords to the range [1/(2N), 1-(1/(2N))] (where N is the width or height of the texture, in pixels). For example, if the source texture is 4x4, the texcoords will be clamped to the range [0.125,0.875]. Note that these values fall at the centers of the first and last pixels in the texture. Due to the calculations used by OpenGL when filtering, this method of clamping ensures that only edge pixels are used in the calculations (not the border pixels, etc). We can probably apply a similar technique that clamps the texcoords to the actual source region. Using the same example from above (50x50 image in a 64x64 texture), if we were to blit the entire image from the source texture to an OGL destination, we currently use the following texcoord range: [0.0, (50/64)] We could try adjusting this slightly according to the approach taken by GL_CLAMP_TO_EDGE: [1/(2*64), (50/64)-(1/(2*64))] This might give us the desired results, but more testing is required. ###@###.### 2005-04-12 18:49:24 GMT Well, the experimental fix (emulating GL_CLAMP_TO_EDGE) described above doesn't quite work. It appears that GL_CLAMP_TO_EDGE really only clamps the texcoords that are used in the filtering process (so that texels outside that range aren't factored in), but when emulating that behavior, we were clamping the actual texcoords. I think the only general purpose solution is the one described earlier (replicate the right and bottom edges, then use GL_CLAMP_TO_EDGE). I've attached the diffs for this approach in the suggested fix field. ###@###.### 2005-04-13 20:31:05 GMT
13-04-2005