diff --git a/gfx/layers/Layers.h b/gfx/layers/Layers.h index ae2cffc9c868..0b73caf6daba 100644 --- a/gfx/layers/Layers.h +++ b/gfx/layers/Layers.h @@ -698,6 +698,39 @@ public: Mutated(); } + /** + * CONSTRUCTION PHASE ONLY + * + * Define a subrect of this layer that will be used as the source + * image for tiling this layer's visible region. The coordinates + * are in the un-transformed space of this layer (i.e. the visible + * region of this this layer is tiled before being transformed). + * The visible region is tiled "outwards" from the source rect; that + * is, the source rect is drawn "in place", then repeated to cover + * the layer's visible region. + * + * The interpretation of the source rect varies depending on + * underlying layer type. For ImageLayers and CanvasLayers, it + * doesn't make sense to set a source rect not fully contained by + * the bounds of their underlying images. For ThebesLayers, thebes + * content may need to be rendered to fill the source rect. For + * ColorLayers, a source rect for tiling doesn't make sense at all. + * + * If aRect is null no tiling will be performed. + * + * NB: this interface is only implemented for BasicImageLayers, and + * then only for source rects the same size as the layers' + * underlying images. + */ + void SetTileSourceRect(const nsIntRect* aRect) + { + mUseTileSourceRect = aRect != nsnull; + if (aRect) { + mTileSourceRect = *aRect; + } + Mutated(); + } + void SetIsFixedPosition(bool aFixedPosition) { mIsFixedPosition = aFixedPosition; } // These getters can be used anytime. @@ -711,6 +744,7 @@ public: virtual Layer* GetFirstChild() { return nsnull; } virtual Layer* GetLastChild() { return nsnull; } const gfx3DMatrix& GetTransform() { return mTransform; } + const nsIntRect* GetTileSourceRect() { return mUseTileSourceRect ? &mTileSourceRect : nsnull; } bool GetIsFixedPosition() { return mIsFixedPosition; } /** diff --git a/gfx/layers/basic/BasicLayers.cpp b/gfx/layers/basic/BasicLayers.cpp index b74f189dcfaf..663ace64405b 100644 --- a/gfx/layers/basic/BasicLayers.cpp +++ b/gfx/layers/basic/BasicLayers.cpp @@ -880,6 +880,7 @@ public: static void PaintContext(gfxPattern* aPattern, const nsIntRegion& aVisible, + const nsIntRect* aTileSourceRect, float aOpacity, gfxContext* aContext); @@ -939,11 +940,14 @@ BasicImageLayer::GetAndPaintCurrentImage(gfxContext* aContext, size = mScaleToSize; } - // The visible region can extend outside the image, so just draw - // within the image bounds. + // The visible region can extend outside the image. If we're not + // tiling, we don't want to draw into that area, so just draw within + // the image bounds. + const nsIntRect* tileSrcRect = GetTileSourceRect(); AutoSetOperator setOperator(aContext, GetOperator()); PaintContext(pat, - nsIntRegion(nsIntRect(0, 0, size.width, size.height)), + tileSrcRect ? GetVisibleRegion() : nsIntRegion(nsIntRect(0, 0, size.width, size.height)), + tileSrcRect, aOpacity, aContext); GetContainer()->NotifyPaintedImage(image); @@ -954,6 +958,7 @@ BasicImageLayer::GetAndPaintCurrentImage(gfxContext* aContext, /*static*/ void BasicImageLayer::PaintContext(gfxPattern* aPattern, const nsIntRegion& aVisible, + const nsIntRect* aTileSourceRect, float aOpacity, gfxContext* aContext) { @@ -973,13 +978,31 @@ BasicImageLayer::PaintContext(gfxPattern* aPattern, } } - aContext->NewPath(); - // No need to snap here; our transform has already taken care of it. - // XXX true for arbitrary regions? Don't care yet though - gfxUtils::PathFromRegion(aContext, aVisible); - aPattern->SetExtend(extend); - aContext->SetPattern(aPattern); - aContext->FillWithOpacity(aOpacity); + if (!aTileSourceRect) { + aContext->NewPath(); + // No need to snap here; our transform has already taken care of it. + // XXX true for arbitrary regions? Don't care yet though + gfxUtils::PathFromRegion(aContext, aVisible); + aPattern->SetExtend(extend); + aContext->SetPattern(aPattern); + aContext->FillWithOpacity(aOpacity); + } else { + nsRefPtr source = aPattern->GetSurface(); + NS_ABORT_IF_FALSE(source, "Expecting a surface pattern"); + gfxIntSize sourceSize = source->GetSize(); + nsIntRect sourceRect(0, 0, sourceSize.width, sourceSize.height); + NS_ABORT_IF_FALSE(sourceRect == *aTileSourceRect, + "Cowardly refusing to create a temporary surface for tiling"); + + gfxContextAutoSaveRestore saveRestore(aContext); + + aContext->NewPath(); + gfxUtils::PathFromRegion(aContext, aVisible); + + aPattern->SetExtend(gfxPattern::EXTEND_REPEAT); + aContext->SetPattern(aPattern); + aContext->FillWithOpacity(aOpacity); + } // Reset extend mode for callers that need to reuse the pattern aPattern->SetExtend(extend); @@ -2606,7 +2629,7 @@ BasicShadowableImageLayer::Paint(gfxContext* aContext) tmpCtx->SetOperator(gfxContext::OPERATOR_SOURCE); PaintContext(pat, nsIntRegion(nsIntRect(0, 0, mSize.width, mSize.height)), - 1.0, tmpCtx); + nsnull, 1.0, tmpCtx); BasicManager()->PaintedImage(BasicManager()->Hold(this), mBackBuffer); @@ -3052,11 +3075,14 @@ BasicShadowImageLayer::Paint(gfxContext* aContext) nsRefPtr pat = new gfxPattern(surface); pat->SetFilter(mFilter); - // The visible region can extend outside the image, so just draw - // within the image bounds. + // The visible region can extend outside the image. If we're not + // tiling, we don't want to draw into that area, so just draw within + // the image bounds. + const nsIntRect* tileSrcRect = GetTileSourceRect(); AutoSetOperator setOperator(aContext, GetOperator()); BasicImageLayer::PaintContext(pat, - nsIntRegion(nsIntRect(0, 0, mSize.width, mSize.height)), + tileSrcRect ? GetEffectiveVisibleRegion() : nsIntRegion(nsIntRect(0, 0, mSize.width, mSize.height)), + tileSrcRect, GetEffectiveOpacity(), aContext); } diff --git a/gfx/layers/ipc/PLayers.ipdl b/gfx/layers/ipc/PLayers.ipdl index c70c62b6508e..c3ebb61230b7 100644 --- a/gfx/layers/ipc/PLayers.ipdl +++ b/gfx/layers/ipc/PLayers.ipdl @@ -117,6 +117,8 @@ struct CommonLayerAttributes { float opacity; bool useClipRect; nsIntRect clipRect; + bool useTileSourceRect; + nsIntRect tileSourceRect; bool isFixedPosition; }; diff --git a/gfx/layers/ipc/ShadowLayers.cpp b/gfx/layers/ipc/ShadowLayers.cpp index 1a719cec49b3..53a05f286f6b 100644 --- a/gfx/layers/ipc/ShadowLayers.cpp +++ b/gfx/layers/ipc/ShadowLayers.cpp @@ -298,6 +298,9 @@ ShadowLayerForwarder::EndTransaction(InfallibleTArray* aReplies) common.clipRect() = (common.useClipRect() ? *mutant->GetClipRect() : nsIntRect()); common.isFixedPosition() = mutant->GetIsFixedPosition(); + common.useTileSourceRect() = !!mutant->GetTileSourceRect(); + common.tileSourceRect() = (common.useTileSourceRect() ? + *mutant->GetTileSourceRect() : nsIntRect()); attrs.specific() = null_t(); mutant->FillSpecificAttributes(attrs.specific()); diff --git a/gfx/layers/ipc/ShadowLayersParent.cpp b/gfx/layers/ipc/ShadowLayersParent.cpp index a014afafa101..4fea3327dd91 100644 --- a/gfx/layers/ipc/ShadowLayersParent.cpp +++ b/gfx/layers/ipc/ShadowLayersParent.cpp @@ -225,6 +225,7 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray& cset, layer->SetOpacity(common.opacity()); layer->SetClipRect(common.useClipRect() ? &common.clipRect() : NULL); layer->SetTransform(common.transform()); + layer->SetTileSourceRect(common.useTileSourceRect() ? &common.tileSourceRect() : NULL); static bool fixedPositionLayersEnabled = getenv("MOZ_ENABLE_FIXED_POSITION_LAYERS") != 0; if (fixedPositionLayersEnabled) { layer->SetIsFixedPosition(common.isFixedPosition()); diff --git a/gfx/layers/opengl/ImageLayerOGL.cpp b/gfx/layers/opengl/ImageLayerOGL.cpp index 123bcff6e292..4657373eb859 100644 --- a/gfx/layers/opengl/ImageLayerOGL.cpp +++ b/gfx/layers/opengl/ImageLayerOGL.cpp @@ -305,6 +305,7 @@ ImageLayerOGL::RenderLayer(int, return; } + data->SetTiling(mUseTileSourceRect); gl()->MakeCurrent(); unsigned int iwidth = cairoImage->mSize.width; unsigned int iheight = cairoImage->mSize.height; @@ -341,16 +342,68 @@ ImageLayerOGL::RenderLayer(int, nsIntRect rect = GetVisibleRegion().GetBounds(); + bool tileIsWholeImage = (mTileSourceRect == nsIntRect(0, 0, iwidth, iheight)) + || !mUseTileSourceRect; + bool imageIsPowerOfTwo = IsPowerOfTwo(iwidth) && + IsPowerOfTwo(iheight); + + bool canDoNPOT = ( + gl()->IsExtensionSupported(GLContext::ARB_texture_non_power_of_two) || + gl()->IsExtensionSupported(GLContext::OES_texture_npot)); + GLContext::RectTriangles triangleBuffer; + // do GL_REPEAT if we can - should be the fastest option. + // draw a single rect for the whole region, a little overdraw + // on the gpu should be faster than tesselating + // maybe we can write a shader that can also handle texture subrects + // and repeat? + if (tileIsWholeImage && (imageIsPowerOfTwo || canDoNPOT)) { + // we need to anchor the repeating texture appropriately + // otherwise it will start from the region border instead + // of the layer origin. This is the offset into the texture + // that the region border represents + float tex_offset_u = (float)(rect.x % iwidth) / iwidth; + float tex_offset_v = (float)(rect.y % iheight) / iheight; + triangleBuffer.addRect(rect.x, rect.y, + rect.x + rect.width, rect.y + rect.height, + tex_offset_u, tex_offset_v, + tex_offset_u + (float)rect.width / (float)iwidth, + tex_offset_v + (float)rect.height / (float)iheight); + } + // can't do fast path via GL_REPEAT - we have to tessellate individual rects. + else { + unsigned int twidth = mTileSourceRect.width; + unsigned int theight = mTileSourceRect.height; - float tex_offset_u = float(rect.x % iwidth) / iwidth; - float tex_offset_v = float(rect.y % iheight) / iheight; - triangleBuffer.addRect(rect.x, rect.y, - rect.x + rect.width, rect.y + rect.height, - tex_offset_u, tex_offset_v, - tex_offset_u + float(rect.width) / float(iwidth), - tex_offset_v + float(rect.height) / float(iheight); + nsIntRegion region = GetVisibleRegion(); + // image subrect in texture coordinates + float subrect_tl_u = float(mTileSourceRect.x) / float(iwidth); + float subrect_tl_v = float(mTileSourceRect.y) / float(iheight); + float subrect_br_u = float(mTileSourceRect.width + mTileSourceRect.x) / float(iwidth); + float subrect_br_v = float(mTileSourceRect.height + mTileSourceRect.y) / float(iheight); + // round rect position down to multiples of texture size + // this way we start at multiples of rect positions + rect.x = (rect.x / iwidth) * iwidth; + rect.y = (rect.y / iheight) * iheight; + // round up size to accomodate for rounding down above + rect.width = (rect.width / iwidth + 2) * iwidth; + rect.height = (rect.height / iheight + 2) * iheight; + + // tesselate the visible region with tiles of subrect size + for (int y = rect.y; y < rect.y + rect.height; y += theight) { + for (int x = rect.x; x < rect.x + rect.width; x += twidth) { + // when we already tessellate, we might as well save on overdraw here + if (!region.Intersects(nsIntRect(x, y, twidth, theight))) { + continue; + } + triangleBuffer.addRect(x, y, + x + twidth, y + theight, + subrect_tl_u, subrect_tl_v, + subrect_br_u, subrect_br_v); + } + } + } GLuint vertAttribIndex = program->AttribLocation(LayerProgram::VertexAttrib); GLuint texCoordAttribIndex = @@ -575,6 +628,25 @@ ImageLayerOGL::AllocateTexturesCairo(CairoImage *aImage) aImage->SetBackendData(LayerManager::LAYERS_OPENGL, backendData.forget()); } +void CairoOGLBackendData::SetTiling(bool aTiling) +{ + if (aTiling == mTiling) + return; + mozilla::gl::GLContext *gl = mTexture.GetGLContext(); + gl->MakeCurrent(); + gl->fActiveTexture(LOCAL_GL_TEXTURE0); + gl->fBindTexture(LOCAL_GL_TEXTURE_2D, mTexture.GetTextureID()); + mTiling = aTiling; + + if (aTiling) { + gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_REPEAT); + gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_REPEAT); + } else { + gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE); + gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE); + } +} + ShadowImageLayerOGL::ShadowImageLayerOGL(LayerManagerOGL* aManager) : ShadowImageLayer(aManager, nsnull) , LayerOGL(aManager) diff --git a/gfx/layers/opengl/ImageLayerOGL.h b/gfx/layers/opengl/ImageLayerOGL.h index d12981375ed7..682ae26e4224 100644 --- a/gfx/layers/opengl/ImageLayerOGL.h +++ b/gfx/layers/opengl/ImageLayerOGL.h @@ -172,9 +172,11 @@ struct THEBES_API PlanarYCbCrOGLBackendData : public ImageBackendData struct CairoOGLBackendData : public ImageBackendData { - CairoOGLBackendData() : mLayerProgram(gl::RGBALayerProgramType) {} + CairoOGLBackendData() : mLayerProgram(gl::RGBALayerProgramType), mTiling(false) {} + void SetTiling(bool aTiling); GLTexture mTexture; gl::ShaderProgramType mLayerProgram; + bool mTiling; }; class ShadowImageLayerOGL : public ShadowImageLayer,