From c2d23145ca7c53278fe19a75faee7226ce963067 Mon Sep 17 00:00:00 2001 From: Vladimir Vukicevic Date: Fri, 12 Nov 2010 12:02:17 -0800 Subject: [PATCH] b=609195; don't use GL_REPEAT for non-power-of-two textures; r=jrmuizel --- gfx/layers/opengl/ThebesLayerOGL.cpp | 46 +++---- gfx/thebes/GLContext.cpp | 186 ++++++++++++++++++++++++--- gfx/thebes/GLContext.h | 31 +++++ 3 files changed, 218 insertions(+), 45 deletions(-) diff --git a/gfx/layers/opengl/ThebesLayerOGL.cpp b/gfx/layers/opengl/ThebesLayerOGL.cpp index 55d5d247e9c7..a49cdc446dc8 100644 --- a/gfx/layers/opengl/ThebesLayerOGL.cpp +++ b/gfx/layers/opengl/ThebesLayerOGL.cpp @@ -73,32 +73,24 @@ BindAndDrawQuadWithTextureRect(LayerProgram *aProg, // "pointer mode" aGl->fBindBuffer(LOCAL_GL_ARRAY_BUFFER, 0); - // NB: quadVertices and texCoords vertices must match - GLfloat quadVertices[] = { - 0.0f, 0.0f, // bottom left - 1.0f, 0.0f, // bottom right - 0.0f, 1.0f, // top left - 1.0f, 1.0f // top right - }; + // Given what we know about these textures and coordinates, we can + // compute fmod(t, 1.0f) to get the same texture coordinate out. If + // the texCoordRect dimension is < 0 or > width/height, then we have + // wraparound that we need to deal with by drawing multiple quads, + // because we can't rely on full non-power-of-two texture support + // (which is required for the REPEAT wrap mode). + + GLContext::RectTriangles rects; + GLContext::DecomposeIntoNoRepeatTriangles(aTexCoordRect, aTexSize, rects); + aGl->fVertexAttribPointer(vertAttribIndex, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - quadVertices); - DEBUG_GL_ERROR_CHECK(aGl); - - GLfloat xleft = GLfloat(aTexCoordRect.x) / GLfloat(aTexSize.width); - GLfloat ytop = GLfloat(aTexCoordRect.y) / GLfloat(aTexSize.height); - GLfloat w = GLfloat(aTexCoordRect.width) / GLfloat(aTexSize.width); - GLfloat h = GLfloat(aTexCoordRect.height) / GLfloat(aTexSize.height); - GLfloat texCoords[] = { - xleft, ytop, - w + xleft, ytop, - xleft, h + ytop, - w + xleft, h + ytop, - }; + rects.vertexCoords); aGl->fVertexAttribPointer(texCoordAttribIndex, 2, LOCAL_GL_FLOAT, LOCAL_GL_FALSE, 0, - texCoords); + rects.texCoords); + DEBUG_GL_ERROR_CHECK(aGl); { @@ -106,7 +98,7 @@ BindAndDrawQuadWithTextureRect(LayerProgram *aProg, { aGl->fEnableVertexAttribArray(vertAttribIndex); - aGl->fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); + aGl->fDrawArrays(LOCAL_GL_TRIANGLES, 0, rects.numRects * 6); DEBUG_GL_ERROR_CHECK(aGl); aGl->fDisableVertexAttribArray(vertAttribIndex); @@ -230,7 +222,7 @@ public: { NS_ASSERTION(gfxASurface::CONTENT_ALPHA != aType,"ThebesBuffer has color"); - mTexImage = gl()->CreateTextureImage(aSize, aType, LOCAL_GL_REPEAT); + mTexImage = gl()->CreateTextureImage(aSize, aType, LOCAL_GL_CLAMP_TO_EDGE); return mTexImage ? mTexImage->GetBackingSurface() : nsnull; } @@ -361,7 +353,7 @@ BasicBufferOGL::BeginPaint(ContentType aContentType) // So allocate a new buffer for the destination. destBufferRect = visibleBounds; destBuffer = gl()->CreateTextureImage(visibleBounds.Size(), aContentType, - LOCAL_GL_REPEAT); + LOCAL_GL_CLAMP_TO_EDGE); DEBUG_GL_ERROR_CHECK(gl()); if (!destBuffer) return result; @@ -380,7 +372,7 @@ BasicBufferOGL::BeginPaint(ContentType aContentType) // The buffer's not big enough, so allocate a new one destBufferRect = visibleBounds; destBuffer = gl()->CreateTextureImage(visibleBounds.Size(), aContentType, - LOCAL_GL_REPEAT); + LOCAL_GL_CLAMP_TO_EDGE); DEBUG_GL_ERROR_CHECK(gl()); if (!destBuffer) return result; @@ -410,7 +402,7 @@ BasicBufferOGL::BeginPaint(ContentType aContentType) // can't blit, just draw everything destBufferRect = visibleBounds; destBuffer = gl()->CreateTextureImage(visibleBounds.Size(), aContentType, - LOCAL_GL_REPEAT); + LOCAL_GL_CLAMP_TO_EDGE); } } @@ -575,7 +567,7 @@ public: { NS_ASSERTION(gfxASurface::CONTENT_ALPHA != aType,"ThebesBuffer has color"); - mTexImage = gl()->CreateTextureImage(aSize, aType, LOCAL_GL_REPEAT); + mTexImage = gl()->CreateTextureImage(aSize, aType, LOCAL_GL_CLAMP_TO_EDGE); } void Upload(gfxASurface* aUpdate, const nsIntRegion& aUpdated, diff --git a/gfx/thebes/GLContext.cpp b/gfx/thebes/GLContext.cpp index 7e053efc1a56..ac6bf9055d91 100644 --- a/gfx/thebes/GLContext.cpp +++ b/gfx/thebes/GLContext.cpp @@ -1157,29 +1157,24 @@ GLContext::BlitTextureImage(TextureImage *aSrc, const nsIntRect& aSrcRect, PushViewportRect(nsIntRect(0, 0, dstSize.width, dstSize.height)); - float sx0 = float(aSrcRect.x) / float(srcSize.width); - float sy0 = float(aSrcRect.y) / float(srcSize.height); - float sx1 = float(aSrcRect.x + aSrcRect.width) / float(srcSize.width); - float sy1 = float(aSrcRect.y + aSrcRect.height) / float(srcSize.height); - float dx0 = 2.0 * float(aDstRect.x) / float(dstSize.width) - 1.0; float dy0 = 2.0 * float(aDstRect.y) / float(dstSize.height) - 1.0; float dx1 = 2.0 * float(aDstRect.x + aDstRect.width) / float(dstSize.width) - 1.0; float dy1 = 2.0 * float(aDstRect.y + aDstRect.height) / float(dstSize.height) - 1.0; - float quadTriangleCoords[] = { - dx0, dy1, - dx0, dy0, - dx1, dy1, - dx1, dy0 - }; + RectTriangles rects; + DecomposeIntoNoRepeatTriangles(aSrcRect, srcSize, + rects); - float texCoords[] = { - sx0, sy1, - sx0, sy0, - sx1, sy1, - sx1, sy0 - }; + GLfloat *quadTriangleCoords = rects.vertexCoords; + GLfloat *texCoords = rects.texCoords; + + // now put the coords into the d[xy]0 .. d[xy]1 coordinate space + // from the 0..1 that it comes out of decompose + for (int i = 0; i < rects.numRects * 6; ++i) { + quadTriangleCoords[i*2] = (quadTriangleCoords[i*2] * (dx1 - dx0)) + dx0; + quadTriangleCoords[i*2+1] = (quadTriangleCoords[i*2+1] * (dy1 - dy0)) + dy0; + } fActiveTexture(LOCAL_GL_TEXTURE0); fBindTexture(LOCAL_GL_TEXTURE_2D, aSrc->Texture()); @@ -1194,7 +1189,7 @@ GLContext::BlitTextureImage(TextureImage *aSrc, const nsIntRect& aSrcRect, DEBUG_GL_ERROR_CHECK(this); - fDrawArrays(LOCAL_GL_TRIANGLE_STRIP, 0, 4); + fDrawArrays(LOCAL_GL_TRIANGLES, 0, rects.numRects * 6); DEBUG_GL_ERROR_CHECK(this); @@ -1223,6 +1218,161 @@ GLContext::BlitTextureImage(TextureImage *aSrc, const nsIntRect& aSrcRect, PopViewportRect(); } +void +GLContext::RectTriangles::addRect(GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, + GLfloat tx0, GLfloat ty0, GLfloat tx1, GLfloat ty1) +{ + NS_ASSERTION(numRects < 4, "Overflow in number of rectangles, max 4!"); + + GLfloat *v = &vertexCoords[numRects*6*2]; + GLfloat *t = &texCoords[numRects*6*2]; + + *v++ = x0; *v++ = y0; + *v++ = x1; *v++ = y0; + *v++ = x0; *v++ = y1; + + *v++ = x0; *v++ = y1; + *v++ = x1; *v++ = y0; + *v++ = x1; *v++ = y1; + + *t++ = tx0; *t++ = ty0; + *t++ = tx1; *t++ = ty0; + *t++ = tx0; *t++ = ty1; + + *t++ = tx0; *t++ = ty1; + *t++ = tx1; *t++ = ty0; + *t++ = tx1; *t++ = ty1; + + numRects++; +} + +static GLfloat +WrapTexCoord(GLfloat v) +{ + // fmodf gives negative results for negative numbers; + // that is, fmodf(0.75, 1.0) == 0.75, but + // fmodf(-0.75, 1.0) == -0.75. For the negative case, + // the result we need is 0.25, so we add 1.0f. + if (v < 0.0f) { + return 1.0f + fmodf(v, 1.0f); + } + + return fmodf(v, 1.0f); +} + +void +GLContext::DecomposeIntoNoRepeatTriangles(const nsIntRect& aTexCoordRect, + const nsIntSize& aTexSize, + RectTriangles& aRects) +{ + // normalize this + nsIntRect tcr(aTexCoordRect); + while (tcr.x > aTexSize.width) + tcr.x -= aTexSize.width; + while (tcr.y > aTexSize.height) + tcr.y -= aTexSize.height; + + // Compute top left and bottom right tex coordinates + GLfloat tl[2] = + { GLfloat(tcr.x) / GLfloat(aTexSize.width), + GLfloat(tcr.y) / GLfloat(aTexSize.height) }; + GLfloat br[2] = + { GLfloat(tcr.XMost()) / GLfloat(aTexSize.width), + GLfloat(tcr.YMost()) / GLfloat(aTexSize.height) }; + + // then check if we wrap in either the x or y axis; if we do, + // then also use fmod to figure out the "true" non-wrapping + // texture coordinates. + + bool xwrap = false, ywrap = false; + if (tcr.x < 0 || tcr.x > aTexSize.width || + tcr.XMost() < 0 || tcr.XMost() > aTexSize.width) + { + xwrap = true; + tl[0] = WrapTexCoord(tl[0]); + br[0] = WrapTexCoord(br[0]); + } + + if (tcr.y < 0 || tcr.y > aTexSize.height || + tcr.YMost() < 0 || tcr.YMost() > aTexSize.height) + { + ywrap = true; + tl[1] = WrapTexCoord(tl[1]); + br[1] = WrapTexCoord(br[1]); + } + + NS_ASSERTION(tl[0] >= 0.0f && tl[0] <= 1.0f && + tl[1] >= 0.0f && tl[1] <= 1.0f && + br[0] >= 0.0f && br[0] <= 1.0f && + br[1] >= 0.0f && br[1] <= 1.0f, + "Somehow generated invalid texture coordinates"); + + // If xwrap is false, the texture will be sampled from tl[0] + // .. br[0]. If xwrap is true, then it will be split into tl[0] + // .. 1.0, and 0.0 .. br[0]. Same for the Y axis. The + // destination rectangle is also split appropriately, according + // to the calculated xmid/ymid values. + + // There isn't a 1:1 mapping between tex coords and destination coords; + // when computing midpoints, we have to take that into account. We + // need to map the texture coords, which are (in the wrap case): + // |tl->1| and |0->br| to the |0->1| range of the vertex coords. So + // we have the length (1-tl)+(br) that needs to map into 0->1. + // These are only valid if there is wrap involved, they won't be used + // otherwise. + GLfloat xlen = (1.0f - tl[0]) + br[0]; + GLfloat ylen = (1.0f - tl[1]) + br[1]; + + NS_ASSERTION(!xwrap || xlen > 0.0f, "xlen isn't > 0, what's going on?"); + NS_ASSERTION(!ywrap || ylen > 0.0f, "ylen isn't > 0, what's going on?"); + NS_ASSERTION(aTexCoordRect.width <= aTexSize.width && + aTexCoordRect.height <= aTexSize.height, "tex coord rect would cause tiling!"); + + if (!xwrap && !ywrap) { + aRects.addRect(0.0f, 0.0f, 1.0f, 1.0f, + tl[0], tl[1], br[0], br[1]); + } else if (!xwrap && ywrap) { + GLfloat ymid = (1.0f - tl[1]) / ylen; + aRects.addRect(0.0f, 0.0f, + 1.0f, ymid, + tl[0], tl[1], + br[0], 1.0f); + aRects.addRect(0.0f, ymid, + 1.0f, 1.0f, + tl[0], 0.0f, + br[0], br[1]); + } else if (xwrap && !ywrap) { + GLfloat xmid = (1.0f - tl[0]) / xlen; + aRects.addRect(0.0f, 0.0f, + xmid, 1.0f, + tl[0], tl[1], + 1.0f, br[1]); + aRects.addRect(xmid, 0.0f, + 1.0f, 1.0f, + 0.0f, tl[1], + br[0], br[1]); + } else { + GLfloat xmid = (1.0f - tl[0]) / xlen; + GLfloat ymid = (1.0f - tl[1]) / ylen; + aRects.addRect(0.0f, 0.0f, + xmid, ymid, + tl[0], tl[1], + 1.0f, 1.0f); + aRects.addRect(xmid, 0.0f, + 1.0f, ymid, + 0.0f, tl[1], + br[0], 1.0f); + aRects.addRect(0.0f, ymid, + xmid, 1.0f, + tl[0], 0.0f, + 1.0f, br[1]); + aRects.addRect(xmid, ymid, + 1.0f, 1.0f, + 0.0f, 0.0f, + br[0], br[1]); + } +} + void GLContext::UseBlitProgram() { diff --git a/gfx/thebes/GLContext.h b/gfx/thebes/GLContext.h index 7f9ae89f408a..d910993be332 100644 --- a/gfx/thebes/GLContext.h +++ b/gfx/thebes/GLContext.h @@ -697,6 +697,37 @@ public: void BlitTextureImage(TextureImage *aSrc, const nsIntRect& aSrcRect, TextureImage *aDst, const nsIntRect& aDstRect); + /** Helper for DecomposeIntoNoRepeatTriangles + */ + struct RectTriangles { + RectTriangles() : numRects(0) { } + + void addRect(GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1, + GLfloat tx0, GLfloat ty0, GLfloat tx1, GLfloat ty1); + + int numRects; + /* max is 4 rectangles, each made up of 2 triangles (3 2-coord vertices each) */ + GLfloat vertexCoords[4*3*2*2]; + GLfloat texCoords[4*3*2*2]; + }; + + /** + * Decompose drawing the possibly-wrapped aTexCoordRect rectangle + * of a texture of aTexSize into one or more rectangles (represented + * as 2 triangles) and associated tex coordinates, such that + * we don't have to use the REPEAT wrap mode. + * + * The resulting triangle vertex coordinates will be in the space of + * (0.0, 0.0) to (1.0, 1.0) -- transform the coordinates appropriately + * if you need a different space. + * + * The resulting vertex coordinates should be drawn using GL_TRIANGLES, + * and rects.numRects * 3 * 6 + */ + static void DecomposeIntoNoRepeatTriangles(const nsIntRect& aTexCoordRect, + const nsIntSize& aTexSize, + RectTriangles& aRects); + /** * Known GL extensions that can be queried by * IsExtensionSupported. The results of this are cached, and as