Merge mozilla-central into mozilla-inbound

This commit is contained in:
Ehsan Akhgari 2012-03-14 13:40:34 -04:00
commit 2a6c344efd
91 changed files with 4690 additions and 2648 deletions

View File

@ -388,6 +388,36 @@ nsDOMWindowUtils::SetResolution(float aXResolution, float aYResolution)
: NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsDOMWindowUtils::SetIsFirstPaint(bool aIsFirstPaint)
{
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsIPresShell* presShell = GetPresShell();
if (presShell) {
presShell->SetIsFirstPaint(aIsFirstPaint);
return NS_OK;
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsDOMWindowUtils::GetIsFirstPaint(bool *aIsFirstPaint)
{
if (!IsUniversalXPConnectCapable()) {
return NS_ERROR_DOM_SECURITY_ERR;
}
nsIPresShell* presShell = GetPresShell();
if (presShell) {
*aIsFirstPaint = presShell->GetIsFirstPaint();
return NS_OK;
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsDOMWindowUtils::SendMouseEvent(const nsAString& aType,
float aX,

View File

@ -70,7 +70,7 @@ interface nsIDOMFile;
interface nsIFile;
interface nsIDOMTouch;
[scriptable, uuid(73b48170-55d5-11e1-b86c-0800200c9a66)]
[scriptable, uuid(5740d0fe-9f4e-431f-b8b3-b82f9b7ff4cf)]
interface nsIDOMWindowUtils : nsISupports {
/**
@ -189,6 +189,15 @@ interface nsIDOMWindowUtils : nsISupports {
*/
void setResolution(in float aXResolution, in float aYResolution);
/**
* Whether the next paint should be flagged as the first paint for a document.
* This gives a way to track the next paint that occurs after the flag is
* set. The flag gets cleared after the next paint.
*
* Can only be accessed with UniversalXPConnect privileges.
*/
attribute boolean isFirstPaint;
/** Synthesize a mouse event. The event types supported are:
* mousedown, mouseup, mousemove, mouseover, mouseout, contextmenu
*

View File

@ -2917,8 +2917,14 @@ void nsPluginInstanceOwner::Paint(gfxContext* aContext,
PRInt32 model = mInstance->GetANPDrawingModel();
float xResolution = mObjectFrame->PresContext()->GetRootPresContext()->PresShell()->GetXResolution();
float yResolution = mObjectFrame->PresContext()->GetRootPresContext()->PresShell()->GetYResolution();
gfxRect scaledFrameRect = aFrameRect;
scaledFrameRect.Scale(xResolution, yResolution);
if (model == kSurface_ANPDrawingModel) {
if (!AddPluginView(aFrameRect)) {
if (!AddPluginView(scaledFrameRect)) {
Invalidate();
}
return;
@ -2928,11 +2934,9 @@ void nsPluginInstanceOwner::Paint(gfxContext* aContext,
if (!mLayer)
mLayer = new AndroidMediaLayer();
// FIXME: this is gross
float zoomLevel = aFrameRect.width / (float)mPluginWindow->width;
mLayer->UpdatePosition(aFrameRect, zoomLevel);
mLayer->UpdatePosition(scaledFrameRect, xResolution);
SendSize((int)aFrameRect.width, (int)aFrameRect.height);
SendSize((int)scaledFrameRect.width, (int)scaledFrameRect.height);
return;
}

View File

@ -164,8 +164,8 @@ Telephony::Create(nsPIDOMWindow* aOwner, nsIRadioInterfaceLayer* aRIL)
nsRefPtr<Telephony> telephony = new Telephony();
telephony->mOwner = aOwner;
telephony->mScriptContext.swap(scriptContext);
telephony->BindToOwner(aOwner);
telephony->mRIL = aRIL;
telephony->mRILTelephonyCallback = new RILTelephonyCallback(telephony);
@ -328,12 +328,16 @@ Telephony::GetActive(jsval* aActive)
return NS_OK;
}
nsresult rv =
nsContentUtils::WrapNative(mScriptContext->GetNativeContext(),
mScriptContext->GetNativeGlobal(),
mActiveCall->ToISupports(), aActive);
nsresult rv;
nsIScriptContext* sc = GetContextForEventHandlers(&rv);
NS_ENSURE_SUCCESS(rv, rv);
if (sc) {
rv =
nsContentUtils::WrapNative(sc->GetNativeContext(),
sc->GetNativeGlobal(),
mActiveCall->ToISupports(), aActive);
NS_ENSURE_SUCCESS(rv, rv);
}
return NS_OK;
}
@ -342,17 +346,24 @@ Telephony::GetCalls(jsval* aCalls)
{
JSObject* calls = mCallsArray;
if (!calls) {
nsresult rv =
nsTArrayToJSArray(mScriptContext->GetNativeContext(),
mScriptContext->GetNativeGlobal(), mCalls, &calls);
nsresult rv;
nsIScriptContext* sc = GetContextForEventHandlers(&rv);
NS_ENSURE_SUCCESS(rv, rv);
if (sc) {
rv =
nsTArrayToJSArray(sc->GetNativeContext(),
sc->GetNativeGlobal(), mCalls, &calls);
NS_ENSURE_SUCCESS(rv, rv);
if (!mRooted) {
NS_HOLD_JS_OBJECTS(this, Telephony);
mRooted = true;
if (!mRooted) {
NS_HOLD_JS_OBJECTS(this, Telephony);
mRooted = true;
}
mCallsArray = calls;
} else {
NS_ENSURE_SUCCESS(rv, rv);
}
mCallsArray = calls;
}
aCalls->setObject(*calls);

View File

@ -118,18 +118,6 @@ public:
return mRIL;
}
nsPIDOMWindow*
Owner() const
{
return mOwner;
}
nsIScriptContext*
ScriptContext() const
{
return mScriptContext;
}
private:
Telephony();
~Telephony();

View File

@ -57,8 +57,8 @@ TelephonyCall::Create(Telephony* aTelephony, const nsAString& aNumber,
nsRefPtr<TelephonyCall> call = new TelephonyCall();
call->mOwner = aTelephony->Owner();
call->mScriptContext = aTelephony->ScriptContext();
call->BindToOwner(aTelephony->GetOwner());
call->mTelephony = aTelephony;
call->mNumber = aNumber;
call->mCallIndex = aCallIndex;

View File

@ -1561,7 +1561,7 @@ public class GeckoAppShell
Log.i("GeckoShell", "post to " + (mainThread ? "main " : "") + "java thread");
getMainHandler().post(new GeckoRunnableCallback());
}
public static android.hardware.Camera sCamera = null;
static native void cameraCallbackBridge(byte[] data);

View File

@ -55,8 +55,11 @@
#include "gfxCrashReporterUtils.h"
#include "gfxUtils.h"
#include "mozilla/Preferences.h"
#include "mozilla/Util.h" // for DebugOnly
using namespace mozilla::gfx;
namespace mozilla {
namespace gl {
@ -388,12 +391,15 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl)
const char *glRendererString;
if (mInitialized) {
// The order of these strings must match up with the order of the enum
// defined in GLContext.h for vendor IDs
glVendorString = (const char *)fGetString(LOCAL_GL_VENDOR);
const char *vendorMatchStrings[VendorOther] = {
"Intel",
"NVIDIA",
"ATI",
"Qualcomm"
"Qualcomm",
"Imagination"
};
mVendor = VendorOther;
for (int i = 0; i < VendorOther; ++i) {
@ -403,9 +409,13 @@ GLContext::InitWithPrefix(const char *prefix, bool trygl)
}
}
// The order of these strings must match up with the order of the enum
// defined in GLContext.h for renderer IDs
glRendererString = (const char *)fGetString(LOCAL_GL_RENDERER);
const char *rendererMatchStrings[RendererOther] = {
"Adreno 200"
"Adreno 200",
"Adreno 205",
"PowerVR SGX 540"
};
mRenderer = RendererOther;
for (int i = 0; i < RendererOther; ++i) {
@ -595,19 +605,99 @@ GLContext::InitExtensions()
#endif
}
// Take texture data in a given buffer and copy it into a larger buffer,
// padding out the edge pixels for filtering if necessary
static void
CopyAndPadTextureData(const GLvoid* srcBuffer,
GLvoid* dstBuffer,
GLsizei srcWidth, GLsizei srcHeight,
GLsizei dstWidth, GLsizei dstHeight,
GLsizei stride, GLint pixelsize)
{
unsigned char *rowDest = static_cast<unsigned char*>(dstBuffer);
const unsigned char *source = static_cast<const unsigned char*>(srcBuffer);
for (GLsizei h = 0; h < srcHeight; ++h) {
memcpy(rowDest, source, srcWidth * pixelsize);
rowDest += dstWidth * pixelsize;
source += stride;
}
GLsizei padHeight = srcHeight;
// Pad out an extra row of pixels so that edge filtering doesn't use garbage data
if (dstHeight > srcHeight) {
memcpy(rowDest, source - stride, srcWidth * pixelsize);
padHeight++;
}
// Pad out an extra column of pixels
if (dstWidth > srcWidth) {
rowDest = static_cast<unsigned char*>(dstBuffer) + srcWidth * pixelsize;
for (GLsizei h = 0; h < padHeight; ++h) {
memcpy(rowDest, rowDest - pixelsize, pixelsize);
rowDest += dstWidth * pixelsize;
}
}
}
bool
GLContext::IsExtensionSupported(const char *extension)
{
return ListHasExtension(fGetString(LOCAL_GL_EXTENSIONS), extension);
}
// In both of these cases (for the Adreno at least) it is impossible
// to determine good or bad driver versions for POT texture uploads,
// so blacklist them all. Newer drivers use a different rendering
// string in the form "Adreno (TM) 200" and the drivers we've seen so
// far work fine with NPOT textures, so don't blacklist those until we
// have evidence of any problems with them.
bool
GLContext::CanUploadSubTextures()
{
// There are certain GPUs that we don't want to use glTexSubImage2D on
// because that function can be very slow and/or buggy
return (Renderer() != RendererAdreno200 &&
Renderer() != RendererAdreno205);
}
return !(Renderer() == RendererAdreno200);
bool
GLContext::CanUploadNonPowerOfTwo()
{
static bool sPowerOfTwoForced;
static bool sPowerOfTwoPrefCached = false;
if (!sPowerOfTwoPrefCached) {
sPowerOfTwoPrefCached = true;
mozilla::Preferences::AddBoolVarCache(&sPowerOfTwoForced,
"gfx.textures.poweroftwo.force-enabled");
}
// Some GPUs driver crash when uploading non power of two 565 textures.
return sPowerOfTwoForced ? false : (Renderer() != RendererAdreno200 &&
Renderer() != RendererAdreno205);
}
bool
GLContext::WantsSmallTiles()
{
#ifdef MOZ_WIDGET_ANDROID
// We must use small tiles for good performance if we can't use
// glTexSubImage2D() for some reason.
if (!CanUploadSubTextures())
return true;
// We can't use small tiles on the SGX 540, because of races in texture upload.
if (Renderer() == RendererSGX540)
return false;
// Don't use small tiles otherwise. (If we implement incremental texture upload,
// then we will want to revisit this.)
return false;
#else
return false;
#endif
}
// Common code for checking for both GL extensions and GLX extensions.
@ -677,9 +767,6 @@ void GLContext::ApplyFilterToBoundTexture(gfxPattern::GraphicsFilter aFilter)
fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_NEAREST);
fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_NEAREST);
} else {
if (aFilter != gfxPattern::FILTER_GOOD) {
NS_WARNING("Unsupported filter type!");
}
fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
}
@ -862,7 +949,7 @@ TiledTextureImage::TiledTextureImage(GLContext* aGL,
, mUseNearestFilter(aUseNearestFilter)
, mTextureState(Created)
{
mTileSize = mGL->GetMaxTextureSize();
mTileSize = mGL->WantsSmallTiles() ? 256 : mGL->GetMaxTextureSize();
if (aSize != nsIntSize(0,0)) {
Resize(aSize);
}
@ -1971,16 +2058,23 @@ GLContext::BlitTextureImage(TextureImage *aSrc, const nsIntRect& aSrcRect,
PushViewportRect(nsIntRect(0, 0, dstSize.width, dstSize.height));
RectTriangles rects;
nsIntSize realTexSize = srcSize;
if (!CanUploadNonPowerOfTwo()) {
realTexSize = nsIntSize(NextPowerOfTwo(srcSize.width),
NextPowerOfTwo(srcSize.height));
}
if (aSrc->GetWrapMode() == LOCAL_GL_REPEAT) {
rects.addRect(/* dest rectangle */
dx0, dy0, dx1, dy1,
/* tex coords */
srcSubRect.x / float(srcSize.width),
srcSubRect.y / float(srcSize.height),
srcSubRect.XMost() / float(srcSize.width),
srcSubRect.YMost() / float(srcSize.height));
srcSubRect.x / float(realTexSize.width),
srcSubRect.y / float(realTexSize.height),
srcSubRect.XMost() / float(realTexSize.width),
srcSubRect.YMost() / float(realTexSize.height));
} else {
DecomposeIntoNoRepeatTriangles(srcSubRect, srcSize, rects);
DecomposeIntoNoRepeatTriangles(srcSubRect, realTexSize, rects);
// now put the coords into the d[xy]0 .. d[xy]1 coordinate space
// from the 0..1 that it comes out of decompose
@ -2225,6 +2319,41 @@ GLContext::TexImage2D(GLenum target, GLint level, GLint internalformat,
NS_ASSERTION(format == internalformat,
"format and internalformat not the same for glTexImage2D on GLES2");
if (!CanUploadNonPowerOfTwo()
&& (stride != width * pixelsize
|| !IsPowerOfTwo(width)
|| !IsPowerOfTwo(height))) {
// Pad out texture width and height to the next power of two
// as we don't support/want non power of two texture uploads
GLsizei paddedWidth = NextPowerOfTwo(width);
GLsizei paddedHeight = NextPowerOfTwo(height);
GLvoid* paddedPixels = new unsigned char[paddedWidth * paddedHeight * pixelsize];
// Pad out texture data to be in a POT sized buffer for uploading to
// a POT sized texture
CopyAndPadTextureData(pixels, paddedPixels, width, height,
paddedWidth, paddedHeight, stride, pixelsize);
fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
NS_MIN(GetAddressAlignment((ptrdiff_t)paddedPixels),
GetAddressAlignment((ptrdiff_t)paddedWidth * pixelsize)));
fTexImage2D(target,
border,
internalformat,
paddedWidth,
paddedHeight,
border,
format,
type,
paddedPixels);
fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT, 4);
delete[] static_cast<unsigned char*>(paddedPixels);
return;
}
if (stride == width * pixelsize) {
fPixelStorei(LOCAL_GL_UNPACK_ALIGNMENT,
NS_MIN(GetAddressAlignment((ptrdiff_t)pixels),
@ -2415,7 +2544,8 @@ GLContext::TexSubImage2DWithoutUnpackSubimage(GLenum target, GLint level,
void
GLContext::RectTriangles::addRect(GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1,
GLfloat tx0, GLfloat ty0, GLfloat tx1, GLfloat ty1)
GLfloat tx0, GLfloat ty0, GLfloat tx1, GLfloat ty1,
bool flip_y /* = false */)
{
vert_coord v;
v.x = x0; v.y = y0;
@ -2432,20 +2562,37 @@ GLContext::RectTriangles::addRect(GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1
v.x = x1; v.y = y1;
vertexCoords.AppendElement(v);
tex_coord t;
t.u = tx0; t.v = ty0;
texCoords.AppendElement(t);
t.u = tx1; t.v = ty0;
texCoords.AppendElement(t);
t.u = tx0; t.v = ty1;
texCoords.AppendElement(t);
if (flip_y) {
tex_coord t;
t.u = tx0; t.v = ty1;
texCoords.AppendElement(t);
t.u = tx1; t.v = ty1;
texCoords.AppendElement(t);
t.u = tx0; t.v = ty0;
texCoords.AppendElement(t);
t.u = tx0; t.v = ty1;
texCoords.AppendElement(t);
t.u = tx1; t.v = ty0;
texCoords.AppendElement(t);
t.u = tx1; t.v = ty1;
texCoords.AppendElement(t);
t.u = tx0; t.v = ty0;
texCoords.AppendElement(t);
t.u = tx1; t.v = ty1;
texCoords.AppendElement(t);
t.u = tx1; t.v = ty0;
texCoords.AppendElement(t);
} else {
tex_coord t;
t.u = tx0; t.v = ty0;
texCoords.AppendElement(t);
t.u = tx1; t.v = ty0;
texCoords.AppendElement(t);
t.u = tx0; t.v = ty1;
texCoords.AppendElement(t);
t.u = tx0; t.v = ty1;
texCoords.AppendElement(t);
t.u = tx1; t.v = ty0;
texCoords.AppendElement(t);
t.u = tx1; t.v = ty1;
texCoords.AppendElement(t);
}
}
static GLfloat
@ -2465,13 +2612,14 @@ WrapTexCoord(GLfloat v)
void
GLContext::DecomposeIntoNoRepeatTriangles(const nsIntRect& aTexCoordRect,
const nsIntSize& aTexSize,
RectTriangles& aRects)
RectTriangles& aRects,
bool aFlipY /* = false */)
{
// normalize this
nsIntRect tcr(aTexCoordRect);
while (tcr.x > aTexSize.width)
while (tcr.x >= aTexSize.width)
tcr.x -= aTexSize.width;
while (tcr.y > aTexSize.height)
while (tcr.y >= aTexSize.height)
tcr.y -= aTexSize.height;
// Compute top left and bottom right tex coordinates
@ -2531,47 +2679,58 @@ GLContext::DecomposeIntoNoRepeatTriangles(const nsIntRect& aTexCoordRect,
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]);
aRects.addRect(0.0f, 0.0f,
1.0f, 1.0f,
tl[0], tl[1],
br[0], br[1],
aFlipY);
} 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);
br[0], 1.0f,
aFlipY);
aRects.addRect(0.0f, ymid,
1.0f, 1.0f,
tl[0], 0.0f,
br[0], br[1]);
br[0], br[1],
aFlipY);
} 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]);
1.0f, br[1],
aFlipY);
aRects.addRect(xmid, 0.0f,
1.0f, 1.0f,
0.0f, tl[1],
br[0], br[1]);
br[0], br[1],
aFlipY);
} 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);
1.0f, 1.0f,
aFlipY);
aRects.addRect(xmid, 0.0f,
1.0f, ymid,
0.0f, tl[1],
br[0], 1.0f);
br[0], 1.0f,
aFlipY);
aRects.addRect(0.0f, ymid,
xmid, 1.0f,
tl[0], 0.0f,
1.0f, br[1]);
1.0f, br[1],
aFlipY);
aRects.addRect(xmid, ymid,
1.0f, 1.0f,
0.0f, 0.0f,
br[0], br[1]);
br[0], br[1],
aFlipY);
}
}

View File

@ -708,11 +708,14 @@ public:
VendorNVIDIA,
VendorATI,
VendorQualcomm,
VendorImagination,
VendorOther
};
enum {
RendererAdreno200,
RendererAdreno205,
RendererSGX540,
RendererOther
};
@ -725,6 +728,8 @@ public:
}
bool CanUploadSubTextures();
bool CanUploadNonPowerOfTwo();
bool WantsSmallTiles();
/**
* If this context wraps a double-buffered target, swap the back
@ -1419,8 +1424,12 @@ public:
struct RectTriangles {
RectTriangles() { }
// Always pass texture coordinates upright. If you want to flip the
// texture coordinates emitted to the tex_coords array, set flip_y to
// true.
void addRect(GLfloat x0, GLfloat y0, GLfloat x1, GLfloat y1,
GLfloat tx0, GLfloat ty0, GLfloat tx1, GLfloat ty1);
GLfloat tx0, GLfloat ty0, GLfloat tx1, GLfloat ty1,
bool flip_y = false);
/**
* these return a float pointer to the start of each array respectively.
@ -1451,7 +1460,8 @@ public:
* 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.
* we don't have to use the REPEAT wrap mode. If aFlipY is true, the
* texture coordinates will be specified vertically flipped.
*
* The resulting triangle vertex coordinates will be in the space of
* (0.0, 0.0) to (1.0, 1.0) -- transform the coordinates appropriately
@ -1462,7 +1472,8 @@ public:
*/
static void DecomposeIntoNoRepeatTriangles(const nsIntRect& aTexCoordRect,
const nsIntSize& aTexSize,
RectTriangles& aRects);
RectTriangles& aRects,
bool aFlipY = false);
/**
* Known GL extensions that can be queried by

View File

@ -1178,12 +1178,12 @@ public:
// still expensive.
#ifndef MOZ_WIDGET_QT
if (!mSurface) {
// We need to be able to bind the surface when we don't
// have access to a surface. We wont be drawing to the screen
// We need to be able to bind NO_SURFACE when we don't
// have access to a surface. We won't be drawing to the screen
// but we will be able to do things like resource releases.
succeeded = sEGLLibrary.fMakeCurrent(EGL_DISPLAY(),
EGL_NO_SURFACE, EGL_NO_SURFACE,
EGL_NO_CONTEXT);
mContext);
if (!succeeded && sEGLLibrary.fGetError() == LOCAL_EGL_CONTEXT_LOST) {
mContextLost = true;
NS_WARNING("EGL context has been lost.");
@ -2245,22 +2245,27 @@ CreateSurfaceForWindow(nsIWidget *aWidget, EGLConfig config)
{
EGLSurface surface;
#ifdef DEBUG
sEGLLibrary.DumpEGLConfig(config);
#endif
#ifdef MOZ_WIDGET_ANDROID
#ifdef MOZ_JAVA_COMPOSITOR
surface = mozilla::AndroidBridge::Bridge()->ProvideEGLSurface();
#elif defined(MOZ_WIDGET_ANDROID)
// On Android, we have to ask Java to make the eglCreateWindowSurface
// call for us. See GLHelpers.java for a description of why.
//
// We also only have one true "window", so we just use it directly and ignore
// what was passed in.
printf_stderr("... requesting window surface from bridge\n");
AndroidGeckoSurfaceView& sview = mozilla::AndroidBridge::Bridge()->SurfaceView();
if (sview.isNull()) {
printf_stderr("got null surface\n");
return NULL;
}
surface = mozilla::AndroidBridge::Bridge()->
CallEglCreateWindowSurface(EGL_DISPLAY(), config,
mozilla::AndroidBridge::Bridge()->SurfaceView());
printf_stderr("got surface %p\n", surface);
CallEglCreateWindowSurface(EGL_DISPLAY(), config, sview);
#else
surface = sEGLLibrary.fCreateWindowSurface(EGL_DISPLAY(), config, GET_NATIVE_WINDOW(aWidget), 0);
#endif
@ -2299,7 +2304,11 @@ GLContextProviderEGL::CreateForWindow(nsIWidget *aWidget)
return nsnull;
}
EGLSurface surface = CreateSurfaceForWindow(aWidget, config);
#ifdef MOZ_JAVA_COMPOSITOR
mozilla::AndroidBridge::Bridge()->RegisterCompositor();
#endif
EGLSurface surface = CreateSurfaceForWindow(aWidget, config);
if (!surface) {
return nsnull;

View File

@ -123,6 +123,10 @@ ifeq ($(MOZ_WIDGET_TOOLKIT),windows)
CPPSRCS += GLContextProviderEGL.cpp
endif
ifdef MOZ_JAVA_COMPOSITOR
DEFINES += -DMOZ_JAVA_COMPOSITOR
endif
include $(topsrcdir)/config/rules.mk
DEFINES := $(filter-out -DUNICODE,$(DEFINES))

View File

@ -486,6 +486,11 @@ public:
LayerUserData* GetUserData(void* aKey)
{ return mUserData.Get(aKey); }
/**
* Flag the next paint as the first for a document.
*/
virtual void SetIsFirstPaint() {}
// We always declare the following logging symbols, because it's
// extremely tricky to conditionally declare them. However, for
// ifndef MOZ_LAYERS_HAVE_LOG builds, they only have trivial

View File

@ -134,6 +134,7 @@ EXPORTS_mozilla/layers =\
ShadowLayersChild.h \
ShadowLayersParent.h \
ShadowLayersManager.h \
RenderTrace.h \
$(NULL)
CPPSRCS += \

View File

@ -47,12 +47,6 @@ namespace layers {
static int colorId = 0;
// This should be done in the printf but android's printf is buggy
const char* colors[] = {
"00", "01", "02", "03", "04", "05", "06", "07", "08", "09",
"10", "11", "12", "13", "14", "15", "16", "17", "18", "19"
};
static gfx3DMatrix GetRootTransform(Layer *aLayer) {
gfx3DMatrix layerTrans = aLayer->GetTransform().ProjectTo2D();
if (aLayer->GetParent() != NULL) {
@ -70,10 +64,13 @@ void RenderTraceLayers(Layer *aLayer, const char *aColor, const gfx3DMatrix aRoo
gfxRect rect(clipRect.x, clipRect.y, clipRect.width, clipRect.height);
trans.TransformBounds(rect);
printf_stderr("%s RENDERTRACE %u rect #%02X%s %i %i %i %i\n",
aLayer->Name(), (int)PR_IntervalNow(),
colorId, aColor,
(int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
if (strcmp(aLayer->Name(), "ContainerLayer") != 0 &&
strcmp(aLayer->Name(), "ShadowContainerLayer") != 0) {
printf_stderr("%s RENDERTRACE %u rect #%02X%s %i %i %i %i\n",
aLayer->Name(), (int)PR_IntervalNow(),
colorId, aColor,
(int)rect.x, (int)rect.y, (int)rect.width, (int)rect.height);
}
colorId++;
@ -100,6 +97,20 @@ void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor) {
RenderTraceInvalidateStart(aLayer, aColor, nsIntRect());
}
void renderTraceEventStart(const char *aComment, const char *aColor) {
printf_stderr("%s RENDERTRACE %u fillrect #%s 0 0 10 10\n",
aComment, (int)PR_IntervalNow(), aColor);
}
void renderTraceEventEnd(const char *aComment, const char *aColor) {
printf_stderr("%s RENDERTRACE %u fillrect #%s 0 0 0 0\n",
aComment, (int)PR_IntervalNow(), aColor);
}
void renderTraceEventEnd(const char *aColor) {
renderTraceEventEnd("", aColor);
}
}
}

View File

@ -43,27 +43,65 @@
// Uncomment this line to enable RENDERTRACE
//#define MOZ_RENDERTRACE
#ifdef MOZ_RENDERTRACE
#include "gfx3DMatrix.h"
#include "nsRect.h"
#ifndef GFX_RENDERTRACE_H
#define GFX_RENDERTRACE_H
#include "gfx3DMatrix.h"
#include "nsRect.h"
namespace mozilla {
namespace layers {
class Layer;
void RenderTraceLayers(Layer *aLayer, const char *aColor, gfx3DMatrix aRootTransform = gfx3DMatrix(), bool aReset = true);
void RenderTraceLayers(Layer *aLayer, const char *aColor, const gfx3DMatrix aRootTransform = gfx3DMatrix(), bool aReset = true);
void RenderTraceInvalidateStart(Layer *aLayer, const char *aColor, const nsIntRect aRect);
void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor);
void renderTraceEventStart(const char *aComment, const char *aColor);
void renderTraceEventEnd(const char *aComment, const char *aColor);
void renderTraceEventEnd(const char *aColor);
struct RenderTraceScope {
public:
RenderTraceScope(const char *aComment, const char *aColor)
: mComment(aComment)
, mColor(aColor)
{
renderTraceEventStart(mComment, mColor);
}
~RenderTraceScope() {
renderTraceEventEnd(mComment, mColor);
}
private:
const char *mComment;
const char *mColor;
};
#ifndef MOZ_RENDERTRACE
inline void RenderTraceLayers(Layer *aLayer, const char *aColor, const gfx3DMatrix aRootTransform, bool aReset)
{}
inline void RenderTraceInvalidateStart(Layer *aLayer, const char *aColor, const nsIntRect aRect)
{}
inline void RenderTraceInvalidateEnd(Layer *aLayer, const char *aColor)
{}
inline void renderTraceEventStart(const char *aComment, const char *aColor)
{}
inline void renderTraceEventEnd(const char *aComment, const char *aColor)
{}
inline void renderTraceEventEnd(const char *aColor)
{}
#endif // MOZ_RENDERTRACE
}
}
#endif //GFX_RENDERTRACE_H
#endif // MOZ_RENDERTRACE

View File

@ -690,9 +690,7 @@ BasicThebesLayer::PaintThebes(gfxContext* aContext,
nsIntRegion toDraw = IntersectWithClip(GetEffectiveVisibleRegion(), aContext);
#ifdef MOZ_RENDERTRACE
RenderTraceInvalidateStart(this, "FFFF00", toDraw.GetBounds());
#endif
if (!toDraw.IsEmpty() && !IsHidden()) {
if (!aCallback) {
@ -730,9 +728,7 @@ BasicThebesLayer::PaintThebes(gfxContext* aContext,
aContext->Restore();
}
#ifdef MOZ_RENDERTRACE
RenderTraceInvalidateEnd(this, "FFFF00");
#endif
return;
}
@ -759,9 +755,7 @@ BasicThebesLayer::PaintThebes(gfxContext* aContext,
nsIntRegion extendedDrawRegion = state.mRegionToDraw;
SetAntialiasingFlags(this, state.mContext);
#ifdef MOZ_RENDERTRACE
RenderTraceInvalidateStart(this, "FFFF00", state.mRegionToDraw.GetBounds());
#endif
PaintBuffer(state.mContext,
state.mRegionToDraw, extendedDrawRegion, state.mRegionToInvalidate,
@ -769,9 +763,7 @@ BasicThebesLayer::PaintThebes(gfxContext* aContext,
aCallback, aCallbackData);
Mutated();
#ifdef MOZ_RENDERTRACE
RenderTraceInvalidateEnd(this, "FFFF00");
#endif
} else {
// It's possible that state.mRegionToInvalidate is nonempty here,
// if we are shrinking the valid region to nothing.
@ -1628,10 +1620,8 @@ BasicLayerManager::EndTransactionInternal(DrawThebesLayerCallback aCallback,
mPhase = PHASE_DRAWING;
#endif
#ifdef MOZ_RENDERTRACE
Layer* aLayer = GetRoot();
RenderTraceLayers(aLayer, "FF00");
#endif
mTransactionIncomplete = false;
@ -1862,6 +1852,8 @@ BasicLayerManager::PaintLayer(gfxContext* aTarget,
void* aCallbackData,
ReadbackProcessor* aReadback)
{
RenderTraceScope trace("BasicLayerManager::PaintLayer", "707070");
const nsIntRect* clipRect = aLayer->GetEffectiveClipRect();
const gfx3DMatrix& effectiveTransform = aLayer->GetEffectiveTransform();
BasicContainerLayer* container = static_cast<BasicContainerLayer*>(aLayer);
@ -3423,6 +3415,7 @@ BasicShadowLayerManager::EndEmptyTransaction()
void
BasicShadowLayerManager::ForwardTransaction()
{
RenderTraceScope rendertrace("Foward Transaction", "000090");
#ifdef DEBUG
mPhase = PHASE_FORWARD;
#endif
@ -3520,5 +3513,11 @@ BasicShadowLayerManager::IsCompositingCheap()
LayerManager::IsCompositingCheap(GetParentBackendType());
}
void
BasicShadowLayerManager::SetIsFirstPaint()
{
ShadowLayerForwarder::SetIsFirstPaint();
}
}
}

View File

@ -267,6 +267,8 @@ public:
virtual bool IsCompositingCheap();
virtual bool HasShadowManagerInternal() const { return HasShadowManager(); }
virtual void SetIsFirstPaint() MOZ_OVERRIDE;
private:
/**
* Forward transaction results to the parent context.

View File

@ -43,12 +43,25 @@
#include "ShadowLayersParent.h"
#include "LayerManagerOGL.h"
#include "nsIWidget.h"
#include "nsGkAtoms.h"
#include "RenderTrace.h"
#if defined(MOZ_WIDGET_ANDROID)
#include "AndroidBridge.h"
#include <android/log.h>
#endif
using base::Thread;
namespace mozilla {
namespace layers {
CompositorParent::CompositorParent(nsIWidget* aWidget)
: mStopped(false), mWidget(aWidget)
CompositorParent::CompositorParent(nsIWidget* aWidget, base::Thread* aCompositorThread)
: mCompositorThread(aCompositorThread)
, mWidget(aWidget)
, mCurrentCompositeTask(NULL)
, mPaused(false)
, mIsFirstPaint(false)
{
MOZ_COUNT_CTOR(CompositorParent);
}
@ -71,34 +84,161 @@ CompositorParent::Destroy()
bool
CompositorParent::RecvStop()
{
mStopped = true;
mPaused = true;
Destroy();
return true;
}
void
CompositorParent::ScheduleRenderOnCompositorThread()
{
CancelableTask *renderTask = NewRunnableMethod(this, &CompositorParent::ScheduleComposition);
mCompositorThread->message_loop()->PostTask(FROM_HERE, renderTask);
}
void
CompositorParent::PauseComposition()
{
NS_ABORT_IF_FALSE(mCompositorThread->thread_id() == PlatformThread::CurrentId(),
"PauseComposition() can only be called on the compositor thread");
if (!mPaused) {
mPaused = true;
#ifdef MOZ_WIDGET_ANDROID
static_cast<LayerManagerOGL*>(mLayerManager.get())->gl()->ReleaseSurface();
#endif
}
}
void
CompositorParent::ResumeComposition()
{
NS_ABORT_IF_FALSE(mCompositorThread->thread_id() == PlatformThread::CurrentId(),
"ResumeComposition() can only be called on the compositor thread");
mPaused = false;
#ifdef MOZ_WIDGET_ANDROID
static_cast<LayerManagerOGL*>(mLayerManager.get())->gl()->RenewSurface();
#endif
}
void
CompositorParent::SchedulePauseOnCompositorThread()
{
CancelableTask *pauseTask = NewRunnableMethod(this,
&CompositorParent::PauseComposition);
mCompositorThread->message_loop()->PostTask(FROM_HERE, pauseTask);
}
void
CompositorParent::ScheduleResumeOnCompositorThread()
{
CancelableTask *resumeTask = NewRunnableMethod(this,
&CompositorParent::ResumeComposition);
mCompositorThread->message_loop()->PostTask(FROM_HERE, resumeTask);
}
void
CompositorParent::ScheduleComposition()
{
CancelableTask *composeTask = NewRunnableMethod(this, &CompositorParent::Composite);
MessageLoop::current()->PostTask(FROM_HERE, composeTask);
if (mCurrentCompositeTask) {
return;
}
#ifdef MOZ_RENDERTRACE
Layer* aLayer = mLayerManager->GetRoot();
mozilla::layers::RenderTraceLayers(aLayer, "0000");
bool initialComposition = mLastCompose.IsNull();
TimeDuration delta;
if (!initialComposition)
delta = mozilla::TimeStamp::Now() - mLastCompose;
#ifdef COMPOSITOR_PERFORMANCE_WARNING
mExpectedComposeTime = mozilla::TimeStamp::Now() + TimeDuration::FromMilliseconds(15);
#endif
mCurrentCompositeTask = NewRunnableMethod(this, &CompositorParent::Composite);
// Since 60 fps is the maximum frame rate we can acheive, scheduling composition
// events less than 15 ms apart wastes computation..
if (!initialComposition && delta.ToMilliseconds() < 15) {
#ifdef COMPOSITOR_PERFORMANCE_WARNING
mExpectedComposeTime = mozilla::TimeStamp::Now() + TimeDuration::FromMilliseconds(15 - delta.ToMilliseconds());
#endif
MessageLoop::current()->PostDelayedTask(FROM_HERE, mCurrentCompositeTask, 15 - delta.ToMilliseconds());
} else {
MessageLoop::current()->PostTask(FROM_HERE, mCurrentCompositeTask);
}
}
void
CompositorParent::SetTransformation(float aScale, nsIntPoint aScrollOffset)
{
mXScale = aScale;
mYScale = aScale;
mScrollOffset = aScrollOffset;
}
void
CompositorParent::Composite()
{
if (mStopped || !mLayerManager) {
NS_ABORT_IF_FALSE(mCompositorThread->thread_id() == PlatformThread::CurrentId(),
"Composite can only be called on the compositor thread");
mCurrentCompositeTask = NULL;
mLastCompose = mozilla::TimeStamp::Now();
if (mPaused || !mLayerManager || !mLayerManager->GetRoot()) {
return;
}
#ifdef MOZ_WIDGET_ANDROID
TransformShadowTree();
#endif
Layer* aLayer = mLayerManager->GetRoot();
mozilla::layers::RenderTraceLayers(aLayer, "0000");
mLayerManager->EndEmptyTransaction();
#ifdef COMPOSITOR_PERFORMANCE_WARNING
if (mExpectedComposeTime + TimeDuration::FromMilliseconds(15) < mozilla::TimeStamp::Now()) {
printf_stderr("Compositor: Composite took %i ms.\n",
15 + (int)(mozilla::TimeStamp::Now() - mExpectedComposeTime).ToMilliseconds());
}
#endif
}
#ifdef MOZ_WIDGET_ANDROID
// Do a breadth-first search to find the first layer in the tree that is
// scrollable.
Layer*
CompositorParent::GetPrimaryScrollableLayer()
{
Layer* root = mLayerManager->GetRoot();
nsTArray<Layer*> queue;
queue.AppendElement(root);
while (queue.Length()) {
ContainerLayer* containerLayer = queue[0]->AsContainerLayer();
queue.RemoveElementAt(0);
if (!containerLayer) {
continue;
}
const FrameMetrics& frameMetrics = containerLayer->GetFrameMetrics();
if (frameMetrics.IsScrollable()) {
return containerLayer;
}
Layer* child = containerLayer->GetFirstChild();
while (child) {
queue.AppendElement(child);
child = child->GetNextSibling();
}
}
return root;
}
#endif
// Go down shadow layer tree, setting properties to match their non-shadow
// counterparts.
static void
@ -117,8 +257,73 @@ SetShadowProperties(Layer* aLayer)
}
void
CompositorParent::ShadowLayersUpdated()
CompositorParent::TransformShadowTree()
{
#ifdef MOZ_WIDGET_ANDROID
Layer* layer = GetPrimaryScrollableLayer();
ShadowLayer* shadow = layer->AsShadowLayer();
ContainerLayer* container = layer->AsContainerLayer();
const FrameMetrics* metrics = &container->GetFrameMetrics();
const gfx3DMatrix& rootTransform = mLayerManager->GetRoot()->GetTransform();
const gfx3DMatrix& currentTransform = layer->GetTransform();
float rootScaleX = rootTransform.GetXScale();
float rootScaleY = rootTransform.GetYScale();
if (mIsFirstPaint && metrics) {
nsIntPoint scrollOffset = metrics->mViewportScrollOffset;
mContentSize = metrics->mContentSize;
mozilla::AndroidBridge::Bridge()->SetFirstPaintViewport(scrollOffset.x, scrollOffset.y,
1/rootScaleX, mContentSize.width,
mContentSize.height);
mIsFirstPaint = false;
} else if (metrics && (metrics->mContentSize != mContentSize)) {
mContentSize = metrics->mContentSize;
mozilla::AndroidBridge::Bridge()->SetPageSize(1/rootScaleX, mContentSize.width,
mContentSize.height);
}
// We request the view transform from Java after sending the above notifications,
// so that Java can take these into account in its response.
RequestViewTransform();
// Handle transformations for asynchronous panning and zooming. We determine the
// zoom used by Gecko from the transformation set on the root layer, and we
// determine the scroll offset used by Gecko from the frame metrics of the
// primary scrollable layer. We compare this to the desired zoom and scroll
// offset in the view transform we obtained from Java in order to compute the
// transformation we need to apply.
if (metrics && metrics->IsScrollable()) {
float tempScaleDiffX = rootScaleX * mXScale;
float tempScaleDiffY = rootScaleY * mYScale;
nsIntPoint metricsScrollOffset = metrics->mViewportScrollOffset;
nsIntPoint scrollCompensation(
(mScrollOffset.x / tempScaleDiffX - metricsScrollOffset.x) * mXScale,
(mScrollOffset.y / tempScaleDiffY - metricsScrollOffset.y) * mYScale);
ViewTransform treeTransform(-scrollCompensation, mXScale, mYScale);
shadow->SetShadowTransform(gfx3DMatrix(treeTransform) * currentTransform);
} else {
ViewTransform treeTransform(nsIntPoint(0,0), mXScale, mYScale);
shadow->SetShadowTransform(gfx3DMatrix(treeTransform) * currentTransform);
}
#endif
}
#ifdef MOZ_WIDGET_ANDROID
void
CompositorParent::RequestViewTransform()
{
mozilla::AndroidBridge::Bridge()->GetViewTransform(mScrollOffset, mXScale, mYScale);
}
#endif
void
CompositorParent::ShadowLayersUpdated(bool isFirstPaint)
{
mIsFirstPaint = mIsFirstPaint || isFirstPaint;
const nsTArray<PLayersParent*>& shadowParents = ManagedPLayersParent();
NS_ABORT_IF_FALSE(shadowParents.Length() <= 1,
"can only support at most 1 ShadowLayersParent");

View File

@ -41,43 +41,121 @@
#ifndef mozilla_layers_CompositorParent_h
#define mozilla_layers_CompositorParent_h
// Enable this pref to turn on compositor performance warning.
// This will print warnings if the compositor isn't meeting
// its responsiveness objectives:
// 1) Compose a frame within 15ms of receiving a ScheduleCompositeCall
// 2) Unless a frame was composited within the throttle threshold in
// which the deadline will be 15ms + throttle threshold
#define COMPOSITOR_PERFORMANCE_WARNING
#include "mozilla/layers/PCompositorParent.h"
#include "mozilla/layers/PLayersParent.h"
#include "base/thread.h"
#include "ShadowLayersManager.h"
class nsIWidget;
namespace base {
class Thread;
}
namespace mozilla {
namespace layers {
class LayerManager;
// Represents (affine) transforms that are calculated from a content view.
struct ViewTransform {
ViewTransform(nsIntPoint aTranslation = nsIntPoint(0, 0), float aXScale = 1, float aYScale = 1)
: mTranslation(aTranslation)
, mXScale(aXScale)
, mYScale(aYScale)
{}
operator gfx3DMatrix() const
{
return
gfx3DMatrix::ScalingMatrix(mXScale, mYScale, 1) *
gfx3DMatrix::Translation(mTranslation.x, mTranslation.y, 0);
}
nsIntPoint mTranslation;
float mXScale;
float mYScale;
};
class CompositorParent : public PCompositorParent,
public ShadowLayersManager
{
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(CompositorParent)
public:
CompositorParent(nsIWidget* aWidget);
CompositorParent(nsIWidget* aWidget, base::Thread* aCompositorThread);
virtual ~CompositorParent();
virtual bool RecvStop() MOZ_OVERRIDE;
virtual void ShadowLayersUpdated() MOZ_OVERRIDE;
virtual void ShadowLayersUpdated(bool isFirstPaint) MOZ_OVERRIDE;
void Destroy();
LayerManager* GetLayerManager() { return mLayerManager; }
void SetTransformation(float aScale, nsIntPoint aScrollOffset);
void AsyncRender();
// Can be called from any thread
void ScheduleRenderOnCompositorThread();
void SchedulePauseOnCompositorThread();
void ScheduleResumeOnCompositorThread();
protected:
virtual PLayersParent* AllocPLayers(const LayersBackend &backendType);
virtual bool DeallocPLayers(PLayersParent* aLayers);
private:
void ScheduleComposition();
void PauseComposition();
void ResumeComposition();
void Composite();
void ScheduleComposition();
void TransformShadowTree();
// Platform specific functions
#ifdef MOZ_WIDGET_ANDROID
/**
* Asks Java for the viewport position and updates the world transform
* accordingly.
*/
void RequestViewTransform();
/**
* Does a breadth-first search to find the first layer in the tree with a
* displayport set.
*/
Layer* GetPrimaryScrollableLayer();
#endif
nsRefPtr<LayerManager> mLayerManager;
bool mStopped;
base::Thread* mCompositorThread;
nsIWidget* mWidget;
CancelableTask *mCurrentCompositeTask;
TimeStamp mLastCompose;
#ifdef COMPOSITOR_PERFORMANCE_WARNING
TimeStamp mExpectedComposeTime;
#endif
bool mPaused;
float mXScale;
float mYScale;
nsIntPoint mScrollOffset;
nsIntSize mContentSize;
// When this flag is set, the next composition will be the first for a
// particular document (i.e. the document displayed on the screen will change).
// This happens when loading a new page or switching tabs. We notify the
// front-end (e.g. Java on Android) about this so that it take the new page
// size and zoom into account when providing us with the next view transform.
bool mIsFirstPaint;
DISALLOW_EVIL_CONSTRUCTORS(CompositorParent);
};

View File

@ -232,7 +232,9 @@ sync protocol PLayers {
parent:
async PLayer();
sync Update(Edit[] cset)
// The isFirstPaint flag can be used to indicate that this is the first update
// for a particular document.
sync Update(Edit[] cset, bool isFirstPaint)
returns (EditReply[] reply);
async __delete__();

View File

@ -50,6 +50,7 @@
#include "ShadowLayers.h"
#include "ShadowLayerChild.h"
#include "gfxipc/ShadowLayerUtils.h"
#include "RenderTrace.h"
using namespace mozilla::ipc;
@ -127,6 +128,7 @@ struct AutoTxnEnd {
ShadowLayerForwarder::ShadowLayerForwarder()
: mShadowManager(NULL)
, mParentBackend(LayerManager::LAYERS_NONE)
, mIsFirstPaint(false)
{
mTxn = new Transaction();
}
@ -256,6 +258,7 @@ ShadowLayerForwarder::PaintedCanvas(ShadowableLayer* aCanvas,
bool
ShadowLayerForwarder::EndTransaction(InfallibleTArray<EditReply>* aReplies)
{
RenderTraceScope rendertrace("Foward Transaction", "000091");
NS_ABORT_IF_FALSE(HasShadowManager(), "no manager to forward to");
NS_ABORT_IF_FALSE(!mTxn->Finished(), "forgot BeginTransaction?");
@ -278,6 +281,7 @@ ShadowLayerForwarder::EndTransaction(InfallibleTArray<EditReply>* aReplies)
// before we add paint ops. This allows layers to record the
// attribute changes before new pixels arrive, which can be useful
// for setting up back/front buffers.
RenderTraceScope rendertrace2("Foward Transaction", "000092");
for (ShadowableLayerSet::const_iterator it = mTxn->mMutants.begin();
it != mTxn->mMutants.end(); ++it) {
ShadowableLayer* shadow = *it;
@ -321,11 +325,13 @@ ShadowLayerForwarder::EndTransaction(InfallibleTArray<EditReply>* aReplies)
PlatformSyncBeforeUpdate();
MOZ_LAYERS_LOG(("[LayersForwarder] sending transaction..."));
if (!mShadowManager->SendUpdate(cset, aReplies)) {
RenderTraceScope rendertrace3("Foward Transaction", "000093");
if (!mShadowManager->SendUpdate(cset, mIsFirstPaint, aReplies)) {
MOZ_LAYERS_LOG(("[LayersForwarder] WARNING: sending transaction failed!"));
return false;
}
mIsFirstPaint = false;
MOZ_LAYERS_LOG(("[LayersForwarder] ... done"));
return true;
}

View File

@ -308,6 +308,11 @@ public:
*/
bool ShouldDoubleBuffer() { return GetParentBackendType() == LayerManager::LAYERS_BASIC; }
/**
* Flag the next paint as the first for a document.
*/
void SetIsFirstPaint() { mIsFirstPaint = true; }
protected:
ShadowLayerForwarder();
@ -332,6 +337,8 @@ private:
Transaction* mTxn;
LayersBackend mParentBackend;
bool mIsFirstPaint;
};

View File

@ -54,7 +54,7 @@ class ShadowLayersManager
{
public:
virtual void ShadowLayersUpdated() = 0;
virtual void ShadowLayersUpdated(bool isFirstPaint) = 0;
};
} // layers

View File

@ -148,8 +148,13 @@ ShadowLayersParent::Destroy()
bool
ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
const bool& isFirstPaint,
InfallibleTArray<EditReply>* reply)
{
#ifdef COMPOSITOR_PERFORMANCE_WARNING
TimeStamp updateStart = TimeStamp::Now();
#endif
MOZ_LAYERS_LOG(("[ParentSide] received txn with %d edits", cset.Length()));
if (mDestroyed || layer_manager()->IsDestroyed()) {
@ -319,9 +324,7 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
static_cast<ShadowThebesLayer*>(shadow->AsLayer());
const ThebesBuffer& newFront = op.newFrontBuffer();
#ifdef MOZ_RENDERTRACE
RenderTraceInvalidateStart(thebes, "FF00FF", op.updatedRegion().GetBounds());
#endif
OptionalThebesBuffer newBack;
nsIntRegion newValidRegion;
@ -336,9 +339,7 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
newBack, newValidRegion,
readonlyFront, frontUpdatedRegion));
#ifdef MOZ_RENDERTRACE
RenderTraceInvalidateEnd(thebes, "FF00FF");
#endif
break;
}
case Edit::TOpPaintCanvas: {
@ -349,9 +350,7 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
ShadowCanvasLayer* canvas =
static_cast<ShadowCanvasLayer*>(shadow->AsLayer());
#ifdef MOZ_RENDERTRACE
RenderTraceInvalidateStart(canvas, "FF00FF", canvas->GetVisibleRegion().GetBounds());
#endif
canvas->SetAllocator(this);
CanvasSurface newBack;
@ -360,9 +359,7 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
replyv.push_back(OpBufferSwap(shadow, NULL,
newBack));
#ifdef MOZ_RENDERTRACE
RenderTraceInvalidateEnd(canvas, "FF00FF");
#endif
break;
}
case Edit::TOpPaintImage: {
@ -373,9 +370,7 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
ShadowImageLayer* image =
static_cast<ShadowImageLayer*>(shadow->AsLayer());
#ifdef MOZ_RENDERTRACE
RenderTraceInvalidateStart(image, "FF00FF", image->GetVisibleRegion().GetBounds());
#endif
image->SetAllocator(this);
SharedImage newBack;
@ -383,9 +378,7 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
replyv.push_back(OpImageSwap(shadow, NULL,
newBack));
#ifdef MOZ_RENDERTRACE
RenderTraceInvalidateEnd(image, "FF00FF");
#endif
break;
}
@ -406,7 +399,12 @@ ShadowLayersParent::RecvUpdate(const InfallibleTArray<Edit>& cset,
// other's buffer contents.
ShadowLayerManager::PlatformSyncBeforeReplyUpdate();
mShadowLayersManager->ShadowLayersUpdated();
mShadowLayersManager->ShadowLayersUpdated(isFirstPaint);
#ifdef COMPOSITOR_PERFORMANCE_WARNING
printf_stderr("Compositor: Layers update took %i ms (blocking gecko).\n",
(int)(mozilla::TimeStamp::Now() - updateStart).ToMilliseconds());
#endif
return true;
}

View File

@ -78,6 +78,7 @@ public:
protected:
NS_OVERRIDE virtual bool RecvUpdate(const EditArray& cset,
const bool& isFirstPaint,
EditReplyArray* reply);
NS_OVERRIDE virtual PLayerParent* AllocPLayer();

View File

@ -283,7 +283,11 @@ CanvasLayerOGL::RenderLayer(int aPreviousDestination,
program->SetRenderOffset(aOffset);
program->SetTextureUnit(0);
mOGLManager->BindAndDrawQuad(program, mNeedsYFlip ? true : false);
if (gl()->CanUploadNonPowerOfTwo()) {
mOGLManager->BindAndDrawQuad(program, mNeedsYFlip ? true : false);
} else {
mOGLManager->BindAndDrawQuadWithTextureRect(program, drawRect, drawRect.Size());
}
#if defined(MOZ_WIDGET_GTK2) && !defined(MOZ_PLATFORM_MAEMO)
if (mPixmap && !mDelayedUpdates) {
@ -415,11 +419,29 @@ ShadowCanvasLayerOGL::RenderLayer(int aPreviousFrameBuffer,
program->SetTextureUnit(0);
mTexImage->BeginTileIteration();
do {
TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
program->SetLayerQuadRect(mTexImage->GetTileRect());
mOGLManager->BindAndDrawQuad(program, mNeedsYFlip); // FIXME flip order of tiles?
} while (mTexImage->NextTile());
if (gl()->CanUploadNonPowerOfTwo()) {
do {
TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
program->SetLayerQuadRect(mTexImage->GetTileRect());
mOGLManager->BindAndDrawQuad(program, mNeedsYFlip); // FIXME flip order of tiles?
} while (mTexImage->NextTile());
} else {
do {
TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
program->SetLayerQuadRect(mTexImage->GetTileRect());
// We can't use BindAndDrawQuad because that always uploads the whole texture from 0.0f -> 1.0f
// in x and y. We use BindAndDrawQuadWithTextureRect to actually draw a subrect of the texture
// We need to reset the origin to 0,0 from the tile rect because the tile originates at 0,0 in the
// actual texture, even though its origin in the composed (tiled) texture is not 0,0
// FIXME: we need to handle mNeedsYFlip, Bug #728625
mOGLManager->BindAndDrawQuadWithTextureRect(program,
nsIntRect(0, 0, mTexImage->GetTileRect().width,
mTexImage->GetTileRect().height),
mTexImage->GetTileRect().Size(),
mTexImage->GetWrapMode(),
mNeedsYFlip);
} while (mTexImage->NextTile());
}
}
void

View File

@ -40,6 +40,7 @@
#include "ImageLayerOGL.h"
#include "gfxImageSurface.h"
#include "gfxUtils.h"
#include "yuv_convert.h"
#include "GLContextProvider.h"
#if defined(MOZ_WIDGET_GTK2) && !defined(MOZ_PLATFORM_MAEMO)
@ -47,6 +48,7 @@
# include "mozilla/X11Util.h"
#endif
using namespace mozilla::gfx;
using namespace mozilla::gl;
namespace mozilla {
@ -342,8 +344,9 @@ ImageLayerOGL::RenderLayer(int,
bool tileIsWholeImage = (mTileSourceRect == nsIntRect(0, 0, iwidth, iheight))
|| !mUseTileSourceRect;
bool imageIsPowerOfTwo = ((iwidth & (iwidth - 1)) == 0 &&
(iheight & (iheight - 1)) == 0);
bool imageIsPowerOfTwo = IsPowerOfTwo(iwidth) &&
IsPowerOfTwo(iheight);
bool canDoNPOT = (
gl()->IsExtensionSupported(GLContext::ARB_texture_non_power_of_two) ||
gl()->IsExtensionSupported(GLContext::OES_texture_npot));
@ -787,11 +790,26 @@ ShadowImageLayerOGL::RenderLayer(int aPreviousFrameBuffer,
mTexImage->SetFilter(mFilter);
mTexImage->BeginTileIteration();
do {
TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
colorProgram->SetLayerQuadRect(mTexImage->GetTileRect());
mOGLManager->BindAndDrawQuad(colorProgram);
} while (mTexImage->NextTile());
if (gl()->CanUploadNonPowerOfTwo()) {
do {
TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
colorProgram->SetLayerQuadRect(mTexImage->GetTileRect());
mOGLManager->BindAndDrawQuad(colorProgram);
} while (mTexImage->NextTile());
} else {
do {
TextureImage::ScopedBindTextureAndApplyFilter texBind(mTexImage, LOCAL_GL_TEXTURE0);
colorProgram->SetLayerQuadRect(mTexImage->GetTileRect());
// We can't use BindAndDrawQuad because that always uploads the whole texture from 0.0f -> 1.0f
// in x and y. We use BindAndDrawQuadWithTextureRect to actually draw a subrect of the texture
mOGLManager->BindAndDrawQuadWithTextureRect(colorProgram,
nsIntRect(0, 0, mTexImage->GetTileRect().width,
mTexImage->GetTileRect().height),
mTexImage->GetTileRect().Size());
} while (mTexImage->NextTile());
}
} else {
gl()->fActiveTexture(LOCAL_GL_TEXTURE0);
gl()->fBindTexture(LOCAL_GL_TEXTURE_2D, mYUVTexture[0].GetTextureID());

View File

@ -68,9 +68,14 @@
#include "sampler.h"
#ifdef MOZ_WIDGET_ANDROID
#include <android/log.h>
#endif
namespace mozilla {
namespace layers {
using namespace mozilla::gfx;
using namespace mozilla::gl;
#ifdef CHECK_CURRENT_PROGRAM
@ -677,7 +682,8 @@ void
LayerManagerOGL::BindAndDrawQuadWithTextureRect(LayerProgram *aProg,
const nsIntRect& aTexCoordRect,
const nsIntSize& aTexSize,
GLenum aWrapMode)
GLenum aWrapMode /* = LOCAL_GL_REPEAT */,
bool aFlipped /* = false */)
{
GLuint vertAttribIndex =
aProg->AttribLocation(LayerProgram::VertexAttrib);
@ -698,16 +704,24 @@ LayerManagerOGL::BindAndDrawQuadWithTextureRect(LayerProgram *aProg,
GLContext::RectTriangles rects;
nsIntSize realTexSize = aTexSize;
if (!mGLContext->CanUploadNonPowerOfTwo()) {
realTexSize = nsIntSize(NextPowerOfTwo(aTexSize.width),
NextPowerOfTwo(aTexSize.height));
}
if (aWrapMode == LOCAL_GL_REPEAT) {
rects.addRect(/* dest rectangle */
0.0f, 0.0f, 1.0f, 1.0f,
/* tex coords */
aTexCoordRect.x / GLfloat(aTexSize.width),
aTexCoordRect.y / GLfloat(aTexSize.height),
aTexCoordRect.XMost() / GLfloat(aTexSize.width),
aTexCoordRect.YMost() / GLfloat(aTexSize.height));
aTexCoordRect.x / GLfloat(realTexSize.width),
aTexCoordRect.y / GLfloat(realTexSize.height),
aTexCoordRect.XMost() / GLfloat(realTexSize.width),
aTexCoordRect.YMost() / GLfloat(realTexSize.height),
aFlipped);
} else {
GLContext::DecomposeIntoNoRepeatTriangles(aTexCoordRect, aTexSize, rects);
GLContext::DecomposeIntoNoRepeatTriangles(aTexCoordRect, realTexSize,
rects, aFlipped);
}
mGLContext->fVertexAttribPointer(vertAttribIndex, 2,
@ -788,10 +802,14 @@ LayerManagerOGL::Render()
mGLContext->fClearColor(0.0, 0.0, 0.0, 0.0);
mGLContext->fClear(LOCAL_GL_COLOR_BUFFER_BIT | LOCAL_GL_DEPTH_BUFFER_BIT);
// Allow widget to render a custom background.
mWidget->DrawWindowUnderlay(this, rect);
// Render our layers.
RootLayer()->RenderLayer(mGLContext->IsDoubleBuffered() ? 0 : mBackBufferFBO,
nsIntPoint(0, 0));
// Allow widget to render a custom foreground too.
mWidget->DrawWindowOverlay(this, rect);
#ifdef MOZ_DUMP_PAINTING

View File

@ -370,8 +370,9 @@ public:
void BindAndDrawQuadWithTextureRect(LayerProgram *aProg,
const nsIntRect& aTexCoordRect,
const nsIntSize& aTexSize,
GLenum aWrapMode = LOCAL_GL_REPEAT);
GLenum aWrapMode = LOCAL_GL_REPEAT,
bool aFlipped = false);
#ifdef MOZ_LAYERS_HAVE_LOG
virtual const char* Name() const { return "OGL"; }
@ -391,7 +392,7 @@ public:
* to a window of the given dimensions.
*/
void SetupPipeline(int aWidth, int aHeight, WorldTransforPolicy aTransformPolicy);
/**
* Setup World transform matrix.
* Transform will be ignored if it is not PreservesAxisAlignedRectangles

View File

@ -181,6 +181,13 @@ public:
*/
void Scale(float aX, float aY, float aZ);
/**
* Return the currently set scaling factors.
*/
float GetXScale() const { return _11; }
float GetYScale() const { return _22; }
float GetZScale() const { return _33; }
/**
* Rotate around the X axis..
*

View File

@ -174,5 +174,71 @@ public:
#endif
};
namespace mozilla {
namespace gfx {
/*
* Copyright 2008 The Android Open Source Project
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
// Some helper functions for power-of-two arithmetic
// from Skia
#if defined(__arm__)
#define CountLeadingZeroes(x) __builtin_clz(x)
#else
#define sub_shift(zeros, x, n) \
zeros -= n; \
x >>= n
static inline int CountLeadingZeroes(uint32_t aNumber)
{
if (aNumber == 0) {
return 32;
}
int zeros = 31;
if (aNumber & 0xFFFF0000) {
sub_shift(zeros, aNumber, 16);
}
if (aNumber & 0xFF00) {
sub_shift(zeros, aNumber, 8);
}
if (aNumber & 0xF0) {
sub_shift(zeros, aNumber, 4);
}
if (aNumber & 0xC) {
sub_shift(zeros, aNumber, 2);
}
if (aNumber & 0x2) {
sub_shift(zeros, aNumber, 1);
}
return zeros;
}
#endif
/**
* Returns true if |aNumber| is a power of two
*/
static inline bool
IsPowerOfTwo(int aNumber)
{
return (aNumber & (aNumber - 1)) == 0;
}
/**
* Returns the first integer greater than |aNumber| which is a power of two
* Undefined for |aNumber| < 0
*/
static inline int
NextPowerOfTwo(int aNumber)
{
return 1 << (32 - CountLeadingZeroes(aNumber - 1));
}
} // namespace gfx
} // namespace mozilla
#endif

View File

@ -871,6 +871,8 @@ ContainerState::CreateOrRecycleThebesLayer(nsIFrame* aActiveScrolledRoot)
matrix.Translate(gfxPoint(pixOffset.x, pixOffset.y));
layer->SetTransform(gfx3DMatrix::From2D(matrix));
// FIXME: Temporary workaround for bug 681192 and bug 724786.
#ifndef MOZ_JAVA_COMPOSITOR
// Calculate exact position of the top-left of the active scrolled root.
// This might not be 0,0 due to the snapping in ScaleToNearestPixels.
gfxPoint activeScrolledRootTopLeft = scaledOffset - matrix.GetTranslation();
@ -882,6 +884,7 @@ ContainerState::CreateOrRecycleThebesLayer(nsIFrame* aActiveScrolledRoot)
nsIntRect invalidate = layer->GetValidRegion().GetBounds();
layer->InvalidateRegion(invalidate);
}
#endif
return layer.forget();
}

View File

@ -506,6 +506,18 @@ public:
nsCOMPtr<nsIDocument> mTop;
};
class nsBeforeFirstPaintDispatcher : public nsRunnable
{
public:
nsBeforeFirstPaintDispatcher(nsIDocument* aDocument)
: mDocument(aDocument) {}
NS_IMETHOD Run();
private:
nsCOMPtr<nsIDocument> mDocument;
};
class nsDocumentShownDispatcher : public nsRunnable
{
public:
@ -2047,8 +2059,13 @@ DocumentViewerImpl::Show(void)
}
}
// Notify observers that a new page has been shown. (But not right now;
// running JS at this time is not safe.)
// Notify observers that a new page is about to be drawn. Execute this
// as soon as it is safe to run JS, which is guaranteed to be before we
// go back to the event loop and actually draw the page.
nsContentUtils::AddScriptRunner(new nsBeforeFirstPaintDispatcher(mDocument));
// Notify observers that a new page has been shown. This will get run
// from the event loop after we actually draw the page.
NS_DispatchToMainThread(new nsDocumentShownDispatcher(mDocument));
return NS_OK;
@ -4370,9 +4387,21 @@ DocumentViewerImpl::SetPrintPreviewPresentation(nsIViewManager* aViewManager,
mPresShell = aPresShell;
}
// Fires the "document-shown" event so that interested parties (right now, the
// Fires the "before-first-paint" event so that interested parties (right now, the
// mobile browser) are aware of it.
NS_IMETHODIMP
nsBeforeFirstPaintDispatcher::Run()
{
nsCOMPtr<nsIObserverService> observerService =
mozilla::services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(mDocument, "before-first-paint", NULL);
}
return NS_OK;
}
// Fires the "document-shown" event so that interested parties are aware of it.
NS_IMETHODIMP
nsDocumentShownDispatcher::Run()
{
nsCOMPtr<nsIObserverService> observerService =

View File

@ -1146,6 +1146,16 @@ public:
float GetXResolution() { return mXResolution; }
float GetYResolution() { return mYResolution; }
/**
* Set the isFirstPaint flag.
*/
void SetIsFirstPaint(bool aIsFirstPaint) { mIsFirstPaint = aIsFirstPaint; }
/**
* Get the isFirstPaint flag.
*/
bool GetIsFirstPaint() const { return mIsFirstPaint; }
/**
* Dispatch a mouse move event based on the most recent mouse position if
* this PresShell is visible. This is used when the contents of the page
@ -1261,6 +1271,8 @@ protected:
bool mIsActive;
bool mFrozen;
bool mIsFirstPaint;
bool mObservesMutationsForPrint;
bool mReflowScheduled; // If true, we have a reflow

View File

@ -823,6 +823,7 @@ PresShell::PresShell()
mSelectionFlags = nsISelectionDisplay::DISPLAY_TEXT | nsISelectionDisplay::DISPLAY_IMAGES;
mIsThemeSupportDisabled = false;
mIsActive = true;
mIsFirstPaint = false;
mFrozen = false;
#ifdef DEBUG
mPresArenaAllocCount = 0;
@ -5372,6 +5373,11 @@ PresShell::Paint(nsIView* aViewToPaint,
LayerManager* layerManager =
aWidgetToPaint->GetLayerManager(&isRetainingManager);
NS_ASSERTION(layerManager, "Must be in paint event");
if (mIsFirstPaint) {
layerManager->SetIsFirstPaint();
mIsFirstPaint = false;
}
layerManager->BeginTransaction();
if (frame && isRetainingManager) {

View File

@ -2167,16 +2167,17 @@ nsGfxScrollFrameInner::BuildDisplayList(nsDisplayListBuilder* aBuilder,
NS_ENSURE_SUCCESS(rv, rv);
// Since making new layers is expensive, only use nsDisplayScrollLayer
// if the area is scrollable.
// if the area is scrollable and there's a displayport (or we're the content
// process).
nsRect scrollRange = GetScrollRange();
ScrollbarStyles styles = GetScrollbarStylesFromFrame();
mShouldBuildLayer =
(XRE_GetProcessType() == GeckoProcessType_Content &&
(styles.mHorizontal != NS_STYLE_OVERFLOW_HIDDEN ||
styles.mVertical != NS_STYLE_OVERFLOW_HIDDEN) &&
(scrollRange.width > 0 ||
scrollRange.height > 0) &&
(!mIsRoot || !mOuter->PresContext()->IsRootContentDocument()));
(usingDisplayport ||
(XRE_GetProcessType() == GeckoProcessType_Content &&
(scrollRange.width > 0 || scrollRange.height > 0) &&
(!mIsRoot || !mOuter->PresContext()->IsRootContentDocument())));
if (ShouldBuildLayer()) {
// ScrollLayerWrapper must always be created because it initializes the

View File

@ -1644,26 +1644,14 @@ nsObjectFrame::PaintPlugin(nsDisplayListBuilder* aBuilder,
{
#if defined(MOZ_WIDGET_ANDROID)
if (mInstanceOwner) {
NPWindow *window;
mInstanceOwner->GetWindow(window);
gfxRect frameGfxRect =
PresContext()->AppUnitsToGfxUnits(aPluginRect);
gfxRect dirtyGfxRect =
PresContext()->AppUnitsToGfxUnits(aDirtyRect);
gfxContext* ctx = aRenderingContext.ThebesContext();
gfx3DMatrix matrix3d = nsLayoutUtils::GetTransformToAncestor(this, nsnull);
gfxMatrix matrix2d;
if (!matrix3d.Is2D(&matrix2d))
return;
// The matrix includes the frame's position, so we need to transform
// from 0,0 to get the correct coordinates.
frameGfxRect.MoveTo(0, 0);
mInstanceOwner->Paint(ctx, matrix2d.Transform(frameGfxRect), dirtyGfxRect);
mInstanceOwner->Paint(ctx, frameGfxRect, dirtyGfxRect);
return;
}
#endif

View File

@ -516,7 +516,7 @@ RenderFrameParent::ContentViewScaleChanged(nsContentView* aView)
}
void
RenderFrameParent::ShadowLayersUpdated()
RenderFrameParent::ShadowLayersUpdated(bool isFirstPaint)
{
mFrameLoader->SetCurrentRemoteFrame(this);

View File

@ -86,7 +86,7 @@ public:
void ContentViewScaleChanged(nsContentView* aView);
virtual void ShadowLayersUpdated() MOZ_OVERRIDE;
virtual void ShadowLayersUpdated(bool isFirstPaint) MOZ_OVERRIDE;
NS_IMETHOD BuildDisplayList(nsDisplayListBuilder* aBuilder,
nsSubDocumentFrame* aFrame,

View File

@ -601,6 +601,9 @@ pref("layers.acceleration.disabled", false);
pref("layers.acceleration.disabled", true);
#endif
pref("layers.offmainthreadcomposition.enabled", true);
pref("layers.acceleration.draw-fps", false);
pref("notification.feature.enabled", true);
// prevent tooltips from showing up

View File

@ -9,7 +9,7 @@
android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
#endif
>
<uses-sdk android:minSdkVersion="5"
<uses-sdk android:minSdkVersion="8"
android:targetSdkVersion="11"/>
#include ../sync/manifests/SyncAndroidManifest_permissions.xml.in
@ -43,6 +43,9 @@
<uses-permission android:name="android.permission.CAMERA" />
<uses-feature android:name="android.hardware.camera" android:required="false"/>
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false"/>
<!-- App requires OpenGL ES 2.0 -->
<uses-feature android:glEsVersion="0x00020000" android:required="true" />
<application android:label="@MOZ_APP_DISPLAYNAME@"
android:icon="@drawable/icon"

View File

@ -42,7 +42,7 @@ package org.mozilla.gecko;
import org.mozilla.gecko.db.BrowserDB;
import org.mozilla.gecko.gfx.FloatSize;
import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
import org.mozilla.gecko.gfx.GeckoLayerClient;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.Layer;
import org.mozilla.gecko.gfx.LayerController;
@ -51,6 +51,7 @@ import org.mozilla.gecko.gfx.PlaceholderLayerClient;
import org.mozilla.gecko.gfx.RectUtils;
import org.mozilla.gecko.gfx.SurfaceTextureLayer;
import org.mozilla.gecko.gfx.ViewportMetrics;
import org.mozilla.gecko.gfx.ImmutableViewportMetrics;
import org.mozilla.gecko.Tab.HistoryEntry;
import java.io.*;
@ -141,7 +142,7 @@ abstract public class GeckoApp
private static LayerController mLayerController;
private static PlaceholderLayerClient mPlaceholderLayerClient;
private static GeckoSoftwareLayerClient mSoftwareLayerClient;
private static GeckoLayerClient mLayerClient;
private AboutHomeContent mAboutHomeContent;
private static AbsoluteLayout mPluginContainer;
@ -535,10 +536,6 @@ abstract public class GeckoApp
}
}
public String getLastViewport() {
return mLastViewport;
}
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
if (mOwnActivityDepth > 0)
@ -562,21 +559,18 @@ abstract public class GeckoApp
}
public void run() {
if (mSoftwareLayerClient == null)
if (mLayerClient == null)
return;
synchronized (mSoftwareLayerClient) {
synchronized (mLayerClient) {
if (!Tabs.getInstance().isSelectedTab(mThumbnailTab))
return;
if (getLayerController().getLayerClient() != mSoftwareLayerClient)
return;
HistoryEntry lastHistoryEntry = mThumbnailTab.getLastHistoryEntry();
if (lastHistoryEntry == null)
return;
ViewportMetrics viewportMetrics = mSoftwareLayerClient.getGeckoViewportMetrics();
ViewportMetrics viewportMetrics = mLayerClient.getGeckoViewportMetrics();
// If we don't have viewport metrics, the screenshot won't be right so bail
if (viewportMetrics == null)
return;
@ -599,8 +593,7 @@ abstract public class GeckoApp
void getAndProcessThumbnailForTab(final Tab tab, boolean forceBigSceenshot) {
boolean isSelectedTab = Tabs.getInstance().isSelectedTab(tab);
final Bitmap bitmap = isSelectedTab ?
mSoftwareLayerClient.getBitmap() : null;
final Bitmap bitmap = isSelectedTab ? mLayerClient.getBitmap() : null;
if (bitmap != null) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
@ -615,8 +608,9 @@ abstract public class GeckoApp
}
mLastScreen = null;
int sw = forceBigSceenshot ? mSoftwareLayerClient.getWidth() : tab.getMinScreenshotWidth();
int sh = forceBigSceenshot ? mSoftwareLayerClient.getHeight(): tab.getMinScreenshotHeight();
View view = mLayerController.getView();
int sw = forceBigSceenshot ? view.getWidth() : tab.getMinScreenshotWidth();
int sh = forceBigSceenshot ? view.getHeight(): tab.getMinScreenshotHeight();
int dw = forceBigSceenshot ? sw : tab.getThumbnailWidth();
int dh = forceBigSceenshot ? sh : tab.getThumbnailHeight();
GeckoAppShell.sendEventToGecko(GeckoEvent.createScreenshotEvent(tab.getId(), sw, sh, dw, dh));
@ -887,7 +881,16 @@ abstract public class GeckoApp
final int tabId = message.getInt("tabID");
final String uri = message.getString("uri");
final String title = message.getString("title");
final String backgroundColor = message.getString("bgColor");
handleContentLoaded(tabId, uri, title);
if (getLayerController() != null) {
if (backgroundColor != null) {
getLayerController().setCheckerboardColor(backgroundColor);
} else {
// Default to black if no color is given
getLayerController().setCheckerboardColor(0);
}
}
Log.i(LOGTAG, "URI - " + uri + ", title - " + title);
} else if (event.equals("DOMTitleChanged")) {
final int tabId = message.getInt("tabID");
@ -1360,12 +1363,12 @@ abstract public class GeckoApp
if (tab == null)
return;
ViewportMetrics targetViewport = mLayerController.getViewportMetrics();
ViewportMetrics pluginViewport;
ImmutableViewportMetrics targetViewport = mLayerController.getViewportMetrics();
ImmutableViewportMetrics pluginViewport;
try {
JSONObject viewportObject = new JSONObject(metadata);
pluginViewport = new ViewportMetrics(viewportObject);
pluginViewport = new ImmutableViewportMetrics(new ViewportMetrics(viewportObject));
} catch (JSONException e) {
Log.e(LOGTAG, "Bad viewport metadata: ", e);
return;
@ -1460,7 +1463,7 @@ abstract public class GeckoApp
return;
}
PointF origin = metrics.getDisplayportOrigin();
PointF origin = metrics.getOrigin();
x = x + (int)origin.x;
y = y + (int)origin.y;
@ -1469,7 +1472,7 @@ abstract public class GeckoApp
if (layer == null)
return;
layer.update(new Point(x, y), new IntSize(w, h), metrics.getZoomFactor(), inverted, blend);
layer.update(new Rect(x, y, x + w, y + h), metrics.getZoomFactor(), inverted, blend);
layerView.addLayer(layer);
// FIXME: shouldn't be necessary, layer will request
@ -1555,7 +1558,7 @@ abstract public class GeckoApp
}
public void repositionPluginViews(Tab tab, boolean setVisible) {
ViewportMetrics targetViewport = mLayerController.getViewportMetrics();
ImmutableViewportMetrics targetViewport = mLayerController.getViewportMetrics();
if (targetViewport == null)
return;
@ -1727,10 +1730,9 @@ abstract public class GeckoApp
if (mLayerController == null) {
/*
* Create a layer client so that Gecko will have a buffer to draw into, but don't hook
* it up to the layer controller yet.
* Create a layer client, but don't hook it up to the layer controller yet.
*/
mSoftwareLayerClient = new GeckoSoftwareLayerClient(this);
mLayerClient = new GeckoLayerClient(this);
/*
* Hook a placeholder layer client up to the layer controller so that the user can pan
@ -1742,8 +1744,7 @@ abstract public class GeckoApp
* run experience, perhaps?
*/
mLayerController = new LayerController(this);
mPlaceholderLayerClient = PlaceholderLayerClient.createInstance(this);
mLayerController.setLayerClient(mPlaceholderLayerClient);
mPlaceholderLayerClient = new PlaceholderLayerClient(mLayerController, mLastViewport);
mGeckoLayout.addView(mLayerController.getView(), 0);
}
@ -2602,7 +2603,7 @@ abstract public class GeckoApp
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Tab:Add", args.toString()));
}
public GeckoSoftwareLayerClient getSoftwareLayerClient() { return mSoftwareLayerClient; }
public GeckoLayerClient getLayerClient() { return mLayerClient; }
public LayerController getLayerController() { return mLayerController; }
// accelerometer
@ -2644,7 +2645,7 @@ abstract public class GeckoApp
mPlaceholderLayerClient.destroy();
LayerController layerController = getLayerController();
layerController.setLayerClient(mSoftwareLayerClient);
layerController.setLayerClient(mLayerClient);
}
public class GeckoAppHandler extends Handler {
@ -2675,20 +2676,20 @@ class PluginLayoutParams extends AbsoluteLayout.LayoutParams
private int mOriginalY;
private int mOriginalWidth;
private int mOriginalHeight;
private ViewportMetrics mOriginalViewport;
private ImmutableViewportMetrics mOriginalViewport;
private float mLastResolution;
public PluginLayoutParams(int aX, int aY, int aWidth, int aHeight, ViewportMetrics aViewport) {
public PluginLayoutParams(int aX, int aY, int aWidth, int aHeight, ImmutableViewportMetrics aViewport) {
super(aWidth, aHeight, aX, aY);
Log.i(LOGTAG, "Creating plugin at " + aX + ", " + aY + ", " + aWidth + "x" + aHeight + ", (" + (aViewport.getZoomFactor() * 100) + "%)");
Log.i(LOGTAG, "Creating plugin at " + aX + ", " + aY + ", " + aWidth + "x" + aHeight + ", (" + (aViewport.zoomFactor * 100) + "%)");
mOriginalX = aX;
mOriginalY = aY;
mOriginalWidth = aWidth;
mOriginalHeight = aHeight;
mOriginalViewport = aViewport;
mLastResolution = aViewport.getZoomFactor();
mLastResolution = aViewport.zoomFactor;
clampToMaxSize();
}
@ -2705,15 +2706,15 @@ class PluginLayoutParams extends AbsoluteLayout.LayoutParams
}
}
public void reset(int aX, int aY, int aWidth, int aHeight, ViewportMetrics aViewport) {
PointF origin = aViewport.getDisplayportOrigin();
public void reset(int aX, int aY, int aWidth, int aHeight, ImmutableViewportMetrics aViewport) {
PointF origin = aViewport.getOrigin();
x = mOriginalX = aX + (int)origin.x;
y = mOriginalY = aY + (int)origin.y;
width = mOriginalWidth = aWidth;
height = mOriginalHeight = aHeight;
mOriginalViewport = aViewport;
mLastResolution = aViewport.getZoomFactor();
mLastResolution = aViewport.zoomFactor;
clampToMaxSize();
}
@ -2731,14 +2732,14 @@ class PluginLayoutParams extends AbsoluteLayout.LayoutParams
}
}
public void reposition(ViewportMetrics viewport) {
PointF targetOrigin = viewport.getDisplayportOrigin();
PointF originalOrigin = mOriginalViewport.getDisplayportOrigin();
public void reposition(ImmutableViewportMetrics viewport) {
PointF targetOrigin = viewport.getOrigin();
PointF originalOrigin = mOriginalViewport.getOrigin();
Point offset = new Point(Math.round(originalOrigin.x - targetOrigin.x),
Math.round(originalOrigin.y - targetOrigin.y));
reposition(offset, viewport.getZoomFactor());
reposition(offset, viewport.zoomFactor);
}
public float getLastResolution() {

View File

@ -39,7 +39,7 @@
package org.mozilla.gecko;
import org.mozilla.gecko.gfx.BitmapUtils;
import org.mozilla.gecko.gfx.GeckoSoftwareLayerClient;
import org.mozilla.gecko.gfx.GeckoLayerClient;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.LayerView;
@ -143,7 +143,7 @@ public class GeckoAppShell
// helper methods
// public static native void setSurfaceView(GeckoSurfaceView sv);
public static native void setSoftwareLayerClient(GeckoSoftwareLayerClient client);
public static native void setLayerClient(GeckoLayerClient client);
public static native void putenv(String map);
public static native void onResume();
public static native void onLowMemory();
@ -198,7 +198,9 @@ public class GeckoAppShell
public static native ByteBuffer allocateDirectBuffer(long size);
public static native void freeDirectBuffer(ByteBuffer buf);
public static native void bindWidgetTexture();
public static native void scheduleComposite();
public static native void schedulePauseComposition();
public static native void scheduleResumeComposition();
private static class GeckoMediaScannerClient implements MediaScannerConnectionClient {
private String mFile = "";
@ -415,9 +417,9 @@ public class GeckoAppShell
Log.i(LOGTAG, "post native init");
// Tell Gecko where the target byte buffer is for rendering
GeckoAppShell.setSoftwareLayerClient(GeckoApp.mAppContext.getSoftwareLayerClient());
GeckoAppShell.setLayerClient(GeckoApp.mAppContext.getLayerClient());
Log.i(LOGTAG, "setSoftwareLayerClient called");
Log.i(LOGTAG, "setLayerClient called");
// First argument is the .apk path
String combinedArgs = apkPath + " -greomni " + apkPath;
@ -446,8 +448,7 @@ public class GeckoAppShell
private static void geckoLoaded() {
final LayerController layerController = GeckoApp.mAppContext.getLayerController();
LayerView v = layerController.getView();
mInputConnection = GeckoInputConnection.create(v);
v.setInputConnectionHandler(mInputConnection);
mInputConnection = v.setInputConnectionHandler();
layerController.setOnTouchListener(new View.OnTouchListener() {
public boolean onTouch(View view, MotionEvent event) {
@ -545,8 +546,6 @@ public class GeckoAppShell
public static void enableLocation(final boolean enable) {
getMainHandler().post(new Runnable() {
public void run() {
LayerView v = GeckoApp.mAppContext.getLayerController().getView();
LocationManager lm = (LocationManager)
GeckoApp.mAppContext.getSystemService(Context.LOCATION_SERVICE);

View File

@ -38,6 +38,7 @@
package org.mozilla.gecko;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.RectUtils;
import org.mozilla.gecko.gfx.ViewportMetrics;
import android.os.*;
import android.app.*;
@ -380,12 +381,11 @@ public class GeckoEvent {
return event;
}
public static GeckoEvent createSizeChangedEvent(int w, int h, int screenw, int screenh, int tilew, int tileh) {
public static GeckoEvent createSizeChangedEvent(int w, int h, int screenw, int screenh) {
GeckoEvent event = new GeckoEvent(SIZE_CHANGED);
event.mPoints = new Point[3];
event.mPoints = new Point[2];
event.mPoints[0] = new Point(w, h);
event.mPoints[1] = new Point(screenw, screenh);
event.mPoints[2] = new Point(tilew, tileh);
return event;
}
@ -396,10 +396,17 @@ public class GeckoEvent {
return event;
}
public static GeckoEvent createViewportEvent(ViewportMetrics viewport) {
public static GeckoEvent createViewportEvent(ViewportMetrics viewport, RectF displayPort) {
GeckoEvent event = new GeckoEvent(VIEWPORT);
event.mCharacters = "Viewport:Change";
event.mCharactersExtra = viewport.toJSON();
PointF origin = viewport.getOrigin();
StringBuffer sb = new StringBuffer(256);
sb.append("{ \"x\" : ").append(origin.x)
.append(", \"y\" : ").append(origin.y)
.append(", \"zoom\" : ").append(viewport.getZoomFactor())
.append(", \"displayPort\" :").append(RectUtils.toJSON(displayPort))
.append('}');
event.mCharactersExtra = sb.toString();
return event;
}

View File

@ -117,16 +117,18 @@ FENNEC_JAVA_FILES = \
gfx/CairoImage.java \
gfx/CairoUtils.java \
gfx/CheckerboardImage.java \
gfx/FlexibleGLSurfaceView.java \
gfx/FloatSize.java \
gfx/GeckoSoftwareLayerClient.java \
gfx/GeckoLayerClient.java \
gfx/GLController.java \
gfx/GLThread.java \
gfx/ImmutableViewportMetrics.java \
gfx/InputConnectionHandler.java \
gfx/IntSize.java \
gfx/Layer.java \
gfx/LayerClient.java \
gfx/LayerController.java \
gfx/LayerRenderer.java \
gfx/LayerView.java \
gfx/MultiTileLayer.java \
gfx/NinePatchTileLayer.java \
gfx/PanningPerfAPI.java \
gfx/PlaceholderLayerClient.java \
@ -139,8 +141,9 @@ FENNEC_JAVA_FILES = \
gfx/TextureGenerator.java \
gfx/TextureReaper.java \
gfx/TileLayer.java \
gfx/ViewTransform.java \
gfx/ViewportMetrics.java \
gfx/WidgetTileLayer.java \
gfx/VirtualLayer.java \
ui/Axis.java \
ui/PanZoomController.java \
ui/SimpleScaleGestureDetector.java \

View File

@ -79,7 +79,11 @@ public class CheckerboardImage extends CairoImage {
checkerboard image that is tinted to the color. Otherwise just return a flat
image of the color. */
public void update(boolean showChecks, int color) {
mMainColor = color;
// XXX We don't handle setting the color to white (-1),
// there a bug somewhere but I'm not sure where,
// let's hardcode the color for now to black and come back and fix it.
//mMainColor = color;
mMainColor = 0;
mShowChecks = showChecks;
short mainColor16 = convertTo16Bit(mMainColor);

View File

@ -0,0 +1,223 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011-2012
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.GeckoApp;
import android.content.Context;
import android.graphics.PixelFormat;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
/*
* This class extends SurfaceView and allows dynamically switching between two modes
* of operation. In one mode, it is used like a GLSurfaceView, and has it's own GL
* thread. In the other mode, it allows external code to perform GL composition, by
* exposing the GL controller.
*
* In our case, we start off in the first mode because we are rendering the placeholder
* image. This mode is initiated by a call to createGLThread(). Once Gecko comes up,
* it invokes registerCxxCompositor() via a JNI call, which shuts down the GL thread and
* returns the GL controller. The JNI code then takes the EGL surface from the GL
* controller and allows the off-main thread compositor to deal with it directly.
*/
public class FlexibleGLSurfaceView extends SurfaceView implements SurfaceHolder.Callback {
private static final String LOGTAG = "GeckoFlexibleGLSurfaceView";
private GLSurfaceView.Renderer mRenderer;
private GLThread mGLThread; // Protected by this class's monitor.
private GLController mController;
private Listener mListener;
public FlexibleGLSurfaceView(Context context) {
super(context);
init();
}
public FlexibleGLSurfaceView(Context context, AttributeSet attributeSet) {
super(context, attributeSet);
init();
}
public void init() {
SurfaceHolder holder = getHolder();
holder.addCallback(this);
holder.setFormat(PixelFormat.RGB_565);
mController = new GLController(this);
}
public void setRenderer(GLSurfaceView.Renderer renderer) {
mRenderer = renderer;
}
public GLSurfaceView.Renderer getRenderer() {
return mRenderer;
}
public void setListener(Listener listener) {
mListener = listener;
}
public synchronized void requestRender() {
if (mGLThread != null) {
mGLThread.renderFrame();
}
if (mListener != null) {
mListener.renderRequested();
}
}
/**
* Creates a Java GL thread. After this is called, the FlexibleGLSurfaceView may be used just
* like a GLSurfaceView. It is illegal to access the controller after this has been called.
*/
public synchronized void createGLThread() {
if (mGLThread != null) {
throw new FlexibleGLSurfaceViewException("createGLThread() called with a GL thread " +
"already in place!");
}
mGLThread = new GLThread(mController);
mGLThread.start();
notifyAll();
}
/**
* Destroys the Java GL thread. Returns a Thread that completes when the Java GL thread is
* fully shut down.
*/
public synchronized Thread destroyGLThread() {
// Wait for the GL thread to be started.
while (mGLThread == null) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
Thread glThread = mGLThread;
mGLThread.shutdown();
mGLThread = null;
return glThread;
}
public synchronized void recreateSurface() {
if (mGLThread == null) {
throw new FlexibleGLSurfaceViewException("recreateSurface() called with no GL " +
"thread active!");
}
mGLThread.recreateSurface();
}
public synchronized GLController getGLController() {
if (mGLThread != null) {
throw new FlexibleGLSurfaceViewException("getGLController() called with a GL thread " +
"active; shut down the GL thread first!");
}
return mController;
}
/** Implementation of SurfaceHolder.Callback */
public synchronized void surfaceChanged(SurfaceHolder holder, int format, int width,
int height) {
mController.sizeChanged(width, height);
if (mGLThread != null) {
mGLThread.surfaceChanged(width, height);
}
if (mListener != null) {
mListener.surfaceChanged(width, height);
}
}
/** Implementation of SurfaceHolder.Callback */
public synchronized void surfaceCreated(SurfaceHolder holder) {
mController.surfaceCreated();
if (mGLThread != null) {
mGLThread.surfaceCreated();
}
}
/** Implementation of SurfaceHolder.Callback */
public synchronized void surfaceDestroyed(SurfaceHolder holder) {
mController.surfaceDestroyed();
if (mGLThread != null) {
mGLThread.surfaceDestroyed();
}
if (mListener != null) {
mListener.compositionPauseRequested();
}
}
/** This function is invoked by Gecko (compositor thread) via JNI; be careful when modifying signature. */
public static GLController registerCxxCompositor() {
try {
FlexibleGLSurfaceView flexView = (FlexibleGLSurfaceView)GeckoApp.mAppContext.getLayerController().getView();
try {
flexView.destroyGLThread().join();
} catch (InterruptedException e) {}
return flexView.getGLController();
} catch (Exception e) {
Log.e(LOGTAG, "### Exception! " + e);
return null;
}
}
public interface Listener {
void renderRequested();
void compositionPauseRequested();
void compositionResumeRequested();
void surfaceChanged(int width, int height);
}
public static class FlexibleGLSurfaceViewException extends RuntimeException {
public static final long serialVersionUID = 1L;
FlexibleGLSurfaceViewException(String e) {
super(e);
}
}
}

View File

@ -0,0 +1,283 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011-2012
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGL11;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.egl.EGLContext;
import javax.microedition.khronos.egl.EGLDisplay;
import javax.microedition.khronos.egl.EGLSurface;
import javax.microedition.khronos.opengles.GL;
import javax.microedition.khronos.opengles.GL10;
public class GLController {
private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
private static final String LOGTAG = "GeckoGLController";
private FlexibleGLSurfaceView mView;
private int mGLVersion;
private boolean mSurfaceValid;
private int mWidth, mHeight;
private EGL10 mEGL;
private EGLDisplay mEGLDisplay;
private EGLConfig mEGLConfig;
private EGLContext mEGLContext;
private EGLSurface mEGLSurface;
private GL mGL;
private static final int LOCAL_EGL_OPENGL_ES2_BIT = 4;
private static final int[] CONFIG_SPEC = {
EGL10.EGL_RED_SIZE, 5,
EGL10.EGL_GREEN_SIZE, 6,
EGL10.EGL_BLUE_SIZE, 5,
EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT,
EGL10.EGL_RENDERABLE_TYPE, LOCAL_EGL_OPENGL_ES2_BIT,
EGL10.EGL_NONE
};
public GLController(FlexibleGLSurfaceView view) {
mView = view;
mGLVersion = 2;
mSurfaceValid = false;
}
public void setGLVersion(int version) {
mGLVersion = version;
}
/** You must call this on the same thread you intend to use OpenGL on. */
public void initGLContext() {
initEGLContext();
createEGLSurface();
}
public void disposeGLContext() {
if (!mEGL.eglMakeCurrent(mEGLDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE,
EGL10.EGL_NO_CONTEXT)) {
throw new GLControllerException("EGL context could not be released!");
}
if (mEGLSurface != null) {
if (!mEGL.eglDestroySurface(mEGLDisplay, mEGLSurface)) {
throw new GLControllerException("EGL surface could not be destroyed!");
}
mEGLSurface = null;
}
if (mEGLContext == null) {
if (!mEGL.eglDestroyContext(mEGLDisplay, mEGLContext)) {
throw new GLControllerException("EGL context could not be destroyed!");
}
mGL = null;
mEGLDisplay = null;
mEGLConfig = null;
mEGLContext = null;
}
}
public GL getGL() { return mEGLContext.getGL(); }
public EGLDisplay getEGLDisplay() { return mEGLDisplay; }
public EGLConfig getEGLConfig() { return mEGLConfig; }
public EGLContext getEGLContext() { return mEGLContext; }
public EGLSurface getEGLSurface() { return mEGLSurface; }
public FlexibleGLSurfaceView getView() { return mView; }
public boolean hasSurface() {
return mEGLSurface != null;
}
public boolean swapBuffers() {
return mEGL.eglSwapBuffers(mEGLDisplay, mEGLSurface);
}
public boolean checkForLostContext() {
if (mEGL.eglGetError() != EGL11.EGL_CONTEXT_LOST) {
return false;
}
mEGLDisplay = null;
mEGLConfig = null;
mEGLContext = null;
mEGLSurface = null;
mGL = null;
return true;
}
public synchronized void waitForValidSurface() {
while (!mSurfaceValid) {
try {
wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
public synchronized int getWidth() {
return mWidth;
}
public synchronized int getHeight() {
return mHeight;
}
synchronized void surfaceCreated() {
mSurfaceValid = true;
notifyAll();
}
synchronized void surfaceDestroyed() {
mSurfaceValid = false;
notifyAll();
}
synchronized void sizeChanged(int newWidth, int newHeight) {
mWidth = newWidth;
mHeight = newHeight;
}
private void initEGL() {
mEGL = (EGL10)EGLContext.getEGL();
mEGLDisplay = mEGL.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY);
if (mEGLDisplay == EGL10.EGL_NO_DISPLAY) {
throw new GLControllerException("eglGetDisplay() failed");
}
int[] version = new int[2];
if (!mEGL.eglInitialize(mEGLDisplay, version)) {
throw new GLControllerException("eglInitialize() failed");
}
mEGLConfig = chooseConfig();
}
private void initEGLContext() {
initEGL();
int[] attribList = { EGL_CONTEXT_CLIENT_VERSION, mGLVersion, EGL10.EGL_NONE };
mEGLContext = mEGL.eglCreateContext(mEGLDisplay, mEGLConfig, EGL10.EGL_NO_CONTEXT,
attribList);
if (mEGLContext == null || mEGLContext == EGL10.EGL_NO_CONTEXT) {
throw new GLControllerException("createContext() failed");
}
}
private EGLConfig chooseConfig() {
int[] numConfigs = new int[1];
if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, null, 0, numConfigs) ||
numConfigs[0] <= 0) {
throw new GLControllerException("No available EGL configurations");
}
EGLConfig[] configs = new EGLConfig[numConfigs[0]];
if (!mEGL.eglChooseConfig(mEGLDisplay, CONFIG_SPEC, configs, numConfigs[0], numConfigs)) {
throw new GLControllerException("No EGL configuration for that specification");
}
// Select the first 565 RGB configuration.
int[] red = new int[1], green = new int[1], blue = new int[1];
for (EGLConfig config : configs) {
mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_RED_SIZE, red);
mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_GREEN_SIZE, green);
mEGL.eglGetConfigAttrib(mEGLDisplay, config, EGL10.EGL_BLUE_SIZE, blue);
if (red[0] == 5 && green[0] == 6 && blue[0] == 5) {
return config;
}
}
throw new GLControllerException("No suitable EGL configuration found");
}
private void createEGLSurface() {
SurfaceHolder surfaceHolder = mView.getHolder();
mEGLSurface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surfaceHolder, null);
if (mEGLSurface == null || mEGLSurface == EGL10.EGL_NO_SURFACE) {
throw new GLControllerException("EGL window surface could not be created!");
}
if (!mEGL.eglMakeCurrent(mEGLDisplay, mEGLSurface, mEGLSurface, mEGLContext)) {
throw new GLControllerException("EGL surface could not be made into the current " +
"surface!");
}
mGL = mEGLContext.getGL();
if (mView.getRenderer() != null) {
mView.getRenderer().onSurfaceCreated((GL10)mGL, mEGLConfig);
mView.getRenderer().onSurfaceChanged((GL10)mGL, mView.getWidth(), mView.getHeight());
}
}
/**
* Provides an EGLSurface without assuming ownership of this surface.
* This class does not keep a reference to the provided EGL surface; the
* caller assumes ownership of the surface once it is returned.
*/
private EGLSurface provideEGLSurface() {
if (mEGL == null) {
initEGL();
}
SurfaceHolder surfaceHolder = mView.getHolder();
EGLSurface surface = mEGL.eglCreateWindowSurface(mEGLDisplay, mEGLConfig, surfaceHolder, null);
if (surface == null || surface == EGL10.EGL_NO_SURFACE) {
throw new GLControllerException("EGL window surface could not be created!");
}
return surface;
}
public static class GLControllerException extends RuntimeException {
public static final long serialVersionUID = 1L;
GLControllerException(String e) {
super(e);
}
}
}

View File

@ -0,0 +1,178 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011-2012
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import android.opengl.GLSurfaceView;
import android.view.SurfaceHolder;
import javax.microedition.khronos.opengles.GL10;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
// A GL thread managed by Java. It is not necessary to use this class to use the
// FlexibleGLSurfaceView, but it can be helpful, especially if the GL rendering is to be done
// entirely in Java.
class GLThread extends Thread {
private LinkedBlockingQueue<Runnable> mQueue;
private GLController mController;
private boolean mRenderQueued;
public GLThread(GLController controller) {
mQueue = new LinkedBlockingQueue<Runnable>();
mController = controller;
}
@Override
public void run() {
while (true) {
Runnable runnable;
try {
runnable = mQueue.take();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
runnable.run();
if (runnable instanceof ShutdownMessage) {
break;
}
}
}
public void recreateSurface() {
mQueue.add(new RecreateSurfaceMessage());
}
public void renderFrame() {
// Make sure there's only one render event in the queue at a time.
synchronized (this) {
if (!mRenderQueued) {
mQueue.add(new RenderFrameMessage());
mRenderQueued = true;
}
}
}
public void shutdown() {
mQueue.add(new ShutdownMessage());
}
public void surfaceChanged(int width, int height) {
mQueue.add(new SizeChangedMessage(width, height));
}
public void surfaceCreated() {
mQueue.add(new SurfaceCreatedMessage());
}
public void surfaceDestroyed() {
mQueue.add(new SurfaceDestroyedMessage());
}
private void doRecreateSurface() {
mController.disposeGLContext();
mController.initGLContext();
}
private GLSurfaceView.Renderer getRenderer() {
return mController.getView().getRenderer();
}
private class RecreateSurfaceMessage implements Runnable {
public void run() {
doRecreateSurface();
}
}
private class RenderFrameMessage implements Runnable {
public void run() {
synchronized (GLThread.this) {
mRenderQueued = false;
}
// Bail out if the surface was lost.
if (mController.getEGLSurface() == null) {
return;
}
GLSurfaceView.Renderer renderer = getRenderer();
if (renderer != null) {
renderer.onDrawFrame((GL10)mController.getGL());
}
mController.swapBuffers();
}
}
private class ShutdownMessage implements Runnable {
public void run() {
mController.disposeGLContext();
mController = null;
}
}
private class SizeChangedMessage implements Runnable {
private int mWidth, mHeight;
public SizeChangedMessage(int width, int height) {
mWidth = width;
mHeight = height;
}
public void run() {
GLSurfaceView.Renderer renderer = getRenderer();
if (renderer != null) {
renderer.onSurfaceChanged((GL10)mController.getGL(), mWidth, mHeight);
}
}
}
private class SurfaceCreatedMessage implements Runnable {
public void run() {
if (!mController.hasSurface()) {
mController.initGLContext();
}
}
}
private class SurfaceDestroyedMessage implements Runnable {
public void run() {
mController.disposeGLContext();
}
}
}

View File

@ -0,0 +1,430 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Chris Lord <chrislord.net@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.FloatUtils;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoEventResponder;
import org.json.JSONException;
import org.json.JSONObject;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.View;
public class GeckoLayerClient implements GeckoEventResponder,
FlexibleGLSurfaceView.Listener {
private static final String LOGTAG = "GeckoLayerClient";
private static final int DEFAULT_DISPLAY_PORT_MARGIN = 300;
private LayerController mLayerController;
private LayerRenderer mLayerRenderer;
private boolean mLayerRendererInitialized;
private IntSize mScreenSize;
private IntSize mWindowSize;
private RectF mDisplayPort;
private VirtualLayer mRootLayer;
/* The viewport that Gecko is currently displaying. */
private ViewportMetrics mGeckoViewport;
private String mLastCheckerboardColor;
/* Used by robocop for testing purposes */
private DrawListener mDrawListener;
/* Used as a temporary ViewTransform by getViewTransform */
private ViewTransform mCurrentViewTransform;
public GeckoLayerClient(Context context) {
// we can fill these in with dummy values because they are always written
// to before being read
mScreenSize = new IntSize(0, 0);
mWindowSize = new IntSize(0, 0);
mDisplayPort = new RectF();
mCurrentViewTransform = new ViewTransform(0, 0, 1);
}
/** Attaches the root layer to the layer controller so that Gecko appears. */
void setLayerController(LayerController layerController) {
LayerView view = layerController.getView();
mLayerController = layerController;
mRootLayer = new VirtualLayer(new IntSize(view.getWidth(), view.getHeight()));
mLayerRenderer = new LayerRenderer(view);
GeckoAppShell.registerGeckoEventListener("Viewport:Update", this);
view.setListener(this);
view.setLayerRenderer(mLayerRenderer);
layerController.setRoot(mRootLayer);
sendResizeEventIfNecessary(true);
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public boolean beginDrawing(int width, int height, String metadata) {
try {
JSONObject viewportObject = new JSONObject(metadata);
mGeckoViewport = new ViewportMetrics(viewportObject);
} catch (JSONException e) {
Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
return false;
}
return true;
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public void endDrawing() {
synchronized (mLayerController) {
RectF position = mGeckoViewport.getViewport();
mRootLayer.setPositionAndResolution(RectUtils.round(position), mGeckoViewport.getZoomFactor());
}
Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
/* Used by robocop for testing purposes */
if (mDrawListener != null) {
mDrawListener.drawFinished();
}
}
RectF getDisplayPort() {
return mDisplayPort;
}
/* Informs Gecko that the screen size has changed. */
private void sendResizeEventIfNecessary(boolean force) {
DisplayMetrics metrics = new DisplayMetrics();
GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
View view = mLayerController.getView();
IntSize newScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
IntSize newWindowSize = new IntSize(view.getWidth(), view.getHeight());
boolean screenSizeChanged = !mScreenSize.equals(newScreenSize);
boolean windowSizeChanged = !mWindowSize.equals(newWindowSize);
if (!force && !screenSizeChanged && !windowSizeChanged) {
return;
}
mScreenSize = newScreenSize;
mWindowSize = newWindowSize;
if (screenSizeChanged) {
Log.d(LOGTAG, "Screen-size changed to " + mScreenSize);
}
if (windowSizeChanged) {
Log.d(LOGTAG, "Window-size changed to " + mWindowSize);
}
GeckoEvent event = GeckoEvent.createSizeChangedEvent(mWindowSize.width, mWindowSize.height,
mScreenSize.width, mScreenSize.height);
GeckoAppShell.sendEventToGecko(event);
}
public Bitmap getBitmap() {
return null;
}
void viewportSizeChanged() {
// here we send gecko a resize message. The code in browser.js is responsible for
// picking up on that resize event, modifying the viewport as necessary, and informing
// us of the new viewport.
sendResizeEventIfNecessary(true);
// the following call also sends gecko a message, which will be processed after the resize
// message above has updated the viewport. this message ensures that if we have just put
// focus in a text field, we scroll the content so that the text field is in view.
GeckoAppShell.viewSizeChanged();
}
private void updateDisplayPort() {
float desiredXMargins = 2 * DEFAULT_DISPLAY_PORT_MARGIN;
float desiredYMargins = 2 * DEFAULT_DISPLAY_PORT_MARGIN;
ImmutableViewportMetrics metrics = mLayerController.getViewportMetrics();
// we need to avoid having a display port that is larger than the page, or we will end up
// painting things outside the page bounds (bug 729169). we simultaneously need to make
// the display port as large as possible so that we redraw less.
// figure out how much of the desired buffer amount we can actually use on the horizontal axis
float xBufferAmount = Math.min(desiredXMargins, metrics.pageSizeWidth - metrics.getWidth());
// if we reduced the buffer amount on the horizontal axis, we should take that saved memory and
// use it on the vertical axis
float savedPixels = (desiredXMargins - xBufferAmount) * (metrics.getHeight() + desiredYMargins);
float extraYAmount = (float)Math.floor(savedPixels / (metrics.getWidth() + xBufferAmount));
float yBufferAmount = Math.min(desiredYMargins + extraYAmount, metrics.pageSizeHeight - metrics.getHeight());
// and the reverse - if we shrunk the buffer on the vertical axis we can add it to the horizontal
if (xBufferAmount == desiredXMargins && yBufferAmount < desiredYMargins) {
savedPixels = (desiredYMargins - yBufferAmount) * (metrics.getWidth() + xBufferAmount);
float extraXAmount = (float)Math.floor(savedPixels / (metrics.getHeight() + yBufferAmount));
xBufferAmount = Math.min(xBufferAmount + extraXAmount, metrics.pageSizeWidth - metrics.getWidth());
}
// and now calculate the display port margins based on how much buffer we've decided to use and
// the page bounds, ensuring we use all of the available buffer amounts on one side or the other
// on any given axis. (i.e. if we're scrolled to the top of the page, the vertical buffer is
// entirely below the visible viewport, but if we're halfway down the page, the vertical buffer
// is split).
float leftMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.viewportRectLeft);
float rightMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.pageSizeWidth - (metrics.viewportRectLeft + metrics.getWidth()));
if (leftMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
rightMargin = xBufferAmount - leftMargin;
} else if (rightMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
leftMargin = xBufferAmount - rightMargin;
} else if (!FloatUtils.fuzzyEquals(leftMargin + rightMargin, xBufferAmount)) {
float delta = xBufferAmount - leftMargin - rightMargin;
leftMargin += delta / 2;
rightMargin += delta / 2;
}
float topMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.viewportRectTop);
float bottomMargin = Math.min(DEFAULT_DISPLAY_PORT_MARGIN, metrics.pageSizeHeight - (metrics.viewportRectTop + metrics.getHeight()));
if (topMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
bottomMargin = yBufferAmount - topMargin;
} else if (bottomMargin < DEFAULT_DISPLAY_PORT_MARGIN) {
topMargin = yBufferAmount - bottomMargin;
} else if (!FloatUtils.fuzzyEquals(topMargin + bottomMargin, yBufferAmount)) {
float delta = yBufferAmount - topMargin - bottomMargin;
topMargin += delta / 2;
bottomMargin += delta / 2;
}
// note that unless the viewport size changes, or the page dimensions change (either because of
// content changes or zooming), the size of the display port should remain constant. this
// is intentional to avoid re-creating textures and all sorts of other reallocations in the
// draw and composition code.
mDisplayPort.left = metrics.viewportRectLeft - leftMargin;
mDisplayPort.top = metrics.viewportRectTop - topMargin;
mDisplayPort.right = metrics.viewportRectRight + rightMargin;
mDisplayPort.bottom = metrics.viewportRectBottom + bottomMargin;
}
private void adjustViewport() {
ViewportMetrics viewportMetrics =
new ViewportMetrics(mLayerController.getViewportMetrics());
viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
updateDisplayPort();
GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(viewportMetrics, mDisplayPort));
}
/** Implementation of GeckoEventResponder/GeckoEventListener. */
public void handleMessage(String event, JSONObject message) {
if ("Viewport:Update".equals(event)) {
try {
ViewportMetrics newMetrics = new ViewportMetrics(message);
synchronized (mLayerController) {
// keep the old viewport size, but update everything else
ImmutableViewportMetrics oldMetrics = mLayerController.getViewportMetrics();
newMetrics.setSize(oldMetrics.getSize());
mLayerController.setViewportMetrics(newMetrics);
mLayerController.abortPanZoomAnimation(false);
}
} catch (JSONException e) {
Log.e(LOGTAG, "Unable to create viewport metrics in " + event + " handler", e);
}
}
}
/** Implementation of GeckoEventResponder. */
public String getResponse() {
// We are responding to the events handled in handleMessage() above with the
// display port we want. Note that all messages we are currently handling
// (just Viewport:Update) require this response, so we can just return this
// indiscriminately.
updateDisplayPort();
return RectUtils.toJSON(mDisplayPort);
}
void geometryChanged() {
/* Let Gecko know if the screensize has changed */
sendResizeEventIfNecessary(false);
if (mLayerController.getRedrawHint())
adjustViewport();
}
public ViewportMetrics getGeckoViewportMetrics() {
// Return a copy, as we modify this inside the Gecko thread
if (mGeckoViewport != null)
return new ViewportMetrics(mGeckoViewport);
return null;
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature.
* The compositor invokes this function just before compositing a frame where the document
* is different from the document composited on the last frame. In these cases, the viewport
* information we have in Java is no longer valid and needs to be replaced with the new
* viewport information provided. setPageSize will never be invoked on the same frame that
* this function is invoked on; and this function will always be called prior to getViewTransform.
*/
public void setFirstPaintViewport(float offsetX, float offsetY, float zoom, float pageWidth, float pageHeight) {
synchronized (mLayerController) {
ViewportMetrics currentMetrics = new ViewportMetrics(mLayerController.getViewportMetrics());
currentMetrics.setOrigin(new PointF(offsetX, offsetY));
currentMetrics.setZoomFactor(zoom);
currentMetrics.setPageSize(new FloatSize(pageWidth, pageHeight));
mLayerController.setViewportMetrics(currentMetrics);
// At this point, we have just switched to displaying a different document than we
// we previously displaying. This means we need to abort any panning/zooming animations
// that are in progress and send an updated display port request to browser.js as soon
// as possible. We accomplish this by passing true to abortPanZoomAnimation, which
// sends the request after aborting the animation. The display port request is actually
// a full viewport update, which is fine because if browser.js has somehow moved to
// be out of sync with this first-paint viewport, then we force them back in sync.
mLayerController.abortPanZoomAnimation(true);
}
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature.
* The compositor invokes this function whenever it determines that the page size
* has changed (based on the information it gets from layout). If setFirstPaintViewport
* is invoked on a frame, then this function will not be. For any given frame, this
* function will be invoked before getViewTransform.
*/
public void setPageSize(float zoom, float pageWidth, float pageHeight) {
synchronized (mLayerController) {
// adjust the page dimensions to account for differences in zoom
// between the rendered content (which is what the compositor tells us)
// and our zoom level (which may have diverged).
float ourZoom = mLayerController.getZoomFactor();
pageWidth = pageWidth * ourZoom / zoom;
pageHeight = pageHeight * ourZoom /zoom;
mLayerController.setPageSize(new FloatSize(pageWidth, pageHeight));
// Here the page size of the document has changed, but the document being displayed
// is still the same. Therefore, we don't need to send anything to browser.js; any
// changes we need to make to the display port will get sent the next time we call
// adjustViewport().
}
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature.
* The compositor invokes this function on every frame to figure out what part of the
* page to display. Since it is called on every frame, it needs to be ultra-fast.
* It avoids taking any locks or allocating any objects. We keep around a
* mCurrentViewTransform so we don't need to allocate a new ViewTransform
* everytime we're called. NOTE: we might be able to return a ImmutableViewportMetrics
* which would avoid the copy into mCurrentViewTransform.
*/
public ViewTransform getViewTransform() {
// getViewportMetrics is thread safe so we don't need to synchronize
// on myLayerController.
ImmutableViewportMetrics viewportMetrics = mLayerController.getViewportMetrics();
mCurrentViewTransform.x = viewportMetrics.viewportRectLeft;
mCurrentViewTransform.y = viewportMetrics.viewportRectTop;
mCurrentViewTransform.scale = viewportMetrics.zoomFactor;
return mCurrentViewTransform;
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public LayerRenderer.Frame createFrame() {
// Create the shaders and textures if necessary.
if (!mLayerRendererInitialized) {
mLayerRenderer.checkMonitoringEnabled();
mLayerRenderer.createDefaultProgram();
mLayerRendererInitialized = true;
}
// Build the contexts and create the frame.
Layer.RenderContext pageContext = mLayerRenderer.createPageContext();
Layer.RenderContext screenContext = mLayerRenderer.createScreenContext();
return mLayerRenderer.createFrame(pageContext, screenContext);
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public void activateProgram() {
mLayerRenderer.activateDefaultProgram();
}
/** This function is invoked by Gecko via JNI; be careful when modifying signature. */
public void deactivateProgram() {
mLayerRenderer.deactivateDefaultProgram();
}
/** Implementation of FlexibleGLSurfaceView.Listener */
public void renderRequested() {
GeckoAppShell.scheduleComposite();
}
/** Implementation of FlexibleGLSurfaceView.Listener */
public void compositionPauseRequested() {
GeckoAppShell.schedulePauseComposition();
}
/** Implementation of FlexibleGLSurfaceView.Listener */
public void compositionResumeRequested() {
GeckoAppShell.scheduleResumeComposition();
}
/** Implementation of FlexibleGLSurfaceView.Listener */
public void surfaceChanged(int width, int height) {
compositionPauseRequested();
mLayerController.setViewportSize(new FloatSize(width, height));
compositionResumeRequested();
renderRequested();
}
/** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
public void setDrawListener(DrawListener listener) {
mDrawListener = listener;
}
/** Used by robocop for testing purposes. Not for production use! This is used via reflection by robocop. */
public interface DrawListener {
public void drawFinished();
}
}

View File

@ -1,575 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Chris Lord <chrislord.net@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.CairoImage;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerClient;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.LayerRenderer;
import org.mozilla.gecko.gfx.MultiTileLayer;
import org.mozilla.gecko.gfx.PointUtils;
import org.mozilla.gecko.gfx.WidgetTileLayer;
import org.mozilla.gecko.FloatUtils;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import org.mozilla.gecko.GeckoEvent;
import org.mozilla.gecko.GeckoEventListener;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.SystemClock;
import android.util.DisplayMetrics;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.nio.ByteBuffer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Transfers a software-rendered Gecko to an ImageLayer so that it can be rendered by our
* compositor.
*
* TODO: Throttle down Gecko's priority when we pan and zoom.
*/
public class GeckoSoftwareLayerClient extends LayerClient implements GeckoEventListener {
private static final String LOGTAG = "GeckoSoftwareLayerClient";
private Context mContext;
private int mFormat;
private IntSize mScreenSize, mViewportSize;
private IntSize mBufferSize;
private ByteBuffer mBuffer;
private Layer mTileLayer;
/* The viewport that Gecko is currently displaying. */
private ViewportMetrics mGeckoViewport;
/* The viewport that Gecko will display when drawing is finished */
private ViewportMetrics mNewGeckoViewport;
private CairoImage mCairoImage;
private static final IntSize TILE_SIZE = new IntSize(256, 256);
private static final long MIN_VIEWPORT_CHANGE_DELAY = 350L;
private long mLastViewportChangeTime;
private boolean mPendingViewportAdjust;
private boolean mViewportSizeChanged;
// Whether or not the last paint we got used direct texturing
private boolean mHasDirectTexture;
// mUpdateViewportOnEndDraw is used to indicate that we received a
// viewport update notification while drawing. therefore, when the
// draw finishes, we need to update the entire viewport rather than
// just the page size. this boolean should always be accessed from
// inside a transaction, so no synchronization is needed.
private boolean mUpdateViewportOnEndDraw;
/* Used by robocop for testing purposes */
private DrawListener mDrawListener;
private static Pattern sColorPattern;
public GeckoSoftwareLayerClient(Context context) {
mContext = context;
mScreenSize = new IntSize(0, 0);
mBufferSize = new IntSize(0, 0);
mFormat = CairoImage.FORMAT_RGB16_565;
mCairoImage = new CairoImage() {
@Override
public ByteBuffer getBuffer() { return mBuffer; }
@Override
public IntSize getSize() { return mBufferSize; }
@Override
public int getFormat() { return mFormat; }
};
}
public int getWidth() {
return mBufferSize.width;
}
public int getHeight() {
return mBufferSize.height;
}
protected void finalize() throws Throwable {
try {
if (mBuffer != null)
GeckoAppShell.freeDirectBuffer(mBuffer);
mBuffer = null;
} finally {
super.finalize();
}
}
/** Attaches the root layer to the layer controller so that Gecko appears. */
@Override
public void setLayerController(LayerController layerController) {
super.setLayerController(layerController);
layerController.setRoot(mTileLayer);
if (mGeckoViewport != null) {
layerController.setViewportMetrics(mGeckoViewport);
}
GeckoAppShell.registerGeckoEventListener("Viewport:UpdateAndDraw", this);
GeckoAppShell.registerGeckoEventListener("Viewport:UpdateLater", this);
GeckoAppShell.registerGeckoEventListener("Checkerboard:Toggle", this);
sendResizeEventIfNecessary();
}
private boolean setHasDirectTexture(boolean hasDirectTexture) {
if (mTileLayer != null && hasDirectTexture == mHasDirectTexture)
return false;
mHasDirectTexture = hasDirectTexture;
if (mHasDirectTexture) {
Log.i(LOGTAG, "Creating WidgetTileLayer");
mTileLayer = new WidgetTileLayer(mCairoImage);
} else {
Log.i(LOGTAG, "Creating MultiTileLayer");
mTileLayer = new MultiTileLayer(mCairoImage, TILE_SIZE);
}
getLayerController().setRoot(mTileLayer);
// Force a resize event to be sent because the results of this
// are different depending on what tile system we're using
sendResizeEventIfNecessary(true);
return true;
}
public Rect beginDrawing(int width, int height, int tileWidth, int tileHeight,
String metadata, boolean hasDirectTexture) {
setHasDirectTexture(hasDirectTexture);
// Make sure the tile-size matches. If it doesn't, we could crash trying
// to access invalid memory.
if (mHasDirectTexture) {
if (tileWidth != 0 || tileHeight != 0) {
Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" + tileHeight);
return null;
}
} else {
if (tileWidth != TILE_SIZE.width || tileHeight != TILE_SIZE.height) {
Log.e(LOGTAG, "Aborting draw, incorrect tile size of " + tileWidth + "x" + tileHeight);
return null;
}
}
LayerController controller = getLayerController();
try {
JSONObject viewportObject = new JSONObject(metadata);
mNewGeckoViewport = new ViewportMetrics(viewportObject);
// Update the background color, if it's present.
String backgroundColorString = viewportObject.optString("backgroundColor");
if (backgroundColorString != null) {
controller.setCheckerboardColor(parseColorFromGecko(backgroundColorString));
}
} catch (JSONException e) {
Log.e(LOGTAG, "Aborting draw, bad viewport description: " + metadata);
return null;
}
// Make sure we don't spend time painting areas we aren't interested in.
// Only do this if the Gecko viewport isn't going to override our viewport.
Rect bufferRect = new Rect(0, 0, width, height);
if (!mUpdateViewportOnEndDraw) {
// First, find out our ideal displayport. We do this by taking the
// clamped viewport origin and taking away the optimum viewport offset.
// This would be what we would send to Gecko if adjustViewport were
// called now.
ViewportMetrics currentMetrics = controller.getViewportMetrics();
PointF currentBestOrigin = RectUtils.getOrigin(currentMetrics.getClampedViewport());
PointF viewportOffset = currentMetrics.getOptimumViewportOffset(new IntSize(width, height));
currentBestOrigin.offset(-viewportOffset.x, -viewportOffset.y);
Rect currentRect = RectUtils.round(new RectF(currentBestOrigin.x, currentBestOrigin.y,
currentBestOrigin.x + width, currentBestOrigin.y + height));
// Second, store Gecko's displayport.
PointF currentOrigin = mNewGeckoViewport.getDisplayportOrigin();
bufferRect = RectUtils.round(new RectF(currentOrigin.x, currentOrigin.y,
currentOrigin.x + width, currentOrigin.y + height));
// Take the intersection of the two as the area we're interested in rendering.
if (!bufferRect.intersect(currentRect)) {
// If there's no intersection, we have no need to render anything,
// but make sure to update the viewport size.
beginTransaction(mTileLayer);
try {
updateViewport(true);
} finally {
endTransaction(mTileLayer);
}
return null;
}
bufferRect.offset(Math.round(-currentOrigin.x), Math.round(-currentOrigin.y));
}
beginTransaction(mTileLayer);
// Synchronise the buffer size with Gecko.
if (mBufferSize.width != width || mBufferSize.height != height) {
mBufferSize = new IntSize(width, height);
// Reallocate the buffer if necessary
if (mTileLayer instanceof MultiTileLayer) {
int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
int size = mBufferSize.getArea() * bpp;
if (mBuffer == null || mBuffer.capacity() != size) {
// Free the old buffer
if (mBuffer != null) {
GeckoAppShell.freeDirectBuffer(mBuffer);
mBuffer = null;
}
mBuffer = GeckoAppShell.allocateDirectBuffer(size);
}
}
}
return bufferRect;
}
private void updateViewport(final boolean onlyUpdatePageSize) {
// save and restore the viewport size stored in java; never let the
// JS-side viewport dimensions override the java-side ones because
// java is the One True Source of this information, and allowing JS
// to override can lead to race conditions where this data gets clobbered.
FloatSize viewportSize = getLayerController().getViewportSize();
mGeckoViewport = mNewGeckoViewport;
mGeckoViewport.setSize(viewportSize);
LayerController controller = getLayerController();
PointF displayportOrigin = mGeckoViewport.getDisplayportOrigin();
mTileLayer.setOrigin(PointUtils.round(displayportOrigin));
mTileLayer.setResolution(mGeckoViewport.getZoomFactor());
if (onlyUpdatePageSize) {
// Don't adjust page size when zooming unless zoom levels are
// approximately equal.
if (FloatUtils.fuzzyEquals(controller.getZoomFactor(),
mGeckoViewport.getZoomFactor()))
controller.setPageSize(mGeckoViewport.getPageSize());
} else {
controller.setViewportMetrics(mGeckoViewport);
controller.abortPanZoomAnimation();
}
}
/*
* TODO: Would be cleaner if this took an android.graphics.Rect instead, but that would require
* a little more JNI magic.
*/
public void endDrawing(int x, int y, int width, int height) {
synchronized (getLayerController()) {
try {
updateViewport(!mUpdateViewportOnEndDraw);
mUpdateViewportOnEndDraw = false;
if (mTileLayer instanceof MultiTileLayer) {
Rect rect = new Rect(x, y, x + width, y + height);
((MultiTileLayer)mTileLayer).invalidate(rect);
}
} finally {
endTransaction(mTileLayer);
}
}
Log.i(LOGTAG, "zerdatime " + SystemClock.uptimeMillis() + " - endDrawing");
/* Used by robocop for testing purposes */
if (mDrawListener != null) {
mDrawListener.drawFinished(x, y, width, height);
}
}
public ViewportMetrics getGeckoViewportMetrics() {
// Return a copy, as we modify this inside the Gecko thread
if (mGeckoViewport != null)
return new ViewportMetrics(mGeckoViewport);
return null;
}
public void copyPixelsFromMultiTileLayer(Bitmap target) {
Canvas c = new Canvas(target);
ByteBuffer tileBuffer = mBuffer.slice();
int bpp = CairoUtils.bitsPerPixelForCairoFormat(mFormat) / 8;
for (int y = 0; y < mBufferSize.height; y += TILE_SIZE.height) {
for (int x = 0; x < mBufferSize.width; x += TILE_SIZE.width) {
// Calculate tile size
IntSize tileSize = new IntSize(Math.min(mBufferSize.width - x, TILE_SIZE.width),
Math.min(mBufferSize.height - y, TILE_SIZE.height));
// Create a Bitmap from this tile
Bitmap tile = Bitmap.createBitmap(tileSize.width, tileSize.height,
CairoUtils.cairoFormatTobitmapConfig(mFormat));
tile.copyPixelsFromBuffer(tileBuffer.asIntBuffer());
// Copy the tile to the master Bitmap and recycle it
c.drawBitmap(tile, x, y, null);
tile.recycle();
// Progress the buffer to the next tile
tileBuffer.position(tileSize.getArea() * bpp);
tileBuffer = tileBuffer.slice();
}
}
}
public Bitmap getBitmap() {
if (mTileLayer == null)
return null;
// Begin a tile transaction, otherwise the buffer can be destroyed while
// we're reading from it.
beginTransaction(mTileLayer);
try {
if (mBuffer == null || mBufferSize.width <= 0 || mBufferSize.height <= 0)
return null;
try {
Bitmap b = null;
if (mTileLayer instanceof MultiTileLayer) {
b = Bitmap.createBitmap(mBufferSize.width, mBufferSize.height,
CairoUtils.cairoFormatTobitmapConfig(mFormat));
copyPixelsFromMultiTileLayer(b);
} else {
Log.w(LOGTAG, "getBitmap() called on a layer (" + mTileLayer + ") we don't know how to get a bitmap from");
}
return b;
} catch (OutOfMemoryError oom) {
Log.w(LOGTAG, "Unable to create bitmap", oom);
return null;
}
} finally {
endTransaction(mTileLayer);
}
}
/** Returns the back buffer. This function is for Gecko to use. */
public ByteBuffer lockBuffer() {
return mBuffer;
}
/**
* Gecko calls this function to signal that it is done with the back buffer. After this call,
* it is forbidden for Gecko to touch the buffer.
*/
public void unlockBuffer() {
/* no-op */
}
@Override
public void geometryChanged() {
/* Let Gecko know if the screensize has changed */
sendResizeEventIfNecessary();
render();
}
private void sendResizeEventIfNecessary() {
sendResizeEventIfNecessary(false);
}
/* Informs Gecko that the screen size has changed. */
private void sendResizeEventIfNecessary(boolean force) {
DisplayMetrics metrics = new DisplayMetrics();
GeckoApp.mAppContext.getWindowManager().getDefaultDisplay().getMetrics(metrics);
// Return immediately if the screen size hasn't changed or the viewport
// size is zero (which indicates that the rendering surface hasn't been
// allocated yet).
boolean screenSizeChanged = (metrics.widthPixels != mScreenSize.width ||
metrics.heightPixels != mScreenSize.height);
boolean viewportSizeValid = (getLayerController() != null &&
getLayerController().getViewportSize().isPositive());
if (!(force || (screenSizeChanged && viewportSizeValid))) {
return;
}
mScreenSize = new IntSize(metrics.widthPixels, metrics.heightPixels);
IntSize bufferSize;
IntSize tileSize;
// Round up depending on layer implementation to remove texture wastage
if (!mHasDirectTexture) {
// Round to the next multiple of the tile size
bufferSize = new IntSize(((mScreenSize.width + LayerController.MIN_BUFFER.width - 1) / TILE_SIZE.width + 1) * TILE_SIZE.width,
((mScreenSize.height + LayerController.MIN_BUFFER.height - 1) / TILE_SIZE.height + 1) * TILE_SIZE.height);
tileSize = TILE_SIZE;
} else {
int maxSize = getLayerController().getView().getMaxTextureSize();
// XXX Integrate gralloc/tiling work to circumvent this
if (mScreenSize.width > maxSize || mScreenSize.height > maxSize)
throw new RuntimeException("Screen size of " + mScreenSize + " larger than maximum texture size of " + maxSize);
// Round to next power of two until we have NPOT texture support, respecting maximum texture size
bufferSize = new IntSize(Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.width + LayerController.MIN_BUFFER.width)),
Math.min(maxSize, IntSize.nextPowerOfTwo(mScreenSize.height + LayerController.MIN_BUFFER.height)));
tileSize = new IntSize(0, 0);
}
Log.i(LOGTAG, "Screen-size changed to " + mScreenSize);
GeckoEvent event = GeckoEvent.createSizeChangedEvent(
bufferSize.width, bufferSize.height, metrics.widthPixels,
metrics.heightPixels, tileSize.width, tileSize.height);
GeckoAppShell.sendEventToGecko(event);
}
@Override
public void viewportSizeChanged() {
mViewportSizeChanged = true;
}
@Override
public void render() {
adjustViewportWithThrottling();
}
private void adjustViewportWithThrottling() {
if (!getLayerController().getRedrawHint())
return;
if (mPendingViewportAdjust)
return;
long timeDelta = System.currentTimeMillis() - mLastViewportChangeTime;
if (timeDelta < MIN_VIEWPORT_CHANGE_DELAY) {
getLayerController().getView().postDelayed(
new Runnable() {
public void run() {
mPendingViewportAdjust = false;
adjustViewport();
}
}, MIN_VIEWPORT_CHANGE_DELAY - timeDelta);
mPendingViewportAdjust = true;
return;
}
adjustViewport();
}
private void adjustViewport() {
ViewportMetrics viewportMetrics =
new ViewportMetrics(getLayerController().getViewportMetrics());
PointF viewportOffset = viewportMetrics.getOptimumViewportOffset(mBufferSize);
viewportMetrics.setViewportOffset(viewportOffset);
viewportMetrics.setViewport(viewportMetrics.getClampedViewport());
GeckoAppShell.sendEventToGecko(GeckoEvent.createViewportEvent(viewportMetrics));
if (mViewportSizeChanged) {
mViewportSizeChanged = false;
GeckoAppShell.viewSizeChanged();
}
mLastViewportChangeTime = System.currentTimeMillis();
}
public void handleMessage(String event, JSONObject message) {
if ("Viewport:UpdateAndDraw".equals(event)) {
mUpdateViewportOnEndDraw = true;
// Redraw everything.
Rect rect = new Rect(0, 0, mBufferSize.width, mBufferSize.height);
GeckoAppShell.sendEventToGecko(GeckoEvent.createDrawEvent(rect));
} else if ("Viewport:UpdateLater".equals(event)) {
mUpdateViewportOnEndDraw = true;
} else if ("Checkerboard:Toggle".equals(event)) {
try {
boolean showChecks = message.getBoolean("value");
LayerController controller = getLayerController();
controller.setCheckerboardShowChecks(showChecks);
Log.i(LOGTAG, "Showing checks: " + showChecks);
} catch(JSONException ex) {
Log.e(LOGTAG, "Error decoding JSON", ex);
}
}
}
// Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color
// cannot be parsed, returns white.
private static int parseColorFromGecko(String string) {
if (sColorPattern == null) {
sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
}
Matcher matcher = sColorPattern.matcher(string);
if (!matcher.matches()) {
return Color.WHITE;
}
int r = Integer.parseInt(matcher.group(1));
int g = Integer.parseInt(matcher.group(2));
int b = Integer.parseInt(matcher.group(3));
return Color.rgb(r, g, b);
}
/** Used by robocop for testing purposes. Not for production use! This is called via reflection by robocop. */
public void setDrawListener(DrawListener listener) {
mDrawListener = listener;
}
/** Used by robocop for testing purposes. Not for production use! This is used via reflection by robocop. */
public interface DrawListener {
public void drawFinished(int x, int y, int width, int height);
}
}

View File

@ -0,0 +1,70 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.gecko.gfx;
import android.graphics.PointF;
import android.graphics.RectF;
/**
* ImmutableViewportMetrics are used to store the viewport metrics
* in way that we can access a version of them from multiple threads
* without having to take a lock
*/
public class ImmutableViewportMetrics {
// We need to flatten the RectF and FloatSize structures
// because Java doesn't have the concept of const classes
public final float pageSizeWidth;
public final float pageSizeHeight;
public final float viewportRectBottom;
public final float viewportRectLeft;
public final float viewportRectRight;
public final float viewportRectTop;
public final float zoomFactor;
public ImmutableViewportMetrics(ViewportMetrics m) {
RectF viewportRect = m.getViewport();
viewportRectBottom = viewportRect.bottom;
viewportRectLeft = viewportRect.left;
viewportRectRight = viewportRect.right;
viewportRectTop = viewportRect.top;
FloatSize pageSize = m.getPageSize();
pageSizeWidth = pageSize.width;
pageSizeHeight = pageSize.height;
zoomFactor = m.getZoomFactor();
}
public float getWidth() {
return viewportRectRight - viewportRectLeft;
}
public float getHeight() {
return viewportRectBottom - viewportRectTop;
}
// some helpers to make ImmutableViewportMetrics act more like ViewportMetrics
public PointF getOrigin() {
return new PointF(viewportRectLeft, viewportRectTop);
}
public FloatSize getSize() {
return new FloatSize(viewportRectRight - viewportRectLeft, viewportRectBottom - viewportRectTop);
}
public RectF getViewport() {
return new RectF(viewportRectLeft,
viewportRectTop,
viewportRectRight,
viewportRectBottom);
}
public FloatSize getPageSize() {
return new FloatSize(pageSizeWidth, pageSizeHeight);
}
}

View File

@ -21,6 +21,7 @@
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Chris Lord <chrislord.net@gmail.com>
* Arkady Blyakher <rkadyb@mit.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -38,28 +39,34 @@
package org.mozilla.gecko.gfx;
import android.graphics.Point;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.Log;
import java.nio.FloatBuffer;
import java.util.concurrent.locks.ReentrantLock;
import javax.microedition.khronos.opengles.GL10;
import org.mozilla.gecko.FloatUtils;
public abstract class Layer {
private final ReentrantLock mTransactionLock;
private boolean mInTransaction;
private Point mNewOrigin;
private Rect mNewPosition;
private float mNewResolution;
private LayerView mView;
protected Point mOrigin;
protected Rect mPosition;
protected float mResolution;
public Layer() {
this(null);
}
public Layer(IntSize size) {
mTransactionLock = new ReentrantLock();
mOrigin = new Point(0, 0);
if (size == null) {
mPosition = new Rect();
} else {
mPosition = new Rect(0, 0, size.width, size.height);
}
mResolution = 1.0f;
}
@ -67,7 +74,7 @@ public abstract class Layer {
* Updates the layer. This returns false if there is still work to be done
* after this update.
*/
public final boolean update(GL10 gl, RenderContext context) {
public final boolean update(RenderContext context) {
if (mTransactionLock.isHeldByCurrentThread()) {
throw new RuntimeException("draw() called while transaction lock held by this " +
"thread?!");
@ -75,7 +82,7 @@ public abstract class Layer {
if (mTransactionLock.tryLock()) {
try {
return performUpdates(gl, context);
return performUpdates(context);
} finally {
mTransactionLock.unlock();
}
@ -87,15 +94,9 @@ public abstract class Layer {
/** Subclasses override this function to draw the layer. */
public abstract void draw(RenderContext context);
/** Subclasses override this function to provide access to the size of the layer. */
public abstract IntSize getSize();
/** Given the intrinsic size of the layer, returns the pixel boundaries of the layer rect. */
protected RectF getBounds(RenderContext context, FloatSize size) {
float scaleFactor = context.zoomFactor / mResolution;
float x = mOrigin.x * scaleFactor, y = mOrigin.y * scaleFactor;
float width = size.width * scaleFactor, height = size.height * scaleFactor;
return new RectF(x, y, x + width, y + height);
protected RectF getBounds(RenderContext context) {
return RectUtils.scale(new RectF(mPosition), context.zoomFactor / mResolution);
}
/**
@ -104,7 +105,7 @@ public abstract class Layer {
* may be overridden.
*/
public Region getValidRegion(RenderContext context) {
return new Region(RectUtils.round(getBounds(context, new FloatSize(getSize()))));
return new Region(RectUtils.round(getBounds(context)));
}
/**
@ -114,28 +115,20 @@ public abstract class Layer {
*
* This function may block, so you should never call this on the main UI thread.
*/
public void beginTransaction(LayerView aView) {
public void beginTransaction() {
if (mTransactionLock.isHeldByCurrentThread())
throw new RuntimeException("Nested transactions are not supported");
mTransactionLock.lock();
mView = aView;
mInTransaction = true;
mNewResolution = mResolution;
}
public void beginTransaction() {
beginTransaction(null);
}
/** Call this when you're done modifying the layer. */
public void endTransaction() {
if (!mInTransaction)
throw new RuntimeException("endTransaction() called outside a transaction");
mInTransaction = false;
mTransactionLock.unlock();
if (mView != null)
mView.requestRender();
}
/** Returns true if the layer is currently in a transaction and false otherwise. */
@ -143,16 +136,16 @@ public abstract class Layer {
return mInTransaction;
}
/** Returns the current layer origin. */
public Point getOrigin() {
return mOrigin;
/** Returns the current layer position. */
public Rect getPosition() {
return mPosition;
}
/** Sets the origin. Only valid inside a transaction. */
public void setOrigin(Point newOrigin) {
/** Sets the position. Only valid inside a transaction. */
public void setPosition(Rect newPosition) {
if (!mInTransaction)
throw new RuntimeException("setOrigin() is only valid inside a transaction");
mNewOrigin = newOrigin;
throw new RuntimeException("setPosition() is only valid inside a transaction");
mNewPosition = newPosition;
}
/** Returns the current layer's resolution. */
@ -177,10 +170,10 @@ public abstract class Layer {
* superclass implementation. Returns false if there is still work to be done after this
* update is complete.
*/
protected boolean performUpdates(GL10 gl, RenderContext context) {
if (mNewOrigin != null) {
mOrigin = mNewOrigin;
mNewOrigin = null;
protected boolean performUpdates(RenderContext context) {
if (mNewPosition != null) {
mPosition = mNewPosition;
mNewPosition = null;
}
if (mNewResolution != 0.0f) {
mResolution = mNewResolution;
@ -194,11 +187,18 @@ public abstract class Layer {
public final RectF viewport;
public final FloatSize pageSize;
public final float zoomFactor;
public final int positionHandle;
public final int textureHandle;
public final FloatBuffer coordBuffer;
public RenderContext(RectF aViewport, FloatSize aPageSize, float aZoomFactor) {
public RenderContext(RectF aViewport, FloatSize aPageSize, float aZoomFactor,
int aPositionHandle, int aTextureHandle, FloatBuffer aCoordBuffer) {
viewport = aViewport;
pageSize = aPageSize;
zoomFactor = aZoomFactor;
positionHandle = aPositionHandle;
textureHandle = aTextureHandle;
coordBuffer = aCoordBuffer;
}
public boolean fuzzyEquals(RenderContext other) {

View File

@ -40,8 +40,6 @@ package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.Layer;
import org.mozilla.gecko.gfx.LayerClient;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.ui.PanZoomController;
import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
import org.mozilla.gecko.GeckoApp;
@ -52,6 +50,7 @@ import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Color;
import android.graphics.Matrix;
import android.graphics.Point;
import android.graphics.PointF;
@ -66,6 +65,8 @@ import android.view.ViewConfiguration;
import java.lang.Math;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* The layer controller manages a tile that represents the visible page. It does panning and
@ -80,7 +81,20 @@ public class LayerController implements Tabs.OnTabsChangedListener {
private Layer mRootLayer; /* The root layer. */
private LayerView mView; /* The main rendering view. */
private Context mContext; /* The current context. */
private ViewportMetrics mViewportMetrics; /* The current viewport metrics. */
/* This is volatile so that we can read and write to it from different threads.
* We avoid synchronization to make getting the viewport metrics from
* the compositor as cheap as possible. The viewport is immutable so
* we don't need to worry about anyone mutating it while we're reading from it.
* Specifically:
* 1) reading mViewportMetrics from any thread is fine without synchronization
* 2) writing to mViewportMetrics requires synchronizing on the layer controller object
* 3) whenver reading multiple fields from mViewportMetrics without synchronization (i.e. in
* case 1 above) you should always frist grab a local copy of the reference, and then use
* that because mViewportMetrics might get reassigned in between reading the different
* fields. */
private volatile ImmutableViewportMetrics mViewportMetrics; /* The current viewport metrics. */
private boolean mWaitForTouchListeners;
private PanZoomController mPanZoomController;
@ -90,7 +104,7 @@ public class LayerController implements Tabs.OnTabsChangedListener {
*/
private OnTouchListener mOnTouchListener; /* The touch listener. */
private LayerClient mLayerClient; /* The layer client. */
private GeckoLayerClient mLayerClient; /* The layer client. */
/* The new color for the checkerboard. */
private int mCheckerboardColor;
@ -117,13 +131,16 @@ public class LayerController implements Tabs.OnTabsChangedListener {
private Timer allowDefaultTimer = null;
private PointF initialTouchLocation = null;
private static Pattern sColorPattern;
public LayerController(Context context) {
mContext = context;
mForceRedraw = true;
mViewportMetrics = new ViewportMetrics();
mViewportMetrics = new ImmutableViewportMetrics(new ViewportMetrics());
mPanZoomController = new PanZoomController(this);
mView = new LayerView(context, this);
mCheckerboardShouldShowChecks = true;
Tabs.getInstance().registerOnTabsChangedListener(this);
@ -137,7 +154,7 @@ public class LayerController implements Tabs.OnTabsChangedListener {
public void setRoot(Layer layer) { mRootLayer = layer; }
public void setLayerClient(LayerClient layerClient) {
public void setLayerClient(GeckoLayerClient layerClient) {
mLayerClient = layerClient;
layerClient.setLayerController(this);
}
@ -146,11 +163,10 @@ public class LayerController implements Tabs.OnTabsChangedListener {
mForceRedraw = true;
}
public LayerClient getLayerClient() { return mLayerClient; }
public Layer getRoot() { return mRootLayer; }
public LayerView getView() { return mView; }
public Context getContext() { return mContext; }
public ViewportMetrics getViewportMetrics() { return mViewportMetrics; }
public ImmutableViewportMetrics getViewportMetrics() { return mViewportMetrics; }
public RectF getViewport() {
return mViewportMetrics.getViewport();
@ -169,7 +185,7 @@ public class LayerController implements Tabs.OnTabsChangedListener {
}
public float getZoomFactor() {
return mViewportMetrics.getZoomFactor();
return mViewportMetrics.zoomFactor;
}
public Bitmap getBackgroundPattern() { return getDrawable("background"); }
@ -199,47 +215,22 @@ public class LayerController implements Tabs.OnTabsChangedListener {
* result in an infinite loop.
*/
public void setViewportSize(FloatSize size) {
// Resize the viewport, and modify its zoom factor so that the page retains proportionally
// zoomed relative to the screen.
float oldHeight = mViewportMetrics.getSize().height;
float oldWidth = mViewportMetrics.getSize().width;
float oldZoomFactor = mViewportMetrics.getZoomFactor();
mViewportMetrics.setSize(size);
ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
viewportMetrics.setSize(size);
mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
// if the viewport got larger (presumably because the vkb went away), and the page
// is smaller than the new viewport size, increase the page size so that the panzoomcontroller
// doesn't zoom in to make it fit (bug 718270). this page size change is in anticipation of
// gecko increasing the page size to match the new viewport size, which will happen the next
// time we get a draw update.
if (size.width >= oldWidth && size.height >= oldHeight) {
FloatSize pageSize = mViewportMetrics.getPageSize();
if (pageSize.width < size.width || pageSize.height < size.height) {
mViewportMetrics.setPageSize(new FloatSize(Math.max(pageSize.width, size.width),
Math.max(pageSize.height, size.height)));
}
}
PointF newFocus = new PointF(size.width / 2.0f, size.height / 2.0f);
float newZoomFactor = size.width * oldZoomFactor / oldWidth;
mViewportMetrics.scaleTo(newZoomFactor, newFocus);
Log.d(LOGTAG, "setViewportSize: " + mViewportMetrics);
setForceRedraw();
if (mLayerClient != null)
if (mLayerClient != null) {
mLayerClient.viewportSizeChanged();
notifyLayerClientOfGeometryChange();
mPanZoomController.abortAnimation();
mView.requestRender();
}
}
/** Scrolls the viewport by the given offset. You must hold the monitor while calling this. */
public void scrollBy(PointF point) {
PointF origin = mViewportMetrics.getOrigin();
ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
PointF origin = viewportMetrics.getOrigin();
origin.offset(point.x, point.y);
mViewportMetrics.setOrigin(origin);
Log.d(LOGTAG, "scrollBy: " + mViewportMetrics);
viewportMetrics.setOrigin(origin);
mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
notifyLayerClientOfGeometryChange();
GeckoApp.mAppContext.repositionPluginViews(false);
@ -251,10 +242,11 @@ public class LayerController implements Tabs.OnTabsChangedListener {
if (mViewportMetrics.getPageSize().fuzzyEquals(size))
return;
mViewportMetrics.setPageSize(size);
Log.d(LOGTAG, "setPageSize: " + mViewportMetrics);
ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
viewportMetrics.setPageSize(size);
mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
// Page size is owned by the LayerClient, so no need to notify it of
// Page size is owned by the layer client, so no need to notify it of
// this change.
mView.post(new Runnable() {
@ -272,8 +264,7 @@ public class LayerController implements Tabs.OnTabsChangedListener {
* while calling this.
*/
public void setViewportMetrics(ViewportMetrics viewport) {
mViewportMetrics = new ViewportMetrics(viewport);
Log.d(LOGTAG, "setViewportMetrics: " + mViewportMetrics);
mViewportMetrics = new ImmutableViewportMetrics(viewport);
// this function may or may not be called on the UI thread,
// but repositionPluginViews must only be called on the UI thread.
GeckoApp.mAppContext.runOnUiThread(new Runnable() {
@ -289,8 +280,9 @@ public class LayerController implements Tabs.OnTabsChangedListener {
* scale operation. You must hold the monitor while calling this.
*/
public void scaleWithFocus(float zoomFactor, PointF focus) {
mViewportMetrics.scaleTo(zoomFactor, focus);
Log.d(LOGTAG, "scaleWithFocus: " + mViewportMetrics + "; zf=" + zoomFactor);
ViewportMetrics viewportMetrics = new ViewportMetrics(mViewportMetrics);
viewportMetrics.scaleTo(zoomFactor, focus);
mViewportMetrics = new ImmutableViewportMetrics(viewportMetrics);
// We assume the zoom level will only be modified by the
// PanZoomController, so no need to notify it of this change.
@ -315,11 +307,14 @@ public class LayerController implements Tabs.OnTabsChangedListener {
}
/** Aborts any pan/zoom animation that is currently in progress. */
public void abortPanZoomAnimation() {
public void abortPanZoomAnimation(final boolean notifyLayerClient) {
if (mPanZoomController != null) {
mView.post(new Runnable() {
public void run() {
mPanZoomController.abortAnimation();
if (notifyLayerClient) {
notifyLayerClientOfGeometryChange();
}
}
});
}
@ -335,16 +330,11 @@ public class LayerController implements Tabs.OnTabsChangedListener {
return true;
}
return aboutToCheckerboard() && mPanZoomController.getRedrawHint();
}
if (!mPanZoomController.getRedrawHint()) {
return false;
}
private RectF getTileRect() {
if (mRootLayer == null)
return new RectF();
float x = mRootLayer.getOrigin().x, y = mRootLayer.getOrigin().y;
IntSize layerSize = mRootLayer.getSize();
return new RectF(x, y, x + layerSize.width, y + layerSize.height);
return aboutToCheckerboard();
}
// Returns true if a checkerboard is about to be visible.
@ -359,29 +349,37 @@ public class LayerController implements Tabs.OnTabsChangedListener {
if (adjustedViewport.right > pageSize.width) adjustedViewport.right = pageSize.width;
if (adjustedViewport.bottom > pageSize.height) adjustedViewport.bottom = pageSize.height;
return !getTileRect().contains(adjustedViewport);
RectF displayPort = (mLayerClient == null ? new RectF() : mLayerClient.getDisplayPort());
return !displayPort.contains(adjustedViewport);
}
/**
* Converts a point from layer view coordinates to layer coordinates. In other words, given a
* point measured in pixels from the top left corner of the layer view, returns the point in
* pixels measured from the top left corner of the root layer, in the coordinate system of the
* layer itself. This method is used by the viewport controller as part of the process of
* translating touch events to Gecko's coordinate system.
* layer itself (CSS pixels). This method is used as part of the process of translating touch
* events to Gecko's coordinate system.
*/
public PointF convertViewPointToLayerPoint(PointF viewPoint) {
if (mRootLayer == null)
return null;
// Undo the transforms.
PointF origin = mViewportMetrics.getOrigin();
PointF newPoint = new PointF(origin.x, origin.y);
newPoint.offset(viewPoint.x, viewPoint.y);
ImmutableViewportMetrics viewportMetrics = mViewportMetrics;
PointF origin = viewportMetrics.getOrigin();
float zoom = viewportMetrics.zoomFactor;
Rect rootPosition = mRootLayer.getPosition();
float rootScale = mRootLayer.getResolution();
Point rootOrigin = mRootLayer.getOrigin();
newPoint.offset(-rootOrigin.x, -rootOrigin.y);
// viewPoint + origin gives the coordinate in device pixels from the top-left corner of the page.
// Divided by zoom, this gives us the coordinate in CSS pixels from the top-left corner of the page.
// rootPosition / rootScale is where Gecko thinks it is (scrollTo position) in CSS pixels from
// the top-left corner of the page. Subtracting the two gives us the offset of the viewPoint from
// the current Gecko coordinate in CSS pixels.
PointF layerPoint = new PointF(
((viewPoint.x + origin.x) / zoom) - (rootPosition.left / rootScale),
((viewPoint.y + origin.y) / zoom) - (rootPosition.top / rootScale));
return newPoint;
return layerPoint;
}
/*
@ -480,5 +478,29 @@ public class LayerController implements Tabs.OnTabsChangedListener {
mCheckerboardColor = newColor;
mView.requestRender();
}
/** Parses and sets a new color for the checkerboard. */
public void setCheckerboardColor(String newColor) {
setCheckerboardColor(parseColorFromGecko(newColor));
}
// Parses a color from an RGB triple of the form "rgb([0-9]+, [0-9]+, [0-9]+)". If the color
// cannot be parsed, returns white.
private static int parseColorFromGecko(String string) {
if (sColorPattern == null) {
sColorPattern = Pattern.compile("rgb\\((\\d+),\\s*(\\d+),\\s*(\\d+)\\)");
}
Matcher matcher = sColorPattern.matcher(string);
if (!matcher.matches()) {
return Color.WHITE;
}
int r = Integer.parseInt(matcher.group(1));
int g = Integer.parseInt(matcher.group(2));
int b = Integer.parseInt(matcher.group(3));
return Color.rgb(r, g, b);
}
}

View File

@ -21,6 +21,7 @@
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Chris Lord <chrislord.net@gmail.com>
* Arkady Blyakher <rkadyb@mit.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -42,13 +43,13 @@ import org.mozilla.gecko.gfx.BufferedCairoImage;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.Layer.RenderContext;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.LayerView;
import org.mozilla.gecko.gfx.NinePatchTileLayer;
import org.mozilla.gecko.gfx.SingleTileLayer;
import org.mozilla.gecko.gfx.TextureReaper;
import org.mozilla.gecko.gfx.TextureGenerator;
import org.mozilla.gecko.gfx.TextLayer;
import org.mozilla.gecko.gfx.TileLayer;
import org.mozilla.gecko.GeckoAppShell;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Point;
@ -57,6 +58,7 @@ import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.graphics.RegionIterator;
import android.opengl.GLES20;
import android.opengl.GLSurfaceView;
import android.os.SystemClock;
import android.util.DisplayMetrics;
@ -64,6 +66,9 @@ import android.util.Log;
import android.view.WindowManager;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
@ -80,7 +85,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
*/
private static final int MAX_FRAME_TIME = 16; /* 1000 ms / 60 FPS */
private static final int FRAME_RATE_METER_WIDTH = 64;
private static final int FRAME_RATE_METER_WIDTH = 128;
private static final int FRAME_RATE_METER_HEIGHT = 32;
private final LayerView mView;
@ -88,10 +93,11 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
private final CheckerboardImage mCheckerboardImage;
private final SingleTileLayer mCheckerboardLayer;
private final NinePatchTileLayer mShadowLayer;
private final TextLayer mFrameRateLayer;
private TextLayer mFrameRateLayer;
private final ScrollbarLayer mHorizScrollLayer;
private final ScrollbarLayer mVertScrollLayer;
private final FadeRunnable mFadeRunnable;
private final FloatBuffer mCoordBuffer;
private RenderContext mLastPageContext;
private int mMaxTextureSize;
@ -100,7 +106,6 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
// Dropped frames display
private int[] mFrameTimings;
private int mCurrentFrame, mFrameTimingsSum, mDroppedFrames;
private boolean mShowFrameRate;
// Render profiling output
private int mFramesRendered;
@ -111,6 +116,48 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
/* Used by robocop for testing purposes */
private IntBuffer mPixelBuffer;
// Used by GLES 2.0
private int mProgram;
private int mPositionHandle;
private int mTextureHandle;
private int mSampleHandle;
private int mTMatrixHandle;
// column-major matrix applied to each vertex to shift the viewport from
// one ranging from (-1, -1),(1,1) to (0,0),(1,1) and to scale all sizes by
// a factor of 2 to fill up the screen
public static final float[] DEFAULT_TEXTURE_MATRIX = {
2.0f, 0.0f, 0.0f, 0.0f,
0.0f, 2.0f, 0.0f, 0.0f,
0.0f, 0.0f, 2.0f, 0.0f,
-1.0f, -1.0f, 0.0f, 1.0f
};
private static final int COORD_BUFFER_SIZE = 20;
// The shaders run on the GPU directly, the vertex shader is only applying the
// matrix transform detailed above
public static final String DEFAULT_VERTEX_SHADER =
"uniform mat4 uTMatrix;\n" +
"attribute vec4 vPosition;\n" +
"attribute vec2 aTexCoord;\n" +
"varying vec2 vTexCoord;\n" +
"void main() {\n" +
" gl_Position = uTMatrix * vPosition;\n" +
" vTexCoord = aTexCoord;\n" +
"}\n";
// Note we flip the y-coordinate in the fragment shader from a
// coordinate system with (0,0) in the top left to one with (0,0) in
// the bottom left.
public static final String DEFAULT_FRAGMENT_SHADER =
"precision mediump float;\n" +
"varying vec2 vTexCoord;\n" +
"uniform sampler2D sTexture;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vec2(vTexCoord.x, 1.0 - vTexCoord.y));\n" +
"}\n";
public LayerRenderer(LayerView view) {
mView = view;
@ -125,30 +172,70 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
CairoImage shadowImage = new BufferedCairoImage(controller.getShadowPattern());
mShadowLayer = new NinePatchTileLayer(shadowImage);
IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT);
mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--");
mHorizScrollLayer = ScrollbarLayer.create(false);
mVertScrollLayer = ScrollbarLayer.create(true);
mHorizScrollLayer = ScrollbarLayer.create(this, false);
mVertScrollLayer = ScrollbarLayer.create(this, true);
mFadeRunnable = new FadeRunnable();
mFrameTimings = new int[60];
mCurrentFrame = mFrameTimingsSum = mDroppedFrames = 0;
mShowFrameRate = false;
// Initialize the FloatBuffer that will be used to store all vertices and texture
// coordinates in draw() commands.
ByteBuffer byteBuffer = GeckoAppShell.allocateDirectBuffer(COORD_BUFFER_SIZE * 4);
byteBuffer.order(ByteOrder.nativeOrder());
mCoordBuffer = byteBuffer.asFloatBuffer();
}
public void onSurfaceCreated(GL10 gl, EGLConfig config) {
checkMonitoringEnabled();
createDefaultProgram();
activateDefaultProgram();
}
gl.glHint(GL10.GL_PERSPECTIVE_CORRECTION_HINT, GL10.GL_FASTEST);
gl.glDisable(GL10.GL_DITHER);
gl.glEnable(GL10.GL_TEXTURE_2D);
public void createDefaultProgram() {
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, DEFAULT_FRAGMENT_SHADER);
mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
GLES20.glLinkProgram(mProgram); // creates OpenGL program executables
// Get handles to the vertex shader's vPosition, aTexCoord, sTexture, and uTMatrix members.
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix");
int maxTextureSizeResult[] = new int[1];
gl.glGetIntegerv(GL10.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeResult, 0);
mMaxTextureSize = maxTextureSizeResult[0];
}
TextureGenerator.get().fill();
// Activates the shader program.
public void activateDefaultProgram() {
// Add the program to the OpenGL environment
GLES20.glUseProgram(mProgram);
// Set the transformation matrix
GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false, DEFAULT_TEXTURE_MATRIX, 0);
// Enable the arrays from which we get the vertex and texture coordinates
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glEnableVertexAttribArray(mTextureHandle);
GLES20.glUniform1i(mSampleHandle, 0);
// TODO: Move these calls into a separate deactivate() call that is called after the
// underlay and overlay are rendered.
}
// Deactivates the shader program. This must be done to avoid crashes after returning to the
// Gecko C++ compositor from Java.
public void deactivateDefaultProgram() {
GLES20.glDisableVertexAttribArray(mTextureHandle);
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glUseProgram(0);
}
public int getMaxTextureSize() {
@ -179,149 +266,14 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
* Called whenever a new frame is about to be drawn.
*/
public void onDrawFrame(GL10 gl) {
long frameStartTime = SystemClock.uptimeMillis();
TextureReaper.get().reap(gl);
TextureGenerator.get().fill();
LayerController controller = mView.getController();
RenderContext screenContext = createScreenContext();
boolean updated = true;
synchronized (controller) {
Layer rootLayer = controller.getRoot();
RenderContext pageContext = createPageContext();
if (!pageContext.fuzzyEquals(mLastPageContext)) {
// the viewport or page changed, so show the scrollbars again
// as per UX decision
mVertScrollLayer.unfade();
mHorizScrollLayer.unfade();
mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
} else if (mFadeRunnable.timeToFade()) {
boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
if (stillFading) {
mFadeRunnable.scheduleNextFadeFrame();
}
}
mLastPageContext = pageContext;
/* Update layers. */
if (rootLayer != null) updated &= rootLayer.update(gl, pageContext);
updated &= mBackgroundLayer.update(gl, screenContext);
updated &= mShadowLayer.update(gl, pageContext);
updateCheckerboardLayer(gl, screenContext);
updated &= mFrameRateLayer.update(gl, screenContext);
updated &= mVertScrollLayer.update(gl, pageContext);
updated &= mHorizScrollLayer.update(gl, pageContext);
for (Layer layer : mExtraLayers)
updated &= layer.update(gl, pageContext);
/* Draw the background. */
mBackgroundLayer.draw(screenContext);
/* Draw the drop shadow, if we need to. */
Rect pageRect = getPageRect();
RectF untransformedPageRect = new RectF(0.0f, 0.0f, pageRect.width(),
pageRect.height());
if (!untransformedPageRect.contains(controller.getViewport()))
mShadowLayer.draw(pageContext);
/* Draw the checkerboard. */
Rect scissorRect = transformToScissorRect(pageRect);
gl.glEnable(GL10.GL_SCISSOR_TEST);
gl.glScissor(scissorRect.left, scissorRect.top,
scissorRect.width(), scissorRect.height());
mCheckerboardLayer.draw(screenContext);
/* Draw the layer the client added to us. */
if (rootLayer != null)
rootLayer.draw(pageContext);
gl.glDisable(GL10.GL_SCISSOR_TEST);
/* Draw any extra layers that were added (likely plugins) */
for (Layer layer : mExtraLayers)
layer.draw(pageContext);
/* Draw the vertical scrollbar. */
IntSize screenSize = new IntSize(controller.getViewportSize());
if (pageRect.height() > screenSize.height)
mVertScrollLayer.draw(pageContext);
/* Draw the horizontal scrollbar. */
if (pageRect.width() > screenSize.width)
mHorizScrollLayer.draw(pageContext);
/* Measure how much of the screen is checkerboarding */
if ((rootLayer != null) &&
(mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) {
// Find out how much of the viewport area is valid
Rect viewport = RectUtils.round(pageContext.viewport);
Region validRegion = rootLayer.getValidRegion(pageContext);
validRegion.op(viewport, Region.Op.INTERSECT);
float checkerboard = 0.0f;
if (!(validRegion.isRect() && validRegion.getBounds().equals(viewport))) {
int screenArea = viewport.width() * viewport.height();
validRegion.op(viewport, Region.Op.REVERSE_DIFFERENCE);
// XXX The assumption here is that a Region never has overlapping
// rects. This is true, as evidenced by reading the SkRegion
// source, but is not mentioned in the Android documentation,
// and so is liable to change.
// If it does change, this code will need to be reevaluated.
Rect r = new Rect();
int checkerboardArea = 0;
for (RegionIterator i = new RegionIterator(validRegion); i.next(r);) {
checkerboardArea += r.width() * r.height();
}
checkerboard = checkerboardArea / (float)screenArea;
}
PanningPerfAPI.recordCheckerboard(checkerboard);
mCompleteFramesRendered += 1.0f - checkerboard;
mFramesRendered ++;
if (frameStartTime - mProfileOutputTime > 1000) {
mProfileOutputTime = frameStartTime;
printCheckerboardStats();
}
}
}
/* Draw the FPS. */
if (mShowFrameRate) {
updateDroppedFrames(frameStartTime);
try {
gl.glEnable(GL10.GL_BLEND);
gl.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
mFrameRateLayer.draw(screenContext);
} finally {
gl.glDisable(GL10.GL_BLEND);
}
}
// If a layer update requires further work, schedule another redraw
if (!updated)
mView.requestRender();
PanningPerfAPI.recordFrameTime();
/* Used by robocop for testing purposes */
IntBuffer pixelBuffer = mPixelBuffer;
if (updated && pixelBuffer != null) {
synchronized (pixelBuffer) {
pixelBuffer.position(0);
gl.glReadPixels(0, 0, (int)screenContext.viewport.width(), (int)screenContext.viewport.height(), GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, pixelBuffer);
pixelBuffer.notify();
}
RenderContext pageContext = createPageContext(), screenContext = createScreenContext();
Frame frame = createFrame(pageContext, screenContext);
synchronized (mView.getController()) {
frame.beginDrawing();
frame.drawBackground();
frame.drawRootLayer();
frame.drawForeground();
frame.endDrawing();
}
}
@ -346,15 +298,15 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
return pixelBuffer;
}
private RenderContext createScreenContext() {
public RenderContext createScreenContext() {
LayerController layerController = mView.getController();
IntSize viewportSize = new IntSize(layerController.getViewportSize());
RectF viewport = new RectF(0.0f, 0.0f, viewportSize.width, viewportSize.height);
FloatSize pageSize = new FloatSize(layerController.getPageSize());
return new RenderContext(viewport, pageSize, 1.0f);
return createContext(viewport, pageSize, 1.0f);
}
private RenderContext createPageContext() {
public RenderContext createPageContext() {
LayerController layerController = mView.getController();
Rect viewport = new Rect();
@ -362,7 +314,12 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
FloatSize pageSize = new FloatSize(layerController.getPageSize());
float zoomFactor = layerController.getZoomFactor();
return new RenderContext(new RectF(viewport), pageSize, zoomFactor);
return createContext(new RectF(viewport), pageSize, zoomFactor);
}
private RenderContext createContext(RectF viewport, FloatSize pageSize, float zoomFactor) {
return new RenderContext(viewport, pageSize, zoomFactor, mPositionHandle, mTextureHandle,
mCoordBuffer);
}
private Rect getPageRect() {
@ -391,14 +348,17 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
}
public void onSurfaceChanged(GL10 gl, final int width, final int height) {
gl.glViewport(0, 0, width, height);
GLES20.glViewport(0, 0, width, height);
if (mFrameRateLayer != null) {
moveFrameRateLayer(width, height);
}
// updating the state in the view/controller/client should be
// done on the main UI thread, not the GL renderer thread
mView.post(new Runnable() {
public void run() {
mView.setViewportSize(new IntSize(width, height));
moveFrameRateLayer(width, height);
}
});
@ -418,7 +378,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
mCurrentFrame = (mCurrentFrame + 1) % mFrameTimings.length;
int averageTime = mFrameTimingsSum / mFrameTimings.length;
mFrameRateLayer.beginTransaction();
mFrameRateLayer.beginTransaction(); // called on compositor thread
try {
mFrameRateLayer.setText(averageTime + " ms/" + mDroppedFrames);
} finally {
@ -428,30 +388,36 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
/* Given the new dimensions for the surface, moves the frame rate layer appropriately. */
private void moveFrameRateLayer(int width, int height) {
mFrameRateLayer.beginTransaction();
mFrameRateLayer.beginTransaction(); // called on compositor thread
try {
Point origin = new Point(width - FRAME_RATE_METER_WIDTH - 8,
height - FRAME_RATE_METER_HEIGHT + 8);
mFrameRateLayer.setOrigin(origin);
Rect position = new Rect(width - FRAME_RATE_METER_WIDTH - 8,
height - FRAME_RATE_METER_HEIGHT + 8,
width - 8,
height + 8);
mFrameRateLayer.setPosition(position);
} finally {
mFrameRateLayer.endTransaction();
}
}
private void checkMonitoringEnabled() {
void checkMonitoringEnabled() {
/* Do this I/O off the main thread to minimize its impact on startup time. */
new Thread(new Runnable() {
@Override
public void run() {
Context context = mView.getContext();
SharedPreferences preferences = context.getSharedPreferences("GeckoApp", 0);
mShowFrameRate = preferences.getBoolean("showFrameRate", false);
if (preferences.getBoolean("showFrameRate", false)) {
IntSize frameRateLayerSize = new IntSize(FRAME_RATE_METER_WIDTH, FRAME_RATE_METER_HEIGHT);
mFrameRateLayer = TextLayer.create(frameRateLayerSize, "-- ms/--");
moveFrameRateLayer(mView.getWidth(), mView.getHeight());
}
mProfileRender = Log.isLoggable(PROFTAG, Log.DEBUG);
}
}).start();
}
private void updateCheckerboardLayer(GL10 gl, RenderContext renderContext) {
private void updateCheckerboardLayer(RenderContext renderContext) {
int checkerboardColor = mView.getController().getCheckerboardColor();
boolean showChecks = mView.getController().checkerboardShouldShowChecks();
if (checkerboardColor == mCheckerboardImage.getColor() &&
@ -459,7 +425,7 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
return;
}
mCheckerboardLayer.beginTransaction();
mCheckerboardLayer.beginTransaction(); // called on compositor thread
try {
mCheckerboardImage.update(showChecks, checkerboardColor);
mCheckerboardLayer.invalidate();
@ -467,7 +433,22 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
mCheckerboardLayer.endTransaction();
}
mCheckerboardLayer.update(gl, renderContext);
mCheckerboardLayer.update(renderContext); // called on compositor thread
}
/*
* create a vertex shader type (GLES20.GL_VERTEX_SHADER)
* or a fragment shader type (GLES20.GL_FRAGMENT_SHADER)
*/
public static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
public Frame createFrame(RenderContext pageContext, RenderContext screenContext) {
return new Frame(pageContext, screenContext);
}
class FadeRunnable implements Runnable {
@ -505,4 +486,221 @@ public class LayerRenderer implements GLSurfaceView.Renderer {
}
}
}
public class Frame {
// The timestamp recording the start of this frame.
private long mFrameStartTime;
// A rendering context for page-positioned layers, and one for screen-positioned layers.
private RenderContext mPageContext, mScreenContext;
// Whether a layer was updated.
private boolean mUpdated;
public Frame(RenderContext pageContext, RenderContext screenContext) {
mPageContext = pageContext;
mScreenContext = screenContext;
}
private void setScissorRect() {
Rect scissorRect = transformToScissorRect(getPageRect());
GLES20.glEnable(GLES20.GL_SCISSOR_TEST);
GLES20.glScissor(scissorRect.left, scissorRect.top,
scissorRect.width(), scissorRect.height());
}
/** This function is invoked via JNI; be careful when modifying signature. */
public void beginDrawing() {
mFrameStartTime = SystemClock.uptimeMillis();
TextureReaper.get().reap();
TextureGenerator.get().fill();
mUpdated = true;
LayerController controller = mView.getController();
Layer rootLayer = controller.getRoot();
if (!mPageContext.fuzzyEquals(mLastPageContext)) {
// the viewport or page changed, so show the scrollbars again
// as per UX decision
mVertScrollLayer.unfade();
mHorizScrollLayer.unfade();
mFadeRunnable.scheduleStartFade(ScrollbarLayer.FADE_DELAY);
} else if (mFadeRunnable.timeToFade()) {
boolean stillFading = mVertScrollLayer.fade() | mHorizScrollLayer.fade();
if (stillFading) {
mFadeRunnable.scheduleNextFadeFrame();
}
}
mLastPageContext = mPageContext;
/* Update layers. */
if (rootLayer != null) mUpdated &= rootLayer.update(mPageContext); // called on compositor thread
mUpdated &= mBackgroundLayer.update(mScreenContext); // called on compositor thread
mUpdated &= mShadowLayer.update(mPageContext); // called on compositor thread
updateCheckerboardLayer(mScreenContext);
if (mFrameRateLayer != null) mUpdated &= mFrameRateLayer.update(mScreenContext); // called on compositor thread
mUpdated &= mVertScrollLayer.update(mPageContext); // called on compositor thread
mUpdated &= mHorizScrollLayer.update(mPageContext); // called on compositor thread
for (Layer layer : mExtraLayers)
mUpdated &= layer.update(mPageContext); // called on compositor thread
GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
// If a layer update requires further work, schedule another redraw
if (!mUpdated)
mView.requestRender();
PanningPerfAPI.recordFrameTime();
/* Used by robocop for testing purposes */
IntBuffer pixelBuffer = mPixelBuffer;
if (mUpdated && pixelBuffer != null) {
synchronized (pixelBuffer) {
pixelBuffer.position(0);
GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(),
(int)mScreenContext.viewport.height(), GLES20.GL_RGBA,
GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
pixelBuffer.notify();
}
}
}
/** This function is invoked via JNI; be careful when modifying signature. */
public void drawBackground() {
/* Draw the background. */
mBackgroundLayer.setMask(getPageRect());
mBackgroundLayer.draw(mScreenContext);
/* Draw the drop shadow, if we need to. */
Rect pageRect = getPageRect();
RectF untransformedPageRect = new RectF(0.0f, 0.0f, pageRect.width(),
pageRect.height());
if (!untransformedPageRect.contains(mView.getController().getViewport()))
mShadowLayer.draw(mPageContext);
/* Find the area the root layer will render into, to mask the scissor rect */
Rect rootMask = null;
Layer rootLayer = mView.getController().getRoot();
if (rootLayer != null) {
RectF rootBounds = rootLayer.getBounds(mPageContext);
rootBounds.offset(-mPageContext.viewport.left, -mPageContext.viewport.top);
rootMask = new Rect();
rootBounds.roundOut(rootMask);
}
/* Draw the checkerboard. */
setScissorRect();
mCheckerboardLayer.setMask(rootMask);
mCheckerboardLayer.draw(mScreenContext);
GLES20.glDisable(GLES20.GL_SCISSOR_TEST);
}
// Draws the layer the client added to us.
void drawRootLayer() {
Layer rootLayer = mView.getController().getRoot();
if (rootLayer == null) {
return;
}
rootLayer.draw(mPageContext);
}
/** This function is invoked via JNI; be careful when modifying signature. */
public void drawForeground() {
Rect pageRect = getPageRect();
LayerController controller = mView.getController();
/* Draw any extra layers that were added (likely plugins) */
if (mExtraLayers.size() > 0) {
// This is a hack. SurfaceTextureLayer draws with its own program, so disable ours here
// and re-enable when done. If we end up adding other types of Layer here we'll need
// to do something different.
deactivateDefaultProgram();
for (Layer layer : mExtraLayers)
layer.draw(mPageContext);
activateDefaultProgram();
}
/* Draw the vertical scrollbar. */
IntSize screenSize = new IntSize(controller.getViewportSize());
if (pageRect.height() > screenSize.height)
mVertScrollLayer.draw(mPageContext);
/* Draw the horizontal scrollbar. */
if (pageRect.width() > screenSize.width)
mHorizScrollLayer.draw(mPageContext);
/* Measure how much of the screen is checkerboarding */
Layer rootLayer = controller.getRoot();
if ((rootLayer != null) &&
(mProfileRender || PanningPerfAPI.isRecordingCheckerboard())) {
// Find out how much of the viewport area is valid
Rect viewport = RectUtils.round(mPageContext.viewport);
Region validRegion = rootLayer.getValidRegion(mPageContext);
validRegion.op(viewport, Region.Op.INTERSECT);
float checkerboard = 0.0f;
if (!(validRegion.isRect() && validRegion.getBounds().equals(viewport))) {
int screenArea = viewport.width() * viewport.height();
validRegion.op(viewport, Region.Op.REVERSE_DIFFERENCE);
// XXX The assumption here is that a Region never has overlapping
// rects. This is true, as evidenced by reading the SkRegion
// source, but is not mentioned in the Android documentation,
// and so is liable to change.
// If it does change, this code will need to be reevaluated.
Rect r = new Rect();
int checkerboardArea = 0;
for (RegionIterator i = new RegionIterator(validRegion); i.next(r);) {
checkerboardArea += r.width() * r.height();
}
checkerboard = checkerboardArea / (float)screenArea;
}
PanningPerfAPI.recordCheckerboard(checkerboard);
mCompleteFramesRendered += 1.0f - checkerboard;
mFramesRendered ++;
if (mFrameStartTime - mProfileOutputTime > 1000) {
mProfileOutputTime = mFrameStartTime;
printCheckerboardStats();
}
}
/* Draw the FPS. */
if (mFrameRateLayer != null) {
updateDroppedFrames(mFrameStartTime);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
mFrameRateLayer.draw(mScreenContext);
}
}
/** This function is invoked via JNI; be careful when modifying signature. */
public void endDrawing() {
// If a layer update requires further work, schedule another redraw
if (!mUpdated)
mView.requestRender();
PanningPerfAPI.recordFrameTime();
/* Used by robocop for testing purposes */
IntBuffer pixelBuffer = mPixelBuffer;
if (mUpdated && pixelBuffer != null) {
synchronized (pixelBuffer) {
pixelBuffer.position(0);
GLES20.glReadPixels(0, 0, (int)mScreenContext.viewport.width(),
(int)mScreenContext.viewport.height(), GLES20.GL_RGBA,
GLES20.GL_UNSIGNED_BYTE, pixelBuffer);
pixelBuffer.notify();
}
}
}
}
}

View File

@ -20,6 +20,7 @@
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Arkady Blyakher <rkadyb@mit.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -37,17 +38,21 @@
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.GeckoInputConnection;
import org.mozilla.gecko.gfx.FloatSize;
import org.mozilla.gecko.gfx.InputConnectionHandler;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.ui.SimpleScaleGestureDetector;
import android.content.Context;
import android.opengl.GLSurfaceView;
import android.view.View;
import android.view.GestureDetector;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import android.view.ScaleGestureDetector;
import android.widget.RelativeLayout;
import android.util.Log;
import java.nio.IntBuffer;
import java.util.LinkedList;
@ -58,7 +63,7 @@ import java.util.LinkedList;
* This view delegates to LayerRenderer to actually do the drawing. Its role is largely that of a
* mediator between the LayerRenderer and the LayerController.
*/
public class LayerView extends GLSurfaceView {
public class LayerView extends FlexibleGLSurfaceView {
private Context mContext;
private LayerController mController;
private InputConnectionHandler mInputConnectionHandler;
@ -79,7 +84,6 @@ public class LayerView extends GLSurfaceView {
mController = controller;
mRenderer = new LayerRenderer(this);
setRenderer(mRenderer);
setRenderMode(RENDERMODE_WHEN_DIRTY);
mGestureDetector = new GestureDetector(context, controller.getGestureListener());
mScaleGestureDetector =
new SimpleScaleGestureDetector(controller.getScaleGestureListener());
@ -88,6 +92,8 @@ public class LayerView extends GLSurfaceView {
setFocusable(true);
setFocusableInTouchMode(true);
createGLThread();
}
private void addToEventQueue(MotionEvent event) {
@ -133,8 +139,10 @@ public class LayerView extends GLSurfaceView {
mController.setViewportSize(new FloatSize(size));
}
public void setInputConnectionHandler(InputConnectionHandler handler) {
mInputConnectionHandler = handler;
public GeckoInputConnection setInputConnectionHandler() {
GeckoInputConnection geckoInputConnection = GeckoInputConnection.create(this);
mInputConnectionHandler = geckoInputConnection;
return geckoInputConnection;
}
@Override
@ -218,5 +226,14 @@ public class LayerView extends GLSurfaceView {
public IntBuffer getPixels() {
return mRenderer.getPixels();
}
public void setLayerRenderer(LayerRenderer renderer) {
mRenderer = renderer;
setRenderer(mRenderer);
}
public LayerRenderer getLayerRenderer() {
return mRenderer;
}
}

View File

@ -1,303 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011-2012
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Chris Lord <chrislord.net@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.CairoImage;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.SingleTileLayer;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.util.Log;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import javax.microedition.khronos.opengles.GL10;
/**
* Encapsulates the logic needed to draw a layer made of multiple tiles.
*
* TODO: Support repeating.
*/
public class MultiTileLayer extends Layer {
private static final String LOGTAG = "GeckoMultiTileLayer";
private final CairoImage mImage;
private IntSize mTileSize;
private IntSize mBufferSize;
private final ArrayList<SubTile> mTiles;
public MultiTileLayer(CairoImage image, IntSize tileSize) {
super();
mImage = image;
mTileSize = tileSize;
mBufferSize = new IntSize(0, 0);
mTiles = new ArrayList<SubTile>();
}
public void invalidate(Rect dirtyRect) {
if (!inTransaction()) {
throw new RuntimeException("invalidate() is only valid inside a transaction");
}
for (SubTile layer : mTiles) {
IntSize tileSize = layer.getSize();
Rect tileRect = new Rect(layer.x, layer.y, layer.x + tileSize.width, layer.y + tileSize.height);
if (tileRect.intersect(dirtyRect)) {
tileRect.offset(-layer.x, -layer.y);
layer.invalidate(tileRect);
}
}
}
public void invalidate() {
for (SubTile layer : mTiles) {
layer.invalidate();
}
}
@Override
public IntSize getSize() {
return mImage.getSize();
}
private void validateTiles() {
IntSize size = getSize();
if (size.equals(mBufferSize)) {
return;
}
// Regenerate tiles
mTiles.clear();
int offset = 0;
final int format = mImage.getFormat();
final ByteBuffer buffer = mImage.getBuffer().slice();
final int bpp = CairoUtils.bitsPerPixelForCairoFormat(format) / 8;
for (int y = 0; y < size.height; y += mTileSize.height) {
for (int x = 0; x < size.width; x += mTileSize.width) {
// Create a CairoImage implementation that returns a
// tile from the parent CairoImage. It's assumed that
// the tiles are stored in series.
final IntSize layerSize =
new IntSize(Math.min(mTileSize.width, size.width - x),
Math.min(mTileSize.height, size.height - y));
final int tileOffset = offset;
CairoImage subImage = new CairoImage() {
@Override
public ByteBuffer getBuffer() {
// Create a ByteBuffer that shares the data of the original
// buffer, but is positioned and limited so that only the
// tile data is accessible.
buffer.position(tileOffset);
ByteBuffer tileBuffer = buffer.slice();
tileBuffer.limit(layerSize.getArea() * bpp);
return tileBuffer;
}
@Override
public IntSize getSize() {
return layerSize;
}
@Override
public int getFormat() {
return format;
}
};
mTiles.add(new SubTile(subImage, x, y));
offset += layerSize.getArea() * bpp;
}
}
// Set tile origins and resolution
refreshTileMetrics(getOrigin(), getResolution(), false);
mBufferSize = size;
}
@Override
protected boolean performUpdates(GL10 gl, RenderContext context) {
super.performUpdates(gl, context);
validateTiles();
// Iterate over the tiles and decide which ones we'll be drawing
int dirtyTiles = 0;
boolean screenUpdateDone = false;
SubTile firstDirtyTile = null;
for (SubTile layer : mTiles) {
// First do a non-texture update to make sure coordinates are
// up-to-date.
boolean invalid = layer.getSkipTextureUpdate();
layer.setSkipTextureUpdate(true);
layer.performUpdates(gl, context);
RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
boolean isDirty = layer.isDirty();
if (isDirty) {
if (!RectF.intersects(layerBounds, context.viewport)) {
if (firstDirtyTile == null)
firstDirtyTile = layer;
dirtyTiles ++;
invalid = true;
} else {
// This tile intersects with the screen and is dirty,
// update it immediately.
layer.setSkipTextureUpdate(false);
screenUpdateDone = true;
layer.performUpdates(gl, context);
invalid = false;
}
}
// We use the SkipTextureUpdate flag as a marker of a tile's
// validity. This is required, as sometimes layers are drawn
// without updating first, and we mustn't draw tiles that have
// been marked as invalid that we haven't updated.
layer.setSkipTextureUpdate(invalid);
}
// Now if no tiles that intersect with the screen were updated, update
// a single tile that doesn't (if there are any). This has the effect
// of spreading out non-critical texture upload over time, and smoothing
// upload-related hitches.
if (!screenUpdateDone && firstDirtyTile != null) {
firstDirtyTile.setSkipTextureUpdate(false);
firstDirtyTile.performUpdates(gl, context);
dirtyTiles --;
}
return (dirtyTiles == 0);
}
private void refreshTileMetrics(Point origin, float resolution, boolean inTransaction) {
IntSize size = getSize();
for (SubTile layer : mTiles) {
if (!inTransaction) {
layer.beginTransaction(null);
}
if (origin != null) {
layer.setOrigin(new Point(origin.x + layer.x, origin.y + layer.y));
}
if (resolution >= 0.0f) {
layer.setResolution(resolution);
}
if (!inTransaction) {
layer.endTransaction();
}
}
}
@Override
public void setOrigin(Point newOrigin) {
super.setOrigin(newOrigin);
refreshTileMetrics(newOrigin, -1, true);
}
@Override
public void setResolution(float newResolution) {
super.setResolution(newResolution);
refreshTileMetrics(null, newResolution, true);
}
@Override
public void beginTransaction(LayerView aView) {
super.beginTransaction(aView);
for (SubTile layer : mTiles) {
layer.beginTransaction(aView);
}
}
@Override
public void endTransaction() {
for (SubTile layer : mTiles) {
layer.endTransaction();
}
super.endTransaction();
}
@Override
public void draw(RenderContext context) {
for (SubTile layer : mTiles) {
// We use the SkipTextureUpdate flag as a validity flag. If it's false,
// the contents of this tile are invalid and we shouldn't draw it.
if (layer.getSkipTextureUpdate())
continue;
// Avoid work, only draw tiles that intersect with the viewport
RectF layerBounds = layer.getBounds(context, new FloatSize(layer.getSize()));
if (RectF.intersects(layerBounds, context.viewport))
layer.draw(context);
}
}
@Override
public Region getValidRegion(RenderContext context) {
Region validRegion = new Region();
for (SubTile tile : mTiles) {
if (tile.getSkipTextureUpdate())
continue;
validRegion.op(tile.getValidRegion(context), Region.Op.UNION);
}
return validRegion;
}
class SubTile extends SingleTileLayer {
public int x;
public int y;
public SubTile(CairoImage mImage, int mX, int mY) {
super(mImage);
x = mX;
y = mY;
}
}
}

View File

@ -20,6 +20,7 @@
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Arkady Blyakher <rkadyb@mit.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -40,11 +41,10 @@ package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.FloatSize;
import android.graphics.PointF;
import android.graphics.RectF;
import android.opengl.GLES11;
import android.opengl.GLES11Ext;
import android.util.Log;
import javax.microedition.khronos.opengles.GL10;
import java.nio.FloatBuffer;
import android.opengl.GLES20;
/**
* Encapsulates the logic needed to draw a nine-patch bitmap using OpenGL ES.
@ -54,7 +54,7 @@ import java.nio.FloatBuffer;
*/
public class NinePatchTileLayer extends TileLayer {
private static final int PATCH_SIZE = 16;
private static final int TEXTURE_SIZE = 48;
private static final int TEXTURE_SIZE = 64;
public NinePatchTileLayer(CairoImage image) {
super(false, image);
@ -65,14 +65,11 @@ public class NinePatchTileLayer extends TileLayer {
if (!initialized())
return;
GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
GLES11.glEnable(GL10.GL_BLEND);
try {
GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
drawPatches(context);
} finally {
GLES11.glDisable(GL10.GL_BLEND);
}
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
drawPatches(context);
}
private void drawPatches(RenderContext context) {
@ -91,34 +88,77 @@ public class NinePatchTileLayer extends TileLayer {
FloatSize size = context.pageSize;
float width = size.width, height = size.height;
drawPatch(context, 0, 0, /* 0 */
drawPatch(context, 0, PATCH_SIZE * 3, /* 0 */
0.0f, 0.0f, PATCH_SIZE, PATCH_SIZE);
drawPatch(context, PATCH_SIZE, 0, /* 1 */
drawPatch(context, PATCH_SIZE, PATCH_SIZE*3, /* 1 */
PATCH_SIZE, 0.0f, width, PATCH_SIZE);
drawPatch(context, PATCH_SIZE * 2, 0, /* 2 */
drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE*3, /* 2 */
PATCH_SIZE + width, 0.0f, PATCH_SIZE, PATCH_SIZE);
drawPatch(context, 0, PATCH_SIZE, /* 3 */
drawPatch(context, 0, PATCH_SIZE * 2, /* 3 */
0.0f, PATCH_SIZE, PATCH_SIZE, height);
drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE, /* 4 */
drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE * 2, /* 4 */
PATCH_SIZE + width, PATCH_SIZE, PATCH_SIZE, height);
drawPatch(context, 0, PATCH_SIZE * 2, /* 5 */
drawPatch(context, 0, PATCH_SIZE, /* 5 */
0.0f, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE);
drawPatch(context, PATCH_SIZE, PATCH_SIZE * 2, /* 6 */
drawPatch(context, PATCH_SIZE, PATCH_SIZE, /* 6 */
PATCH_SIZE, PATCH_SIZE + height, width, PATCH_SIZE);
drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE * 2, /* 7 */
drawPatch(context, PATCH_SIZE * 2, PATCH_SIZE, /* 7 */
PATCH_SIZE + width, PATCH_SIZE + height, PATCH_SIZE, PATCH_SIZE);
}
private void drawPatch(RenderContext context, int textureX, int textureY, float tileX,
float tileY, float tileWidth, float tileHeight) {
int[] cropRect = { textureX, textureY + PATCH_SIZE, PATCH_SIZE, -PATCH_SIZE };
GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
0);
private void drawPatch(RenderContext context, int textureX, int textureY,
float tileX, float tileY, float tileWidth, float tileHeight) {
RectF viewport = context.viewport;
float viewportHeight = viewport.height();
float drawX = tileX - viewport.left - PATCH_SIZE;
float drawY = viewportHeight - (tileY + tileHeight - viewport.top - PATCH_SIZE);
GLES11Ext.glDrawTexfOES(drawX, drawY, 0.0f, tileWidth, tileHeight);
float[] coords = {
//x, y, z, texture_x, texture_y
drawX/viewport.width(), drawY/viewport.height(), 0,
textureX/(float)TEXTURE_SIZE, textureY/(float)TEXTURE_SIZE,
drawX/viewport.width(), (drawY+tileHeight)/viewport.height(), 0,
textureX/(float)TEXTURE_SIZE, (textureY+PATCH_SIZE)/(float)TEXTURE_SIZE,
(drawX+tileWidth)/viewport.width(), drawY/viewport.height(), 0,
(textureX+PATCH_SIZE)/(float)TEXTURE_SIZE, textureY/(float)TEXTURE_SIZE,
(drawX+tileWidth)/viewport.width(), (drawY+tileHeight)/viewport.height(), 0,
(textureX+PATCH_SIZE)/(float)TEXTURE_SIZE, (textureY+PATCH_SIZE)/(float)TEXTURE_SIZE
};
// Get the buffer and handles from the context
FloatBuffer coordBuffer = context.coordBuffer;
int positionHandle = context.positionHandle;
int textureHandle = context.textureHandle;
// Make sure we are at position zero in the buffer in case other draw methods did not clean
// up after themselves
coordBuffer.position(0);
coordBuffer.put(coords);
// Vertex coordinates are x,y,z starting at position 0 into the buffer.
coordBuffer.position(0);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
// Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
coordBuffer.position(3);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S,
GLES20.GL_CLAMP_TO_EDGE);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T,
GLES20.GL_CLAMP_TO_EDGE);
// Use bilinear filtering for both magnification and minimization of the texture. This
// applies only to the shadow layer so we do not incur a high overhead.
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_LINEAR);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
}

View File

@ -37,24 +37,15 @@
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.BufferedCairoImage;
import org.mozilla.gecko.gfx.CairoUtils;
import org.mozilla.gecko.gfx.FloatSize;
import org.mozilla.gecko.gfx.LayerClient;
import org.mozilla.gecko.gfx.PointUtils;
import org.mozilla.gecko.gfx.SingleTileLayer;
import org.mozilla.gecko.GeckoApp;
import org.mozilla.gecko.GeckoAppShell;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.PointF;
import android.graphics.RectF;
import android.os.Environment;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.File;
import java.io.ByteArrayInputStream;
import java.nio.ByteBuffer;
@ -62,22 +53,23 @@ import java.nio.ByteBuffer;
* A stand-in for Gecko that renders cached content of the previous page. We use this until Gecko
* is up, then we hand off control to it.
*/
public class PlaceholderLayerClient extends LayerClient {
public class PlaceholderLayerClient {
private static final String LOGTAG = "PlaceholderLayerClient";
private Context mContext;
private final LayerController mLayerController;
private ViewportMetrics mViewport;
private boolean mViewportUnknown;
private int mWidth, mHeight, mFormat;
private ByteBuffer mBuffer;
private PlaceholderLayerClient(Context context) {
mContext = context;
String viewport = GeckoApp.mAppContext.getLastViewport();
public PlaceholderLayerClient(LayerController controller, String lastViewport) {
mLayerController = controller;
mViewportUnknown = true;
if (viewport != null) {
if (lastViewport != null) {
try {
JSONObject viewportObject = new JSONObject(viewport);
JSONObject viewportObject = new JSONObject(lastViewport);
mViewport = new ViewportMetrics(viewportObject);
mViewportUnknown = false;
} catch (JSONException e) {
@ -88,10 +80,24 @@ public class PlaceholderLayerClient extends LayerClient {
mViewport = new ViewportMetrics();
}
loadScreenshot();
}
public static PlaceholderLayerClient createInstance(Context context) {
return new PlaceholderLayerClient(context);
if (mViewportUnknown)
mViewport.setViewport(mLayerController.getViewport());
mLayerController.setViewportMetrics(mViewport);
BufferedCairoImage image = new BufferedCairoImage(mBuffer, mWidth, mHeight, mFormat);
SingleTileLayer tileLayer = new SingleTileLayer(image);
tileLayer.beginTransaction(); // calling thread irrelevant; nobody else has a ref to tileLayer yet
try {
Point origin = PointUtils.round(mViewport.getOrigin());
tileLayer.setPosition(new Rect(origin.x, origin.y, origin.x + mWidth, origin.y + mHeight));
} finally {
tileLayer.endTransaction();
}
mLayerController.setRoot(tileLayer);
}
public void destroy() {
@ -104,6 +110,7 @@ public class PlaceholderLayerClient extends LayerClient {
boolean loadScreenshot() {
if (GeckoApp.mAppContext.mLastScreen == null)
return false;
Bitmap bitmap = BitmapFactory.decodeStream(new ByteArrayInputStream(GeckoApp.mAppContext.mLastScreen));
if (bitmap == null)
return false;
@ -121,35 +128,9 @@ public class PlaceholderLayerClient extends LayerClient {
if (mViewportUnknown) {
mViewport.setPageSize(new FloatSize(mWidth, mHeight));
if (getLayerController() != null)
getLayerController().setPageSize(mViewport.getPageSize());
mLayerController.setPageSize(mViewport.getPageSize());
}
return true;
}
@Override
public void geometryChanged() { /* no-op */ }
@Override
public void viewportSizeChanged() { /* no-op */ }
@Override
public void render() { /* no-op */ }
@Override
public void setLayerController(LayerController layerController) {
super.setLayerController(layerController);
if (mViewportUnknown)
mViewport.setViewport(layerController.getViewport());
layerController.setViewportMetrics(mViewport);
BufferedCairoImage image = new BufferedCairoImage(mBuffer, mWidth, mHeight, mFormat);
SingleTileLayer tileLayer = new SingleTileLayer(image);
beginTransaction(tileLayer);
tileLayer.setOrigin(PointUtils.round(mViewport.getDisplayportOrigin()));
endTransaction(tileLayer);
layerController.setRoot(tileLayer);
}
}

View File

@ -58,6 +58,16 @@ public final class RectUtils {
}
}
public static String toJSON(RectF rect) {
StringBuffer sb = new StringBuffer(256);
sb.append("{ \"left\": ").append(rect.left)
.append(", \"top\": ").append(rect.top)
.append(", \"right\": ").append(rect.right)
.append(", \"bottom\": ").append(rect.bottom)
.append('}');
return sb.toString();
}
public static Rect contract(Rect rect, int lessWidth, int lessHeight) {
float halfLessWidth = lessWidth / 2.0f;
float halfLessHeight = lessHeight / 2.0f;

View File

@ -20,6 +20,7 @@
*
* Contributor(s):
* Kartikaya Gupta <kgupta@mozilla.com>
* Arkady Blyakher <rkadyb@mit.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -45,11 +46,10 @@ import android.graphics.PointF;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLES11;
import android.opengl.GLES11Ext;
import android.opengl.GLES20;
import android.util.Log;
import java.nio.ByteBuffer;
import javax.microedition.khronos.opengles.GL10;
import java.nio.FloatBuffer;
import org.mozilla.gecko.FloatUtils;
import org.mozilla.gecko.GeckoAppShell;
@ -64,12 +64,6 @@ public class ScrollbarLayer extends TileLayer {
private static final int BAR_SIZE = 6;
private static final int CAP_RADIUS = (BAR_SIZE / 2);
private static final int[] CROPRECT_MIDPIXEL = new int[] { CAP_RADIUS, CAP_RADIUS, 1, 1 };
private static final int[] CROPRECT_TOPCAP = new int[] { 0, CAP_RADIUS, BAR_SIZE, -CAP_RADIUS };
private static final int[] CROPRECT_BOTTOMCAP = new int[] { 0, BAR_SIZE, BAR_SIZE, -CAP_RADIUS };
private static final int[] CROPRECT_LEFTCAP = new int[] { 0, BAR_SIZE, CAP_RADIUS, -BAR_SIZE };
private static final int[] CROPRECT_RIGHTCAP = new int[] { CAP_RADIUS, BAR_SIZE, CAP_RADIUS, -BAR_SIZE };
private final boolean mVertical;
private final ByteBuffer mBuffer;
private final Bitmap mBitmap;
@ -77,14 +71,95 @@ public class ScrollbarLayer extends TileLayer {
private float mOpacity;
private boolean mFinalized = false;
private ScrollbarLayer(CairoImage image, boolean vertical, ByteBuffer buffer) {
private LayerRenderer mRenderer;
private int mProgram;
private int mPositionHandle;
private int mTextureHandle;
private int mSampleHandle;
private int mTMatrixHandle;
private int mOpacityHandle;
// Fragment shader used to draw the scroll-bar with opacity
private static final String FRAGMENT_SHADER =
"precision mediump float;\n" +
"varying vec2 vTexCoord;\n" +
"uniform sampler2D sTexture;\n" +
"uniform float uOpacity;\n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vec2(vTexCoord.x, 1.0 - vTexCoord.y));\n" +
" gl_FragColor.a *= uOpacity;\n" +
"}\n";
// Dimensions of the texture image
private static final float TEX_HEIGHT = 8.0f;
private static final float TEX_WIDTH = 8.0f;
// Texture coordinates for the scrollbar's body
// We take a 1x1 pixel from the center of the image and scale it to become the bar
private static final float[] BODY_TEX_COORDS = {
// x, y
CAP_RADIUS/TEX_WIDTH, CAP_RADIUS/TEX_HEIGHT,
CAP_RADIUS/TEX_WIDTH, (CAP_RADIUS+1)/TEX_HEIGHT,
(CAP_RADIUS+1)/TEX_WIDTH, CAP_RADIUS/TEX_HEIGHT,
(CAP_RADIUS+1)/TEX_WIDTH, (CAP_RADIUS+1)/TEX_HEIGHT
};
// Texture coordinates for the top cap of the scrollbar
private static final float[] TOP_CAP_TEX_COORDS = {
// x, y
0 , 1.0f - CAP_RADIUS/TEX_HEIGHT,
0 , 1.0f,
BAR_SIZE/TEX_WIDTH, 1.0f - CAP_RADIUS/TEX_HEIGHT,
BAR_SIZE/TEX_WIDTH, 1.0f
};
// Texture coordinates for the bottom cap of the scrollbar
private static final float[] BOT_CAP_TEX_COORDS = {
// x, y
0 , 1.0f - BAR_SIZE/TEX_HEIGHT,
0 , 1.0f - CAP_RADIUS/TEX_HEIGHT,
BAR_SIZE/TEX_WIDTH, 1.0f - BAR_SIZE/TEX_HEIGHT,
BAR_SIZE/TEX_WIDTH, 1.0f - CAP_RADIUS/TEX_HEIGHT
};
// Texture coordinates for the left cap of the scrollbar
private static final float[] LEFT_CAP_TEX_COORDS = {
// x, y
0 , 1.0f - BAR_SIZE/TEX_HEIGHT,
0 , 1.0f,
CAP_RADIUS/TEX_WIDTH, 1.0f - BAR_SIZE/TEX_HEIGHT,
CAP_RADIUS/TEX_WIDTH, 1.0f
};
// Texture coordinates for the right cap of the scrollbar
private static final float[] RIGHT_CAP_TEX_COORDS = {
// x, y
CAP_RADIUS/TEX_WIDTH, 1.0f - BAR_SIZE/TEX_HEIGHT,
CAP_RADIUS/TEX_WIDTH, 1.0f,
BAR_SIZE/TEX_WIDTH , 1.0f - BAR_SIZE/TEX_HEIGHT,
BAR_SIZE/TEX_WIDTH , 1.0f
};
private ScrollbarLayer(LayerRenderer renderer, CairoImage image, boolean vertical, ByteBuffer buffer) {
super(false, image);
mVertical = vertical;
mBuffer = buffer;
mRenderer = renderer;
IntSize size = image.getSize();
mBitmap = Bitmap.createBitmap(size.width, size.height, Bitmap.Config.ARGB_8888);
mCanvas = new Canvas(mBitmap);
// Paint a spot to use as the scroll indicator
Paint foregroundPaint = new Paint();
foregroundPaint.setAntiAlias(true);
foregroundPaint.setStyle(Paint.Style.FILL);
foregroundPaint.setColor(Color.argb(127, 0, 0, 0));
mCanvas.drawColor(Color.argb(0, 0, 0, 0), PorterDuff.Mode.CLEAR);
mCanvas.drawCircle(CAP_RADIUS, CAP_RADIUS, CAP_RADIUS, foregroundPaint);
mBitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
}
protected void finalize() throws Throwable {
@ -97,14 +172,55 @@ public class ScrollbarLayer extends TileLayer {
}
}
public static ScrollbarLayer create(boolean vertical) {
public static ScrollbarLayer create(LayerRenderer renderer, boolean vertical) {
// just create an empty image for now, it will get drawn
// on demand anyway
int imageSize = IntSize.nextPowerOfTwo(BAR_SIZE);
ByteBuffer buffer = GeckoAppShell.allocateDirectBuffer(imageSize * imageSize * 4);
CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize, CairoImage.FORMAT_ARGB32);
return new ScrollbarLayer(image, vertical, buffer);
CairoImage image = new BufferedCairoImage(buffer, imageSize, imageSize,
CairoImage.FORMAT_ARGB32);
return new ScrollbarLayer(renderer, image, vertical, buffer);
}
private void createProgram() {
int vertexShader = LayerRenderer.loadShader(GLES20.GL_VERTEX_SHADER,
LayerRenderer.DEFAULT_VERTEX_SHADER);
int fragmentShader = LayerRenderer.loadShader(GLES20.GL_FRAGMENT_SHADER,
FRAGMENT_SHADER);
mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, vertexShader); // add the vertex shader to program
GLES20.glAttachShader(mProgram, fragmentShader); // add the fragment shader to program
GLES20.glLinkProgram(mProgram); // creates OpenGL program executables
// Get handles to the shaders' vPosition, aTexCoord, sTexture, and uTMatrix members.
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
mTMatrixHandle = GLES20.glGetUniformLocation(mProgram, "uTMatrix");
mOpacityHandle = GLES20.glGetUniformLocation(mProgram, "uOpacity");
}
private void activateProgram() {
// Add the program to the OpenGL environment
GLES20.glUseProgram(mProgram);
// Set the transformation matrix
GLES20.glUniformMatrix4fv(mTMatrixHandle, 1, false,
LayerRenderer.DEFAULT_TEXTURE_MATRIX, 0);
// Enable the arrays from which we get the vertex and texture coordinates
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glEnableVertexAttribArray(mTextureHandle);
GLES20.glUniform1i(mSampleHandle, 0);
GLES20.glUniform1f(mOpacityHandle, mOpacity);
}
private void deactivateProgram() {
GLES20.glDisableVertexAttribArray(mTextureHandle);
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glUseProgram(0);
}
/**
@ -116,9 +232,9 @@ public class ScrollbarLayer extends TileLayer {
if (FloatUtils.fuzzyEquals(mOpacity, 0.0f)) {
return false;
}
beginTransaction();
beginTransaction(); // called on compositor thread
try {
setOpacity(Math.max(mOpacity - FADE_AMOUNT, 0.0f));
mOpacity = Math.max(mOpacity - FADE_AMOUNT, 0.0f);
invalidate();
} finally {
endTransaction();
@ -135,9 +251,9 @@ public class ScrollbarLayer extends TileLayer {
if (FloatUtils.fuzzyEquals(mOpacity, 1.0f)) {
return false;
}
beginTransaction();
beginTransaction(); // called on compositor thread
try {
setOpacity(1.0f);
mOpacity = 1.0f;
invalidate();
} finally {
endTransaction();
@ -145,58 +261,213 @@ public class ScrollbarLayer extends TileLayer {
return true;
}
private void setOpacity(float opacity) {
mOpacity = opacity;
Paint foregroundPaint = new Paint();
foregroundPaint.setAntiAlias(true);
foregroundPaint.setStyle(Paint.Style.FILL);
// use a (a,r,g,b) color of (127,0,0,0), and multiply the alpha by mOpacity for fading
foregroundPaint.setColor(Color.argb(Math.round(mOpacity * 127), 0, 0, 0));
mCanvas.drawColor(Color.argb(0, 0, 0, 0), PorterDuff.Mode.CLEAR);
mCanvas.drawCircle(CAP_RADIUS, CAP_RADIUS, CAP_RADIUS, foregroundPaint);
mBitmap.copyPixelsToBuffer(mBuffer.asIntBuffer());
}
@Override
public void draw(RenderContext context) {
if (!initialized())
return;
try {
GLES11.glEnable(GL10.GL_BLEND);
GLES11.glBlendFunc(GL10.GL_SRC_ALPHA, GL10.GL_ONE_MINUS_SRC_ALPHA);
Rect rect = RectUtils.round(mVertical ? getVerticalRect(context) : getHorizontalRect(context));
GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
float viewHeight = context.viewport.height();
// for the body of the scrollbar, we take a 1x1 pixel from the center of the image
// and scale it to become the bar
GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_MIDPIXEL, 0);
GLES11Ext.glDrawTexfOES(rect.left, viewHeight - rect.bottom, 0.0f, rect.width(), rect.height());
if (mVertical) {
// top endcap
GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_TOPCAP, 0);
GLES11Ext.glDrawTexfOES(rect.left, viewHeight - rect.top, 0.0f, BAR_SIZE, CAP_RADIUS);
// bottom endcap
GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_BOTTOMCAP, 0);
GLES11Ext.glDrawTexfOES(rect.left, viewHeight - (rect.bottom + CAP_RADIUS), 0.0f, BAR_SIZE, CAP_RADIUS);
} else {
// left endcap
GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_LEFTCAP, 0);
GLES11Ext.glDrawTexfOES(rect.left - CAP_RADIUS, viewHeight - rect.bottom, 0.0f, CAP_RADIUS, BAR_SIZE);
// right endcap
GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, CROPRECT_RIGHTCAP, 0);
GLES11Ext.glDrawTexfOES(rect.right, viewHeight - rect.bottom, 0.0f, CAP_RADIUS, BAR_SIZE);
}
} finally {
GLES11.glDisable(GL10.GL_BLEND);
// Create the shader program, if necessary
if (mProgram == 0) {
createProgram();
}
// Enable the shader program
mRenderer.deactivateDefaultProgram();
activateProgram();
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_SRC_ALPHA, GLES20.GL_ONE_MINUS_SRC_ALPHA);
Rect rect = RectUtils.round(mVertical
? getVerticalRect(context)
: getHorizontalRect(context));
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
float viewWidth = context.viewport.width();
float viewHeight = context.viewport.height();
float top = viewHeight - rect.top;
float bot = viewHeight - rect.bottom;
// Coordinates for the scrollbar's body combined with the texture coordinates
float[] bodyCoords = {
// x, y, z, texture_x, texture_y
rect.left/viewWidth, bot/viewHeight, 0,
BODY_TEX_COORDS[0], BODY_TEX_COORDS[1],
rect.left/viewWidth, (bot+rect.height())/viewHeight, 0,
BODY_TEX_COORDS[2], BODY_TEX_COORDS[3],
(rect.left+rect.width())/viewWidth, bot/viewHeight, 0,
BODY_TEX_COORDS[4], BODY_TEX_COORDS[5],
(rect.left+rect.width())/viewWidth, (bot+rect.height())/viewHeight, 0,
BODY_TEX_COORDS[6], BODY_TEX_COORDS[7]
};
// Get the buffer and handles from the context
FloatBuffer coordBuffer = context.coordBuffer;
int positionHandle = mPositionHandle;
int textureHandle = mTextureHandle;
// Make sure we are at position zero in the buffer in case other draw methods did not
// clean up after themselves
coordBuffer.position(0);
coordBuffer.put(bodyCoords);
// Vertex coordinates are x,y,z starting at position 0 into the buffer.
coordBuffer.position(0);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
coordBuffer);
// Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
coordBuffer.position(3);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
coordBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
// Reset the position in the buffer for the next set of vertex and texture coordinates.
coordBuffer.position(0);
if (mVertical) {
// top endcap
float[] topCap = {
// x, y, z, texture_x, texture_y
rect.left/viewWidth, top/viewHeight, 0,
TOP_CAP_TEX_COORDS[0], TOP_CAP_TEX_COORDS[1],
rect.left/viewWidth, (top+CAP_RADIUS)/viewHeight, 0,
TOP_CAP_TEX_COORDS[2], TOP_CAP_TEX_COORDS[3],
(rect.left+BAR_SIZE)/viewWidth, top/viewHeight, 0,
TOP_CAP_TEX_COORDS[4], TOP_CAP_TEX_COORDS[5],
(rect.left+BAR_SIZE)/viewWidth, (top+CAP_RADIUS)/viewHeight, 0,
TOP_CAP_TEX_COORDS[6], TOP_CAP_TEX_COORDS[7]
};
coordBuffer.put(topCap);
// Vertex coordinates are x,y,z starting at position 0 into the buffer.
coordBuffer.position(0);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
coordBuffer);
// Texture coordinates are texture_x, texture_y starting at position 3 into the
// buffer.
coordBuffer.position(3);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
coordBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
// Reset the position in the buffer for the next set of vertex and texture
// coordinates.
coordBuffer.position(0);
// bottom endcap
float[] botCap = {
// x, y, z, texture_x, texture_y
rect.left/viewWidth, (bot-CAP_RADIUS)/viewHeight, 0,
BOT_CAP_TEX_COORDS[0], BOT_CAP_TEX_COORDS[1],
rect.left/viewWidth, (bot)/viewHeight, 0,
BOT_CAP_TEX_COORDS[2], BOT_CAP_TEX_COORDS[3],
(rect.left+BAR_SIZE)/viewWidth, (bot-CAP_RADIUS)/viewHeight, 0,
BOT_CAP_TEX_COORDS[4], BOT_CAP_TEX_COORDS[5],
(rect.left+BAR_SIZE)/viewWidth, (bot)/viewHeight, 0,
BOT_CAP_TEX_COORDS[6], BOT_CAP_TEX_COORDS[7]
};
coordBuffer.put(botCap);
// Vertex coordinates are x,y,z starting at position 0 into the buffer.
coordBuffer.position(0);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
coordBuffer);
// Texture coordinates are texture_x, texture_y starting at position 3 into the
// buffer.
coordBuffer.position(3);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
coordBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
// Reset the position in the buffer for the next set of vertex and texture
// coordinates.
coordBuffer.position(0);
} else {
// left endcap
float[] leftCap = {
// x, y, z, texture_x, texture_y
(rect.left-CAP_RADIUS)/viewWidth, bot/viewHeight, 0,
LEFT_CAP_TEX_COORDS[0], LEFT_CAP_TEX_COORDS[1],
(rect.left-CAP_RADIUS)/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
LEFT_CAP_TEX_COORDS[2], LEFT_CAP_TEX_COORDS[3],
(rect.left)/viewWidth, bot/viewHeight, 0, LEFT_CAP_TEX_COORDS[4],
LEFT_CAP_TEX_COORDS[5],
(rect.left)/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
LEFT_CAP_TEX_COORDS[6], LEFT_CAP_TEX_COORDS[7]
};
coordBuffer.put(leftCap);
// Vertex coordinates are x,y,z starting at position 0 into the buffer.
coordBuffer.position(0);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
coordBuffer);
// Texture coordinates are texture_x, texture_y starting at position 3 into the
// buffer.
coordBuffer.position(3);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
coordBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
// Reset the position in the buffer for the next set of vertex and texture
// coordinates.
coordBuffer.position(0);
// right endcap
float[] rightCap = {
// x, y, z, texture_x, texture_y
rect.right/viewWidth, (bot)/viewHeight, 0,
RIGHT_CAP_TEX_COORDS[0], RIGHT_CAP_TEX_COORDS[1],
rect.right/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
RIGHT_CAP_TEX_COORDS[2], RIGHT_CAP_TEX_COORDS[3],
(rect.right+CAP_RADIUS)/viewWidth, (bot)/viewHeight, 0,
RIGHT_CAP_TEX_COORDS[4], RIGHT_CAP_TEX_COORDS[5],
(rect.right+CAP_RADIUS)/viewWidth, (bot+BAR_SIZE)/viewHeight, 0,
RIGHT_CAP_TEX_COORDS[6], RIGHT_CAP_TEX_COORDS[7]
};
coordBuffer.put(rightCap);
// Vertex coordinates are x,y,z starting at position 0 into the buffer.
coordBuffer.position(0);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20,
coordBuffer);
// Texture coordinates are texture_x, texture_y starting at position 3 into the
// buffer.
coordBuffer.position(3);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20,
coordBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
// Enable the default shader program again
deactivateProgram();
mRenderer.activateDefaultProgram();
}
private RectF getVerticalRect(RenderContext context) {

View File

@ -20,6 +20,7 @@
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Arkady Blyakher <rkadyb@mit.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -42,13 +43,12 @@ import org.mozilla.gecko.gfx.CairoUtils;
import org.mozilla.gecko.gfx.IntSize;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.TileLayer;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.opengl.GLES11;
import android.opengl.GLES11Ext;
import android.graphics.Region;
import android.graphics.RegionIterator;
import android.opengl.GLES20;
import android.util.Log;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import javax.microedition.khronos.opengles.GL10;
@ -58,12 +58,23 @@ import javax.microedition.khronos.opengles.GL10;
* TODO: Repeating textures really should be their own type of layer.
*/
public class SingleTileLayer extends TileLayer {
private static final String LOGTAG = "GeckoSingleTileLayer";
private Rect mMask;
public SingleTileLayer(CairoImage image) { this(false, image); }
public SingleTileLayer(boolean repeat, CairoImage image) {
super(repeat, image);
}
/**
* Set an area to mask out when rendering.
*/
public void setMask(Rect aMaskRect) {
mMask = aMaskRect;
}
@Override
public void draw(RenderContext context) {
// mTextureIDs may be null here during startup if Layer.java's draw method
@ -71,31 +82,81 @@ public class SingleTileLayer extends TileLayer {
if (!initialized())
return;
GLES11.glBindTexture(GL10.GL_TEXTURE_2D, getTextureID());
RectF bounds;
int[] cropRect;
IntSize size = getSize();
Rect position = getPosition();
RectF viewport = context.viewport;
if (repeats()) {
bounds = new RectF(0.0f, 0.0f, viewport.width(), viewport.height());
int width = Math.round(viewport.width());
int height = Math.round(-viewport.height());
cropRect = new int[] { 0, size.height, width, height };
int height = Math.round(viewport.height());
} else {
bounds = getBounds(context, new FloatSize(size));
cropRect = new int[] { 0, size.height, size.width, -size.height };
bounds = getBounds(context);
}
GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
0);
Rect intBounds = new Rect();
bounds.roundOut(intBounds);
Region maskedBounds = new Region(intBounds);
if (mMask != null) {
maskedBounds.op(mMask, Region.Op.DIFFERENCE);
if (maskedBounds.isEmpty())
return;
}
float height = bounds.height();
float left = bounds.left - viewport.left;
float top = viewport.height() - (bounds.top + height - viewport.top);
// XXX Possible optimisation here, form this array so we can draw it in
// a single call.
RegionIterator i = new RegionIterator(maskedBounds);
for (Rect subRect = new Rect(); i.next(subRect);) {
// Compensate for rounding errors at the edge of the tile caused by
// the roundOut above
RectF subRectF = new RectF(Math.max(bounds.left, (float)subRect.left),
Math.max(bounds.top, (float)subRect.top),
Math.min(bounds.right, (float)subRect.right),
Math.min(bounds.bottom, (float)subRect.bottom));
GLES11Ext.glDrawTexfOES(left, top, 0.0f, bounds.width(), height);
int[] cropRect = new int[] { Math.round(subRectF.left - bounds.left),
Math.round(subRectF.bottom - bounds.top),
Math.round(subRectF.right - bounds.left),
Math.round(subRectF.top - bounds.top) };
float height = subRectF.height();
float left = subRectF.left - viewport.left;
float top = viewport.height() - (subRectF.top + height - viewport.top);
float[] coords = {
//x, y, z, texture_x, texture_y
left/viewport.width(), top/viewport.height(), 0,
cropRect[0]/(float)position.width(), cropRect[1]/(float)position.height(),
left/viewport.width(), (top+height)/viewport.height(), 0,
cropRect[0]/(float)position.width(), cropRect[3]/(float)position.height(),
(left+subRectF.width())/viewport.width(), top/viewport.height(), 0,
cropRect[2]/(float)position.width(), cropRect[1]/(float)position.height(),
(left+subRectF.width())/viewport.width(), (top+height)/viewport.height(), 0,
cropRect[2]/(float)position.width(), cropRect[3]/(float)position.height()
};
FloatBuffer coordBuffer = context.coordBuffer;
int positionHandle = context.positionHandle;
int textureHandle = context.textureHandle;
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, getTextureID());
// Make sure we are at position zero in the buffer
coordBuffer.position(0);
coordBuffer.put(coords);
// Vertex coordinates are x,y,z starting at position 0 into the buffer.
coordBuffer.position(0);
GLES20.glVertexAttribPointer(positionHandle, 3, GLES20.GL_FLOAT, false, 20, coordBuffer);
// Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
coordBuffer.position(3);
GLES20.glVertexAttribPointer(textureHandle, 2, GLES20.GL_FLOAT, false, 20, coordBuffer);
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
}
}
}

View File

@ -43,18 +43,13 @@ import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
import android.opengl.GLES11;
import android.opengl.GLES11Ext;
import android.opengl.Matrix;
import android.opengl.GLES20;
import android.util.Log;
import android.view.Surface;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11Ext;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import android.hardware.Camera;
public class SurfaceTextureLayer extends Layer implements SurfaceTexture.OnFrameAvailableListener {
private static final String LOGTAG = "SurfaceTextureLayer";
@ -64,19 +59,63 @@ public class SurfaceTextureLayer extends Layer implements SurfaceTexture.OnFrame
private final Surface mSurface;
private int mTextureId;
private boolean mHaveFrame;
private IntSize mSize;
private IntSize mNewSize;
private float[] mTextureTransform = new float[16];
private boolean mInverted;
private boolean mNewInverted;
private boolean mBlend;
private boolean mNewBlend;
private FloatBuffer textureBuffer;
private FloatBuffer textureBufferInverted;
private static int mProgram;
private static int mPositionHandle;
private static int mTextureHandle;
private static int mSampleHandle;
private static int mProjectionMatrixHandle;
private static int mTextureMatrixHandle;
public SurfaceTextureLayer(int textureId) {
private static final float[] PROJECTION_MATRIX = {
2.0f, 0.0f, 0.0f, 0.0f,
0.0f, 2.0f, 0.0f, 0.0f,
0.0f, 0.0f, 2.0f, 0.0f,
-1.0f, -1.0f, 0.0f, 1.0f
};
private static final String VERTEX_SHADER =
"uniform mat4 projectionMatrix;\n" +
"uniform mat4 textureMatrix;\n" +
"attribute vec4 vPosition;\n" +
"attribute vec4 aTexCoord;\n" +
"varying vec2 vTexCoord;\n" +
"void main() {\n" +
" gl_Position = projectionMatrix * vPosition;\n" +
" vTexCoord = (textureMatrix * vec4(aTexCoord.x, aTexCoord.y, 0.0, 1.0)).xy;\n" +
"}\n";
private static String FRAGMENT_SHADER_OES =
"#extension GL_OES_EGL_image_external : require\n" +
"precision mediump float;\n" +
"varying vec2 vTexCoord; \n" +
"uniform samplerExternalOES sTexture; \n" +
"void main() {\n" +
" gl_FragColor = texture2D(sTexture, vTexCoord); \n" +
"}\n";
private static final float TEXTURE_MAP[] = {
0.0f, 1.0f, // top left
0.0f, 0.0f, // bottom left
1.0f, 1.0f, // top right
1.0f, 0.0f, // bottom right
};
private static final float TEXTURE_MAP_INVERTED[] = {
0.0f, 0.0f, // bottom left
0.0f, 1.0f, // top left
1.0f, 0.0f, // bottom right
1.0f, 1.0f, // top right
};
private SurfaceTextureLayer(int textureId) {
mTextureId = textureId;
mHaveFrame = true;
mInverted = false;
@ -92,24 +131,6 @@ public class SurfaceTextureLayer extends Layer implements SurfaceTexture.OnFrame
}
mSurface = tmp;
float textureMap[] = {
0.0f, 1.0f, // top left
0.0f, 0.0f, // bottom left
1.0f, 1.0f, // top right
1.0f, 0.0f, // bottom right
};
textureBuffer = createBuffer(textureMap);
float textureMapInverted[] = {
0.0f, 0.0f, // bottom left
0.0f, 1.0f, // top left
1.0f, 0.0f, // bottom right
1.0f, 1.0f, // top right
};
textureBufferInverted = createBuffer(textureMapInverted);
}
public static SurfaceTextureLayer create() {
@ -122,39 +143,22 @@ public class SurfaceTextureLayer extends Layer implements SurfaceTexture.OnFrame
// For SurfaceTexture.OnFrameAvailableListener
public void onFrameAvailable(SurfaceTexture texture) {
// FIXME: for some reason this doesn't get called
mHaveFrame = true;
GeckoApp.mAppContext.requestRender();
}
private FloatBuffer createBuffer(float[] input) {
// a float has 4 bytes so we allocate for each coordinate 4 bytes
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(input.length * 4);
byteBuffer.order(ByteOrder.nativeOrder());
public void update(Rect position, float resolution, boolean inverted, boolean blend) {
beginTransaction(); // this is called on the Gecko thread
FloatBuffer floatBuffer = byteBuffer.asFloatBuffer();
floatBuffer.put(input);
floatBuffer.position(0);
return floatBuffer;
}
public void update(Point origin, IntSize size, float resolution, boolean inverted, boolean blend) {
beginTransaction(null);
setOrigin(origin);
setPosition(position);
setResolution(resolution);
mNewSize = size;
mNewInverted = inverted;
mNewBlend = blend;
endTransaction();
}
@Override
public IntSize getSize() { return mSize; }
@Override
protected void finalize() throws Throwable {
if (mSurfaceTexture != null) {
@ -173,94 +177,129 @@ public class SurfaceTextureLayer extends Layer implements SurfaceTexture.OnFrame
}
@Override
protected boolean performUpdates(GL10 gl, RenderContext context) {
super.performUpdates(gl, context);
if (mNewSize != null) {
mSize = mNewSize;
mNewSize = null;
}
protected boolean performUpdates(RenderContext context) {
super.performUpdates(context);
mInverted = mNewInverted;
mBlend = mNewBlend;
gl.glEnable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
gl.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
mSurfaceTexture.updateTexImage();
gl.glDisable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
// FIXME: we should return true and rely on onFrameAvailable, but
// that isn't working for some reason
return false;
return true;
}
private float mapToGLCoords(float input, float viewport, boolean flip) {
if (flip) input = viewport - input;
return ((input / viewport) * 2.0f) - 1.0f;
private static boolean ensureProgram() {
if (mProgram != 0)
return true;
int vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER);
int fragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FRAGMENT_SHADER_OES);
mProgram = GLES20.glCreateProgram();
GLES20.glAttachShader(mProgram, vertexShader);
GLES20.glAttachShader(mProgram, fragmentShader);
GLES20.glLinkProgram(mProgram);
mPositionHandle = GLES20.glGetAttribLocation(mProgram, "vPosition");
mTextureHandle = GLES20.glGetAttribLocation(mProgram, "aTexCoord");
mSampleHandle = GLES20.glGetUniformLocation(mProgram, "sTexture");
mProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgram, "projectionMatrix");
mTextureMatrixHandle = GLES20.glGetUniformLocation(mProgram, "textureMatrix");
return mProgram != 0;
}
private static int loadShader(int type, String shaderCode) {
int shader = GLES20.glCreateShader(type);
GLES20.glShaderSource(shader, shaderCode);
GLES20.glCompileShader(shader);
return shader;
}
private static void activateProgram() {
GLES20.glUseProgram(mProgram);
}
public static void deactivateProgram() {
GLES20.glDisableVertexAttribArray(mTextureHandle);
GLES20.glDisableVertexAttribArray(mPositionHandle);
GLES20.glUseProgram(0);
}
@Override
public void draw(RenderContext context) {
if (!ensureProgram() || !mHaveFrame)
return;
// Enable GL_TEXTURE_EXTERNAL_OES and bind our texture
GLES11.glEnable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
GLES11.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
// Enable vertex and texture coordinate buffers
GLES11.glEnableClientState(GL10.GL_VERTEX_ARRAY);
GLES11.glEnableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
// Load whatever texture transform the SurfaceMatrix needs
float[] matrix = new float[16];
mSurfaceTexture.getTransformMatrix(matrix);
GLES11.glMatrixMode(GLES11.GL_TEXTURE);
GLES11.glLoadMatrixf(matrix, 0);
// Figure out vertices to put the texture in the right spot on the screen
IntSize size = getSize();
RectF bounds = getBounds(context, new FloatSize(size));
RectF rect = getBounds(context);
RectF viewport = context.viewport;
bounds.offset(-viewport.left, -viewport.top);
rect.offset(-viewport.left, -viewport.top);
float vertices[] = new float[8];
float viewWidth = viewport.width();
float viewHeight = viewport.height();
// Bottom left
vertices[0] = mapToGLCoords(bounds.left, viewport.width(), false);
vertices[1] = mapToGLCoords(bounds.bottom, viewport.height(), true);
float top = viewHeight - rect.top;
float bot = viewHeight - rect.bottom;
// Top left
vertices[2] = mapToGLCoords(bounds.left, viewport.width(), false);
vertices[3] = mapToGLCoords(bounds.top, viewport.height(), true);
float[] textureCoords = mInverted ? TEXTURE_MAP_INVERTED : TEXTURE_MAP;
// Bottom right
vertices[4] = mapToGLCoords(bounds.right, viewport.width(), false);
vertices[5] = mapToGLCoords(bounds.bottom, viewport.height(), true);
// Coordinates for the scrollbar's body combined with the texture coordinates
float[] coords = {
// x, y, z, texture_x, texture_y
rect.left/viewWidth, bot/viewHeight, 0,
textureCoords[0], textureCoords[1],
// Top right
vertices[6] = mapToGLCoords(bounds.right, viewport.width(), false);
vertices[7] = mapToGLCoords(bounds.top, viewport.height(), true);
rect.left/viewWidth, (bot+rect.height())/viewHeight, 0,
textureCoords[2], textureCoords[3],
// Set texture and vertex buffers
GLES11.glVertexPointer(2, GL10.GL_FLOAT, 0, createBuffer(vertices));
GLES11.glTexCoordPointer(2, GL10.GL_FLOAT, 0, mInverted ? textureBufferInverted : textureBuffer);
(rect.left+rect.width())/viewWidth, bot/viewHeight, 0,
textureCoords[4], textureCoords[5],
(rect.left+rect.width())/viewWidth, (bot+rect.height())/viewHeight, 0,
textureCoords[6], textureCoords[7]
};
FloatBuffer coordBuffer = context.coordBuffer;
coordBuffer.position(0);
coordBuffer.put(coords);
activateProgram();
// Set the transformation matrix
GLES20.glUniformMatrix4fv(mProjectionMatrixHandle, 1, false, PROJECTION_MATRIX, 0);
// Enable the arrays from which we get the vertex and texture coordinates
GLES20.glEnableVertexAttribArray(mPositionHandle);
GLES20.glEnableVertexAttribArray(mTextureHandle);
GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
GLES20.glUniform1i(mSampleHandle, 0);
GLES20.glBindTexture(LOCAL_GL_TEXTURE_EXTERNAL_OES, mTextureId);
mSurfaceTexture.updateTexImage();
mSurfaceTexture.getTransformMatrix(mTextureTransform);
GLES20.glUniformMatrix4fv(mTextureMatrixHandle, 1, false, mTextureTransform, 0);
// Vertex coordinates are x,y,z starting at position 0 into the buffer.
coordBuffer.position(0);
GLES20.glVertexAttribPointer(mPositionHandle, 3, GLES20.GL_FLOAT, false, 20,
coordBuffer);
// Texture coordinates are texture_x, texture_y starting at position 3 into the buffer.
coordBuffer.position(3);
GLES20.glVertexAttribPointer(mTextureHandle, 3, GLES20.GL_FLOAT, false, 20,
coordBuffer);
if (mBlend) {
GLES11.glEnable(GL10.GL_BLEND);
GLES11.glBlendFunc(GL10.GL_ONE, GL10.GL_ONE_MINUS_SRC_ALPHA);
GLES20.glEnable(GLES20.GL_BLEND);
GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA);
}
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, 0, 4);
// Draw the vertices as triangle strip
GLES11.glDrawArrays(GL10.GL_TRIANGLE_STRIP, 0, vertices.length / 2);
// Clean up
GLES11.glDisableClientState(GL10.GL_VERTEX_ARRAY);
GLES11.glDisableClientState(GL10.GL_TEXTURE_COORD_ARRAY);
GLES11.glDisable(LOCAL_GL_TEXTURE_EXTERNAL_OES);
GLES11.glLoadIdentity();
if (mBlend) {
GLES11.glDisable(GL10.GL_BLEND);
}
if (mBlend)
GLES20.glDisable(GLES20.GL_BLEND);
deactivateProgram();
}
public SurfaceTexture getSurfaceTexture() {

View File

@ -37,16 +37,22 @@
package org.mozilla.gecko.gfx;
import android.opengl.GLES10;
import java.util.Stack;
import android.util.Log;
import android.opengl.GLES20;
import java.util.concurrent.ArrayBlockingQueue;
import javax.microedition.khronos.egl.EGL10;
import javax.microedition.khronos.egl.EGLContext;
public class TextureGenerator {
private static final int MIN_TEXTURES = 5;
private static final String LOGTAG = "TextureGenerator";
private static final int POOL_SIZE = 5;
private static TextureGenerator sSharedInstance;
private Stack<Integer> mTextureIds;
private TextureGenerator() { mTextureIds = new Stack<Integer>(); }
private ArrayBlockingQueue<Integer> mTextureIds;
private EGLContext mContext;
private TextureGenerator() { mTextureIds = new ArrayBlockingQueue<Integer>(POOL_SIZE); }
public static TextureGenerator get() {
if (sSharedInstance == null)
@ -55,17 +61,45 @@ public class TextureGenerator {
}
public synchronized int take() {
if (mTextureIds.empty())
try {
// Will block until one becomes available
return (int)mTextureIds.take();
} catch (InterruptedException e) {
return 0;
return (int)mTextureIds.pop();
}
}
public synchronized void fill() {
int[] textures = new int[1];
while (mTextureIds.size() < MIN_TEXTURES) {
GLES10.glGenTextures(1, textures, 0);
mTextureIds.push(textures[0]);
EGL10 egl = (EGL10)EGLContext.getEGL();
EGLContext context = egl.eglGetCurrentContext();
if (mContext != null && mContext != context) {
mTextureIds.clear();
}
mContext = context;
int numNeeded = mTextureIds.remainingCapacity();
if (numNeeded == 0)
return;
// Clear existing GL errors
int error;
while ((error = GLES20.glGetError()) != GLES20.GL_NO_ERROR) {
Log.w(LOGTAG, String.format("Clearing GL error: %#x", error));
}
int[] textures = new int[numNeeded];
GLES20.glGenTextures(numNeeded, textures, 0);
error = GLES20.glGetError();
if (error != GLES20.GL_NO_ERROR) {
Log.e(LOGTAG, String.format("Failed to generate textures: %#x", error), new Exception());
return;
}
for (int i = 0; i < numNeeded; i++) {
mTextureIds.offer(textures[i]);
}
}
}

View File

@ -20,6 +20,7 @@
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Arkady Blyakher <rkadyb@mit.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -37,7 +38,7 @@
package org.mozilla.gecko.gfx;
import javax.microedition.khronos.opengles.GL10;
import android.opengl.GLES20;
import java.util.ArrayList;
/** Manages a list of dead tiles, so we don't leak resources. */
@ -62,13 +63,13 @@ public class TextureReaper {
mDeadTextureIDs.add(textureID);
}
public void reap(GL10 gl) {
public void reap() {
int[] deadTextureIDs = new int[mDeadTextureIDs.size()];
for (int i = 0; i < deadTextureIDs.length; i++)
deadTextureIDs[i] = mDeadTextureIDs.get(i);
mDeadTextureIDs.clear();
gl.glDeleteTextures(deadTextureIDs.length, deadTextureIDs, 0);
GLES20.glDeleteTextures(deadTextureIDs.length, deadTextureIDs, 0);
}
}

View File

@ -20,6 +20,7 @@
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Arkady Blyakher <rkadyb@mit.edu>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
@ -43,8 +44,6 @@ import android.graphics.RectF;
import android.graphics.Region;
import android.opengl.GLES20;
import android.util.Log;
import javax.microedition.khronos.opengles.GL10;
import javax.microedition.khronos.opengles.GL11Ext;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@ -65,18 +64,15 @@ public abstract class TileLayer extends Layer {
private int[] mTextureIDs;
public TileLayer(boolean repeat, CairoImage image) {
super(image.getSize());
mRepeat = repeat;
mImage = image;
mSize = new IntSize(0, 0);
mSkipTextureUpdate = false;
IntSize bufferSize = mImage.getSize();
mDirtyRect = new Rect();
}
@Override
public IntSize getSize() { return mImage.getSize(); }
protected boolean repeats() { return mRepeat; }
protected int getTextureID() { return mTextureIDs[0]; }
protected boolean initialized() { return mImage != null && mTextureIDs != null; }
@ -87,6 +83,14 @@ public abstract class TileLayer extends Layer {
TextureReaper.get().add(mTextureIDs);
}
@Override
public void setPosition(Rect newPosition) {
if (newPosition.width() != mImage.getSize().width || newPosition.height() != mImage.getSize().height) {
throw new RuntimeException("Error: changing the size of a tile layer is not allowed!");
}
super.setPosition(newPosition);
}
/**
* Invalidates the given rect so that it will be uploaded again. Only valid inside a
* transaction.
@ -106,7 +110,7 @@ public abstract class TileLayer extends Layer {
return mImage.getSize().isPositive() && (mTextureIDs == null || !mDirtyRect.isEmpty());
}
private void validateTexture(GL10 gl) {
private void validateTexture() {
/* Calculate the ideal texture size. This must be a power of two if
* the texture is repeated or OpenGL ES 2.0 isn't supported, as
* OpenGL ES 2.0 is required for NPOT texture support (without
@ -129,7 +133,7 @@ public abstract class TileLayer extends Layer {
// Free the texture immediately, so we don't incur a
// temporarily increased memory usage.
TextureReaper.get().reap(gl);
TextureReaper.get().reap();
}
}
}
@ -144,15 +148,15 @@ public abstract class TileLayer extends Layer {
}
@Override
protected boolean performUpdates(GL10 gl, RenderContext context) {
super.performUpdates(gl, context);
protected boolean performUpdates(RenderContext context) {
super.performUpdates(context);
if (mSkipTextureUpdate) {
return false;
}
// Reallocate the texture if the size has changed
validateTexture(gl);
validateTexture();
// Don't do any work if the image has an invalid size.
if (!mImage.getSize().isPositive())
@ -160,9 +164,9 @@ public abstract class TileLayer extends Layer {
// If we haven't allocated a texture, assume the whole region is dirty
if (mTextureIDs == null) {
uploadFullTexture(gl);
uploadFullTexture();
} else {
uploadDirtyRect(gl, mDirtyRect);
uploadDirtyRect(mDirtyRect);
}
mDirtyRect.setEmpty();
@ -170,12 +174,12 @@ public abstract class TileLayer extends Layer {
return true;
}
private void uploadFullTexture(GL10 gl) {
private void uploadFullTexture() {
IntSize bufferSize = mImage.getSize();
uploadDirtyRect(gl, new Rect(0, 0, bufferSize.width, bufferSize.height));
uploadDirtyRect(new Rect(0, 0, bufferSize.width, bufferSize.height));
}
private void uploadDirtyRect(GL10 gl, Rect dirtyRect) {
private void uploadDirtyRect(Rect dirtyRect) {
// If we have nothing to upload, just return for now
if (dirtyRect.isEmpty())
return;
@ -189,7 +193,7 @@ public abstract class TileLayer extends Layer {
if (mTextureIDs == null) {
mTextureIDs = new int[1];
gl.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
GLES20.glGenTextures(mTextureIDs.length, mTextureIDs, 0);
newlyCreated = true;
}
@ -199,20 +203,12 @@ public abstract class TileLayer extends Layer {
int cairoFormat = mImage.getFormat();
CairoGLInfo glInfo = new CairoGLInfo(cairoFormat);
bindAndSetGLParameters(gl);
bindAndSetGLParameters();
if (newlyCreated || dirtyRect.contains(bufferRect)) {
if (mSize.equals(bufferSize)) {
gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
0, glInfo.format, glInfo.type, imageBuffer);
return;
} else {
gl.glTexImage2D(GL10.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width, mSize.height,
0, glInfo.format, glInfo.type, null);
gl.glTexSubImage2D(GL10.GL_TEXTURE_2D, 0, 0, 0, bufferSize.width, bufferSize.height,
glInfo.format, glInfo.type, imageBuffer);
return;
}
GLES20.glTexImage2D(GLES20.GL_TEXTURE_2D, 0, glInfo.internalFormat, mSize.width,
mSize.height, 0, glInfo.format, glInfo.type, imageBuffer);
return;
}
// Make sure that the dirty region intersects with the buffer rect,
@ -237,19 +233,21 @@ public abstract class TileLayer extends Layer {
}
viewBuffer.position(position);
gl.glTexSubImage2D(GL10.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width,
Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()),
glInfo.format, glInfo.type, viewBuffer);
GLES20.glTexSubImage2D(GLES20.GL_TEXTURE_2D, 0, 0, dirtyRect.top, bufferSize.width,
Math.min(bufferSize.height - dirtyRect.top, dirtyRect.height()),
glInfo.format, glInfo.type, viewBuffer);
}
private void bindAndSetGLParameters(GL10 gl) {
gl.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
private void bindAndSetGLParameters() {
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, mTextureIDs[0]);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER,
GLES20.GL_NEAREST);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER,
GLES20.GL_LINEAR);
int repeatMode = mRepeat ? GL10.GL_REPEAT : GL10.GL_CLAMP_TO_EDGE;
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_S, repeatMode);
gl.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_WRAP_T, repeatMode);
int repeatMode = mRepeat ? GLES20.GL_REPEAT : GLES20.GL_CLAMP_TO_EDGE;
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, repeatMode);
GLES20.glTexParameterf(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, repeatMode);
}
}

View File

@ -37,40 +37,15 @@
package org.mozilla.gecko.gfx;
/**
* A layer client provides tiles and manages other information used by the layer controller.
*/
public abstract class LayerClient {
private LayerController mLayerController;
public class ViewTransform {
public float x;
public float y;
public float scale;
public abstract void geometryChanged();
public abstract void viewportSizeChanged();
protected abstract void render();
public LayerController getLayerController() { return mLayerController; }
public void setLayerController(LayerController layerController) {
mLayerController = layerController;
}
/**
* A utility function for calling Layer.beginTransaction with the
* appropriate LayerView.
*/
public void beginTransaction(Layer aLayer) {
if (mLayerController != null) {
LayerView view = mLayerController.getView();
if (view != null) {
aLayer.beginTransaction(view);
return;
}
}
aLayer.beginTransaction();
}
// Included for symmetry.
public void endTransaction(Layer aLayer) {
aLayer.endTransaction();
public ViewTransform(float inX, float inY, float inScale) {
x = inX;
y = inY;
scale = inScale;
}
}

View File

@ -62,14 +62,7 @@ public class ViewportMetrics {
private FloatSize mPageSize;
private RectF mViewportRect;
private PointF mViewportOffset;
private float mZoomFactor;
private boolean mAllowZoom;
// A scale from -1,-1 to 1,1 that represents what edge of the displayport
// we want the viewport to be biased towards.
private PointF mViewportBias;
private static final float MAX_BIAS = 0.8f;
public ViewportMetrics() {
DisplayMetrics metrics = new DisplayMetrics();
@ -77,22 +70,25 @@ public class ViewportMetrics {
mPageSize = new FloatSize(metrics.widthPixels, metrics.heightPixels);
mViewportRect = new RectF(0, 0, metrics.widthPixels, metrics.heightPixels);
mViewportOffset = new PointF(0, 0);
mZoomFactor = 1.0f;
mViewportBias = new PointF(0.0f, 0.0f);
mAllowZoom = true;
}
public ViewportMetrics(ViewportMetrics viewport) {
mPageSize = new FloatSize(viewport.getPageSize());
mViewportRect = new RectF(viewport.getViewport());
PointF offset = viewport.getViewportOffset();
mViewportOffset = new PointF(offset.x, offset.y);
mZoomFactor = viewport.getZoomFactor();
mViewportBias = viewport.mViewportBias;
mAllowZoom = viewport.mAllowZoom;
}
public ViewportMetrics(ImmutableViewportMetrics viewport) {
mPageSize = new FloatSize(viewport.pageSizeWidth, viewport.pageSizeHeight);
mViewportRect = new RectF(viewport.viewportRectLeft,
viewport.viewportRectTop,
viewport.viewportRectRight,
viewport.viewportRectBottom);
mZoomFactor = viewport.zoomFactor;
}
public ViewportMetrics(JSONObject json) throws JSONException {
float x = (float)json.getDouble("x");
float y = (float)json.getDouble("y");
@ -100,57 +96,17 @@ public class ViewportMetrics {
float height = (float)json.getDouble("height");
float pageWidth = (float)json.getDouble("pageWidth");
float pageHeight = (float)json.getDouble("pageHeight");
float offsetX = (float)json.getDouble("offsetX");
float offsetY = (float)json.getDouble("offsetY");
float zoom = (float)json.getDouble("zoom");
mAllowZoom = json.getBoolean("allowZoom");
mPageSize = new FloatSize(pageWidth, pageHeight);
mViewportRect = new RectF(x, y, x + width, y + height);
mViewportOffset = new PointF(offsetX, offsetY);
mZoomFactor = zoom;
mViewportBias = new PointF(0.0f, 0.0f);
}
public PointF getOptimumViewportOffset(IntSize displayportSize) {
/* XXX Until bug #524925 is fixed, changing the viewport origin will
* cause unnecessary relayouts. This may cause rendering time to
* increase and should be considered.
*/
RectF viewport = getClampedViewport();
FloatSize bufferSpace = new FloatSize(displayportSize.width - viewport.width(),
displayportSize.height - viewport.height());
PointF optimumOffset =
new PointF(bufferSpace.width * ((mViewportBias.x + 1.0f) / 2.0f),
bufferSpace.height * ((mViewportBias.y + 1.0f) / 2.0f));
// Make sure this offset won't cause wasted pixels in the displayport
// (i.e. make sure the resultant displayport intersects with the page
// as much as possible)
if (viewport.left - optimumOffset.x < 0)
optimumOffset.x = viewport.left;
else if ((bufferSpace.width - optimumOffset.x) + viewport.right > mPageSize.width)
optimumOffset.x = bufferSpace.width - (mPageSize.width - viewport.right);
if (viewport.top - optimumOffset.y < 0)
optimumOffset.y = viewport.top;
else if ((bufferSpace.height - optimumOffset.y) + viewport.bottom > mPageSize.height)
optimumOffset.y = bufferSpace.height - (mPageSize.height - viewport.bottom);
return new PointF(Math.round(optimumOffset.x), Math.round(optimumOffset.y));
}
public PointF getOrigin() {
return new PointF(mViewportRect.left, mViewportRect.top);
}
public PointF getDisplayportOrigin() {
return new PointF(mViewportRect.left - mViewportOffset.x,
mViewportRect.top - mViewportOffset.y);
}
public FloatSize getSize() {
return new FloatSize(mViewportRect.width(), mViewportRect.height());
}
@ -179,10 +135,6 @@ public class ViewportMetrics {
return clampedViewport;
}
public PointF getViewportOffset() {
return mViewportOffset;
}
public FloatSize getPageSize() {
return mPageSize;
}
@ -191,10 +143,6 @@ public class ViewportMetrics {
return mZoomFactor;
}
public boolean getAllowZoom() {
return mAllowZoom;
}
public void setPageSize(FloatSize pageSize) {
mPageSize = pageSize;
}
@ -204,23 +152,6 @@ public class ViewportMetrics {
}
public void setOrigin(PointF origin) {
// When the origin is set, we compare it with the last value set and
// change the viewport bias accordingly, so that any viewport based
// on these metrics will have a larger buffer in the direction of
// movement.
// XXX Note the comment about bug #524925 in getOptimumViewportOffset.
// Ideally, the viewport bias would be a sliding scale, but we
// don't want to change it too often at the moment.
if (FloatUtils.fuzzyEquals(origin.x, mViewportRect.left))
mViewportBias.x = 0;
else
mViewportBias.x = ((mViewportRect.left - origin.x) > 0) ? MAX_BIAS : -MAX_BIAS;
if (FloatUtils.fuzzyEquals(origin.y, mViewportRect.top))
mViewportBias.y = 0;
else
mViewportBias.y = ((mViewportRect.top - origin.y) > 0) ? MAX_BIAS : -MAX_BIAS;
mViewportRect.set(origin.x, origin.y,
origin.x + mViewportRect.width(),
origin.y + mViewportRect.height());
@ -231,10 +162,6 @@ public class ViewportMetrics {
mViewportRect.bottom = mViewportRect.top + size.height;
}
public void setViewportOffset(PointF offset) {
mViewportOffset = offset;
}
public void setZoomFactor(float zoomFactor) {
mZoomFactor = zoomFactor;
}
@ -255,15 +182,6 @@ public class ViewportMetrics {
setOrigin(origin);
mZoomFactor = newZoomFactor;
// Similar to setOrigin, set the viewport bias based on the focal point
// of the zoom so that a viewport based on these metrics will have a
// larger buffer based on the direction of movement when scaling.
//
// This is biased towards scaling outwards, as zooming in doesn't
// really require a viewport bias.
mViewportBias.set(((focus.x / mViewportRect.width()) * (2.0f * MAX_BIAS)) - MAX_BIAS,
((focus.y / mViewportRect.height()) * (2.0f * MAX_BIAS)) - MAX_BIAS);
}
/*
@ -276,14 +194,12 @@ public class ViewportMetrics {
result.mPageSize = mPageSize.interpolate(to.mPageSize, t);
result.mZoomFactor = FloatUtils.interpolate(mZoomFactor, to.mZoomFactor, t);
result.mViewportRect = RectUtils.interpolate(mViewportRect, to.mViewportRect, t);
result.mViewportOffset = PointUtils.interpolate(mViewportOffset, to.mViewportOffset, t);
return result;
}
public boolean fuzzyEquals(ViewportMetrics other) {
return mPageSize.fuzzyEquals(other.mPageSize)
&& RectUtils.fuzzyEquals(mViewportRect, other.mViewportRect)
&& FloatUtils.fuzzyEquals(mViewportOffset, other.mViewportOffset)
&& FloatUtils.fuzzyEquals(mZoomFactor, other.mZoomFactor);
}
@ -300,8 +216,6 @@ public class ViewportMetrics {
.append(", \"height\" : ").append(height)
.append(", \"pageWidth\" : ").append(mPageSize.width)
.append(", \"pageHeight\" : ").append(mPageSize.height)
.append(", \"offsetX\" : ").append(mViewportOffset.x)
.append(", \"offsetY\" : ").append(mViewportOffset.y)
.append(", \"zoom\" : ").append(mZoomFactor)
.append(" }");
return sb.toString();
@ -312,9 +226,7 @@ public class ViewportMetrics {
StringBuffer buff = new StringBuffer(128);
buff.append("v=").append(mViewportRect.toString())
.append(" p=").append(mPageSize.toString())
.append(" z=").append(mZoomFactor)
.append(" o=").append(mViewportOffset.x)
.append(',').append(mViewportOffset.y);
.append(" z=").append(mZoomFactor);
return buff.toString();
}
}

View File

@ -0,0 +1,74 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
* Chris Lord <chrislord.net@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import android.graphics.Rect;
public class VirtualLayer extends Layer {
public VirtualLayer(IntSize size) {
super(size);
}
@Override
public void draw(RenderContext context) {
// No-op.
}
void setPositionAndResolution(Rect newPosition, float newResolution) {
// This is an optimized version of the following code:
// beginTransaction();
// try {
// setPosition(newPosition);
// setResolution(newResolution);
// performUpdates(null);
// } finally {
// endTransaction();
// }
// it is safe to drop the transaction lock in this instance (i.e. for the
// VirtualLayer that is just a shadow of what gecko is painting) because
// the position and resolution of this layer are never used for anything
// meaningful.
// XXX The above is not true any more; the compositor uses these values
// in order to determine where to draw the checkerboard. The values are
// also used in LayerController's convertViewPointToLayerPoint function.
mPosition = newPosition;
mResolution = newResolution;
}
}

View File

@ -1,128 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2009-2010
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* James Willcox <jwillcox@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
package org.mozilla.gecko.gfx;
import org.mozilla.gecko.gfx.LayerController;
import org.mozilla.gecko.gfx.SingleTileLayer;
import org.mozilla.gecko.GeckoAppShell;
import android.opengl.GLES11;
import android.opengl.GLES11Ext;
import android.graphics.RectF;
import android.util.Log;
import javax.microedition.khronos.opengles.GL10;
/**
* Encapsulates the logic needed to draw the single-tiled Gecko texture
*/
public class WidgetTileLayer extends Layer {
private static final String LOGTAG = "WidgetTileLayer";
private int[] mTextureIDs;
private CairoImage mImage;
public WidgetTileLayer(CairoImage image) {
mImage = image;
}
protected boolean initialized() { return mTextureIDs != null; }
@Override
public IntSize getSize() { return mImage.getSize(); }
protected void bindAndSetGLParameters() {
GLES11.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
GLES11.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MAG_FILTER, GL10.GL_LINEAR);
}
@Override
protected void finalize() throws Throwable {
if (mTextureIDs != null)
TextureReaper.get().add(mTextureIDs);
}
@Override
protected boolean performUpdates(GL10 gl, RenderContext context) {
super.performUpdates(gl, context);
if (mTextureIDs == null) {
mTextureIDs = new int[1];
GLES11.glGenTextures(1, mTextureIDs, 0);
}
bindAndSetGLParameters();
GeckoAppShell.bindWidgetTexture();
return true;
}
@Override
public void draw(RenderContext context) {
// mTextureIDs may be null here during startup if Layer.java's draw method
// failed to acquire the transaction lock and call performUpdates.
if (!initialized())
return;
GLES11.glBindTexture(GL10.GL_TEXTURE_2D, mTextureIDs[0]);
RectF bounds;
int[] cropRect;
IntSize size = getSize();
RectF viewport = context.viewport;
bounds = getBounds(context, new FloatSize(size));
cropRect = new int[] { 0, size.height, size.width, -size.height };
bounds.offset(-viewport.left, -viewport.top);
GLES11.glTexParameteriv(GL10.GL_TEXTURE_2D, GLES11Ext.GL_TEXTURE_CROP_RECT_OES, cropRect,
0);
float top = viewport.height() - (bounds.top + bounds.height());
// There may be errors from a previous GL call, so clear them first because
// we want to check for one below
while (GLES11.glGetError() != GLES11.GL_NO_ERROR);
GLES11Ext.glDrawTexfOES(bounds.left, top, 0.0f, bounds.width(), bounds.height());
int error = GLES11.glGetError();
if (error != GLES11.GL_NO_ERROR) {
Log.i(LOGTAG, "Failed to draw texture: " + error);
}
}
}

View File

@ -119,7 +119,8 @@ public class PanZoomController
* similar to TOUCHING but after starting a pan */
PANNING_HOLD_LOCKED, /* like PANNING_HOLD, but axis lock still in effect */
PINCHING, /* nth touch-start, where n > 1. this mode allows pan and zoom */
ANIMATED_ZOOM /* animated zoom to a new rect */
ANIMATED_ZOOM, /* animated zoom to a new rect */
BOUNCE /* in a bounce animation */
}
private final LayerController mController;
@ -221,11 +222,12 @@ public class PanZoomController
case FLING:
mX.stopFling();
mY.stopFling();
mState = PanZoomState.NOTHING;
// fall through
case BOUNCE:
case ANIMATED_ZOOM:
// the zoom that's in progress likely makes no sense any more (such as if
// the screen orientation changed) so abort it
mState = PanZoomState.NOTHING;
// fall through
case NOTHING:
// Don't do animations here; they're distracting and can cause flashes on page
@ -240,7 +242,7 @@ public class PanZoomController
public void pageSizeUpdated() {
if (mState == PanZoomState.NOTHING) {
ViewportMetrics validated = getValidViewportMetrics();
if (! mController.getViewportMetrics().fuzzyEquals(validated)) {
if (! (new ViewportMetrics(mController.getViewportMetrics())).fuzzyEquals(validated)) {
// page size changed such that we are now in overscroll. snap to the
// the nearest valid viewport
mController.setViewportMetrics(validated);
@ -254,7 +256,6 @@ public class PanZoomController
*/
private boolean onTouchStart(MotionEvent event) {
Log.d(LOGTAG, "onTouchStart in state " + mState);
// user is taking control of movement, so stop
// any auto-movement we have going
stopAnimationTimer();
@ -264,6 +265,7 @@ public class PanZoomController
case ANIMATED_ZOOM:
return false;
case FLING:
case BOUNCE:
case NOTHING:
startTouch(event.getX(0), event.getY(0), event.getEventTime());
return false;
@ -281,11 +283,11 @@ public class PanZoomController
}
private boolean onTouchMove(MotionEvent event) {
Log.d(LOGTAG, "onTouchMove in state " + mState);
switch (mState) {
case NOTHING:
case FLING:
case BOUNCE:
// should never happen
Log.e(LOGTAG, "Received impossible touch move while in " + mState);
return false;
@ -327,11 +329,11 @@ public class PanZoomController
}
private boolean onTouchEnd(MotionEvent event) {
Log.d(LOGTAG, "onTouchEnd in " + mState);
switch (mState) {
case NOTHING:
case FLING:
case BOUNCE:
// should never happen
Log.e(LOGTAG, "Received impossible touch end while in " + mState);
return false;
@ -360,8 +362,6 @@ public class PanZoomController
}
private boolean onTouchCancel(MotionEvent event) {
Log.d(LOGTAG, "onTouchCancel in " + mState);
mState = PanZoomState.NOTHING;
// ensure we snap back if we're overscrolled
bounce();
@ -467,8 +467,7 @@ public class PanZoomController
return;
}
mState = PanZoomState.FLING;
Log.d(LOGTAG, "end bounce at " + metrics);
mState = PanZoomState.BOUNCE;
startAnimationTimer(new BounceRunnable(bounceStartMetrics, metrics));
}
@ -581,7 +580,7 @@ public class PanZoomController
* animation by setting the state to PanZoomState.NOTHING. Handle this case and bail
* out.
*/
if (mState != PanZoomState.FLING) {
if (mState != PanZoomState.BOUNCE) {
finishAnimation();
return;
}
@ -773,9 +772,6 @@ public class PanZoomController
if (GeckoApp.mDOMFullScreen)
return false;
if (!mController.getViewportMetrics().getAllowZoom())
return false;
if (mState == PanZoomState.ANIMATED_ZOOM)
return false;
@ -836,7 +832,18 @@ public class PanZoomController
}
public boolean getRedrawHint() {
return (mState == PanZoomState.NOTHING || mState == PanZoomState.FLING);
switch (mState) {
case PINCHING:
case ANIMATED_ZOOM:
case BOUNCE:
// don't redraw during these because the zoom is (or might be, in the case
// of BOUNCE) be changing rapidly and gecko will have to redraw the entire
// display port area. we trigger a force-redraw upon exiting these states.
return false;
default:
// allow redrawing in other states
return true;
}
}
private void sendPointToGecko(String event, MotionEvent motionEvent) {

View File

@ -74,33 +74,6 @@ const kStateActive = 0x00000001; // :active pseudoclass for elements
const kXLinkNamespace = "http://www.w3.org/1999/xlink";
// TODO: Take into account ppi in these units?
// The ratio of velocity that is retained every ms.
const kPanDeceleration = 0.999;
// The number of ms to consider events over for a swipe gesture.
const kSwipeLength = 500;
// The number of pixels to move before we consider a drag to be more than
// just a click with jitter.
const kDragThreshold = 10;
// The number of pixels to move to break out of axis-lock
const kLockBreakThreshold = 100;
// Minimum speed to move during kinetic panning. 0.015 pixels/ms is roughly
// equivalent to a pixel every 4 frames at 60fps.
const kMinKineticSpeed = 0.015;
// Maximum kinetic panning speed. 9 pixels/ms is equivalent to 150 pixels per
// frame at 60fps.
const kMaxKineticSpeed = 9;
// The maximum magnitude of disparity allowed between axes acceleration. If
// it's larger than this, lock the slow-moving axis.
const kAxisLockRatio = 5;
// The element tag names that are considered to receive input. Mouse-down
// events directed to one of these are allowed to go through.
const kElementsReceivingInput = {
@ -115,6 +88,9 @@ const kElementsReceivingInput = {
video: true
};
const kDefaultCSSViewportWidth = 980;
const kDefaultCSSViewportHeight = 480;
function dump(a) {
Cc["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
}
@ -172,35 +148,11 @@ var Strings = {};
var MetadataProvider = {
getDrawMetadata: function getDrawMetadata() {
return BrowserApp.getDrawMetadata();
return JSON.stringify(BrowserApp.selectedTab.getViewport());
},
paintingSuppressed: function paintingSuppressed() {
// Get the current tab. Don't suppress painting if there are no tabs yet.
let tab = BrowserApp.selectedTab;
if (!tab)
return false;
// If the viewport metadata has not yet been updated (and therefore the browser size has not
// been changed accordingly), do not draw yet. We'll get an unsightly flash on page transitions
// otherwise, because we receive a paint event after the new document is shown but before the
// correct browser size for the new document has been set.
//
// This whole situation exists because the docshell and the browser element are unaware of the
// existence of <meta viewport>. Therefore they dispatch paint events without knowledge of the
// invariant that the page must not be drawn until the browser size has been appropriately set.
// It would be easier if the docshell were made aware of the existence of <meta viewport> so
// that this logic could be removed.
let viewportDocumentId = tab.documentIdForCurrentViewport;
let contentDocumentId = ViewportHandler.getIdForDocument(tab.browser.contentDocument);
if (viewportDocumentId != null && viewportDocumentId != contentDocumentId)
return true;
// Suppress painting if the current presentation shell is suppressing painting.
let cwu = tab.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
return cwu.paintingSuppressed;
return false;
}
};
@ -440,6 +392,24 @@ var BrowserApp = {
SearchEngines.uninit();
},
// This function returns false during periods where the browser displayed document is
// different from the browser content document, so user actions and some kinds of viewport
// updates should be ignored. This period starts when we start loading a new page or
// switch tabs, and ends when the new browser content document has been drawn and handed
// off to the compositor.
isBrowserContentDocumentDisplayed: function() {
if (window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).isFirstPaint)
return false;
let tab = this.selectedTab;
if (!tab)
return true;
return tab.contentDocumentIsDisplayed;
},
displayedDocumentChanged: function() {
window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).isFirstPaint = true;
},
get tabs() {
return this._tabs;
},
@ -457,8 +427,9 @@ var BrowserApp = {
return;
aTab.setActive(true);
aTab.updateViewport(false);
this.deck.selectedPanel = aTab.vbox;
aTab.setResolution(aTab._zoom, true);
this.displayedDocumentChanged();
this.deck.selectedPanel = aTab.browser;
},
get selectedBrowser() {
@ -839,80 +810,48 @@ var BrowserApp = {
// as well as the browser's content window, and modify the scrollX and scrollY on the content window.
focused.scrollIntoView(false);
// As Gecko isn't aware of the zoom level we're drawing with, the element may not entirely be in view
// yet. Check for that, and scroll some extra to compensate, if necessary.
let focusedRect = focused.getBoundingClientRect();
let visibleContentWidth = gScreenWidth / tab._zoom;
let visibleContentHeight = gScreenHeight / tab._zoom;
let positionChanged = false;
let scrollX = win.scrollX;
let scrollY = win.scrollY;
if (focusedRect.right >= visibleContentWidth && focusedRect.left > 0) {
// the element is too far off the right side, so we need to scroll to the right more
scrollX += Math.min(focusedRect.left, focusedRect.right - visibleContentWidth);
positionChanged = true;
} else if (focusedRect.left < 0) {
// the element is too far off the left side, so we need to scroll to the left more
scrollX += focusedRect.left;
positionChanged = true;
}
if (focusedRect.bottom >= visibleContentHeight && focusedRect.top > 0) {
// the element is too far down, so we need to scroll down more
scrollY += Math.min(focusedRect.top, focusedRect.bottom - visibleContentHeight);
positionChanged = true;
} else if (focusedRect.top < 0) {
// the element is too far up, so we need to scroll up more
scrollY += focusedRect.top;
positionChanged = true;
}
if (positionChanged)
win.scrollTo(scrollX, scrollY);
// update userScrollPos so that we don't send a duplicate viewport update by triggering
// our scroll listener
tab.userScrollPos.x = win.scrollX;
tab.userScrollPos.y = win.scrollY;
// note that:
// 1. because of the way we do zooming using a CSS transform, gecko does not take into
// account the effect of the zoom on the viewport size.
// 2. if the input element is near the bottom/right of the page (less than one viewport
// height/width away from the bottom/right), the scrollIntoView call will make gecko scroll to the
// bottom/right of the page in an attempt to align the input field with the top of the viewport.
// however, since gecko doesn't know about the zoom, what it thinks is the "bottom/right of
// the page" isn't actually the bottom/right of the page at the current zoom level, and we
// need to adjust this further.
// 3. we can't actually adjust this by changing the window scroll position, as gecko already thinks
// we're at the bottom/right, so instead we do it by changing the viewportExcess on the tab and
// moving the browser element.
let visibleContentWidth = tab._viewport.width / tab._viewport.zoom;
let visibleContentHeight = tab._viewport.height / tab._viewport.zoom;
// get the rect that the focused element occupies relative to what gecko thinks the viewport is,
// and adjust it by viewportExcess to so that it is relative to what the user sees as the viewport.
let focusedRect = focused.getBoundingClientRect();
focusedRect = {
left: focusedRect.left - tab.viewportExcess.x,
right: focusedRect.right - tab.viewportExcess.x,
top: focusedRect.top - tab.viewportExcess.y,
bottom: focusedRect.bottom - tab.viewportExcess.y
};
let transformChanged = false;
if (focusedRect.right >= visibleContentWidth && focusedRect.left > 0) {
// the element is too far off the right side, so we need to scroll to the right more
tab.viewportExcess.x += Math.min(focusedRect.left, focusedRect.right - visibleContentWidth);
transformChanged = true;
} else if (focusedRect.left < 0) {
// the element is too far off the left side, so we need to scroll to the left more
tab.viewportExcess.x += focusedRect.left;
transformChanged = true;
}
if (focusedRect.bottom >= visibleContentHeight && focusedRect.top > 0) {
// the element is too far down, so we need to scroll down more
tab.viewportExcess.y += Math.min(focusedRect.top, focusedRect.bottom - visibleContentHeight);
transformChanged = true;
} else if (focusedRect.top < 0) {
// the element is too far up, so we need to scroll up more
tab.viewportExcess.y += focusedRect.top;
transformChanged = true;
}
if (transformChanged)
tab.updateTransform();
// finally, let java know where we ended up
tab.sendViewportUpdate();
}
},
getDrawMetadata: function getDrawMetadata() {
let viewport = this.selectedTab.viewport;
// Sample the background color of the page and pass it along. (This is used to draw the
// checkerboard.)
try {
let browser = this.selectedBrowser;
if (browser) {
let { contentDocument, contentWindow } = browser;
let computedStyle = contentWindow.getComputedStyle(contentDocument.body);
viewport.backgroundColor = computedStyle.backgroundColor;
}
} catch (e) {
// Ignore. Catching and ignoring exceptions here ensures that Talos succeeds.
}
return JSON.stringify(viewport);
},
observe: function(aSubject, aTopic, aData) {
let browser = this.selectedBrowser;
if (!browser)
@ -978,8 +917,10 @@ var BrowserApp = {
} else if (aTopic == "FullScreen:Exit") {
browser.contentDocument.mozCancelFullScreen();
} else if (aTopic == "Viewport:Change") {
this.selectedTab.viewport = JSON.parse(aData);
ViewportHandler.onResize();
if (this.isBrowserContentDocumentDisplayed())
this.selectedTab.setViewport(JSON.parse(aData));
} else if (aTopic == "SearchEngines:Get") {
this.getSearchEngines();
} else if (aTopic == "Passwords:Init") {
var storage = Components.classes["@mozilla.org/login-manager/storage/mozStorage;1"].
getService(Components.interfaces.nsILoginManagerStorage);
@ -1493,17 +1434,14 @@ let gScreenHeight = 1;
function Tab(aURL, aParams) {
this.browser = null;
this.vbox = null;
this.id = 0;
this.showProgress = true;
this.create(aURL, aParams);
this._viewport = { x: 0, y: 0, width: gScreenWidth, height: gScreenHeight, offsetX: 0, offsetY: 0,
pageWidth: gScreenWidth, pageHeight: gScreenHeight, zoom: 1.0 };
this.viewportExcess = { x: 0, y: 0 };
this.documentIdForCurrentViewport = null;
this._zoom = 1.0;
this.userScrollPos = { x: 0, y: 0 };
this._pluginCount = 0;
this._pluginOverlayShowing = false;
this.contentDocumentIsDisplayed = true;
}
Tab.prototype = {
@ -1513,21 +1451,16 @@ Tab.prototype = {
aParams = aParams || {};
this.vbox = document.createElement("vbox");
this.vbox.align = "start";
BrowserApp.deck.appendChild(this.vbox);
this.browser = document.createElement("browser");
this.browser.setAttribute("type", "content-targetable");
this.setBrowserSize(980, 480);
this.browser.style.MozTransformOrigin = "0 0";
this.vbox.appendChild(this.browser);
this.setBrowserSize(kDefaultCSSViewportWidth, kDefaultCSSViewportHeight);
BrowserApp.deck.appendChild(this.browser);
this.browser.stop();
// Turn off clipping so we can buffer areas outside of the browser element.
let frameLoader = this.browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
frameLoader.clipSubdocument = false;
frameLoader.renderMode = Ci.nsIFrameLoader.RENDER_MODE_ASYNC_SCROLL;
frameLoader.clampScrollPosition = false;
// only set tab uri if uri is valid
let uri = null;
@ -1571,7 +1504,7 @@ Tab.prototype = {
this.browser.addEventListener("pagehide", this, true);
this.browser.addEventListener("pageshow", this, true);
Services.obs.addObserver(this, "document-shown", false);
Services.obs.addObserver(this, "before-first-paint", false);
if (!aParams.delayLoad) {
let flags = "flags" in aParams ? aParams.flags : Ci.nsIWebNavigation.LOAD_FLAGS_NONE;
@ -1617,17 +1550,15 @@ Tab.prototype = {
this.browser.removeEventListener("pagehide", this, true);
this.browser.removeEventListener("pageshow", this, true);
Services.obs.removeObserver(this, "document-shown");
Services.obs.removeObserver(this, "before-first-paint");
// Make sure the previously selected panel remains selected. The selected panel of a deck is
// not stable when panels are removed.
let selectedPanel = BrowserApp.deck.selectedPanel;
BrowserApp.deck.removeChild(this.vbox);
BrowserApp.deck.removeChild(this.browser);
BrowserApp.deck.selectedPanel = selectedPanel;
this.browser = null;
this.vbox = null;
this.documentIdForCurrentViewport = null;
},
// This should be called to update the browser when the tab gets selected/unselected
@ -1645,76 +1576,68 @@ Tab.prototype = {
}
},
set viewport(aViewport) {
setDisplayPort: function(aViewportX, aViewportY, aDisplayPortRect) {
let zoom = this._zoom;
if (zoom <= 0)
return;
let element = this.browser.contentDocument.documentElement;
if (!element)
return;
let cwu = window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
cwu.setDisplayPortForElement((aDisplayPortRect.left - aViewportX) / zoom,
(aDisplayPortRect.top - aViewportY) / zoom,
(aDisplayPortRect.right - aDisplayPortRect.left) / zoom,
(aDisplayPortRect.bottom - aDisplayPortRect.top) / zoom,
element);
},
setViewport: function(aViewport) {
// Transform coordinates based on zoom
aViewport.x /= aViewport.zoom;
aViewport.y /= aViewport.zoom;
let x = aViewport.x / aViewport.zoom;
let y = aViewport.y / aViewport.zoom;
// Set scroll position
let win = this.browser.contentWindow;
win.scrollTo(aViewport.x, aViewport.y);
win.scrollTo(x, y);
this.userScrollPos.x = win.scrollX;
this.userScrollPos.y = win.scrollY;
// If we've been asked to over-scroll, do it via the transformation
// and store it separately to the viewport.
let excessX = aViewport.x - win.scrollX;
let excessY = aViewport.y - win.scrollY;
this._viewport.width = gScreenWidth = aViewport.width;
this._viewport.height = gScreenHeight = aViewport.height;
let transformChanged = false;
if ((aViewport.offsetX != this._viewport.offsetX) ||
(excessX != this.viewportExcess.x)) {
this._viewport.offsetX = aViewport.offsetX;
this.viewportExcess.x = excessX;
transformChanged = true;
}
if ((aViewport.offsetY != this._viewport.offsetY) ||
(excessY != this.viewportExcess.y)) {
this._viewport.offsetY = aViewport.offsetY;
this.viewportExcess.y = excessY;
transformChanged = true;
}
if (Math.abs(aViewport.zoom - this._viewport.zoom) >= 1e-6) {
this._viewport.zoom = aViewport.zoom;
transformChanged = true;
}
if (transformChanged)
this.updateTransform();
this.setResolution(aViewport.zoom, false);
this.setDisplayPort(aViewport.x, aViewport.y, aViewport.displayPort);
},
updateTransform: function() {
let hasZoom = (Math.abs(this._viewport.zoom - 1.0) >= 1e-6);
let x = this._viewport.offsetX + Math.round(-this.viewportExcess.x * this._viewport.zoom);
let y = this._viewport.offsetY + Math.round(-this.viewportExcess.y * this._viewport.zoom);
let transform =
"translate(" + x + "px, " +
y + "px)";
if (hasZoom)
transform += " scale(" + this._viewport.zoom + ")";
this.browser.style.MozTransform = transform;
setResolution: function(aZoom, aForce) {
// Set zoom level
if (aForce || Math.abs(aZoom - this._zoom) >= 1e-6) {
this._zoom = aZoom;
if (BrowserApp.selectedTab == this) {
let cwu = window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
cwu.setResolution(aZoom, aZoom);
}
}
},
get viewport() {
// Update the viewport to current dimensions
this._viewport.x = (this.browser.contentWindow.scrollX +
this.viewportExcess.x) || 0;
this._viewport.y = (this.browser.contentWindow.scrollY +
this.viewportExcess.y) || 0;
getViewport: function() {
let viewport = {
width: gScreenWidth,
height: gScreenHeight,
pageWidth: gScreenWidth,
pageHeight: gScreenHeight,
zoom: this._zoom
};
// Set the viewport offset to current scroll offset
viewport.x = this.browser.contentWindow.scrollX || 0;
viewport.y = this.browser.contentWindow.scrollY || 0;
// Transform coordinates based on zoom
this._viewport.x = Math.round(this._viewport.x * this._viewport.zoom);
this._viewport.y = Math.round(this._viewport.y * this._viewport.zoom);
viewport.x = Math.round(viewport.x * viewport.zoom);
viewport.y = Math.round(viewport.y * viewport.zoom);
let doc = this.browser.contentDocument;
if (doc != null) {
let pageWidth = this._viewport.width, pageHeight = this._viewport.height;
let pageWidth = viewport.width, pageHeight = viewport.height;
if (doc instanceof SVGDocument) {
let rect = doc.rootElement.getBoundingClientRect();
// we need to add rect.left and rect.top twice so that the SVG is drawn
@ -1731,10 +1654,8 @@ Tab.prototype = {
}
/* Transform the page width and height based on the zoom factor. */
pageWidth *= this._viewport.zoom;
pageHeight *= this._viewport.zoom;
this._viewport.allowZoom = this.metadata.allowZoom;
pageWidth *= viewport.zoom;
pageHeight *= viewport.zoom;
/*
* Avoid sending page sizes of less than screen size before we hit DOMContentLoaded, because
@ -1742,40 +1663,24 @@ Tab.prototype = {
* send updates regardless of page size; we'll zoom to fit the content as needed.
*/
if (doc.readyState === 'complete' || (pageWidth >= gScreenWidth && pageHeight >= gScreenHeight)) {
this._viewport.pageWidth = pageWidth;
this._viewport.pageHeight = pageHeight;
viewport.pageWidth = pageWidth;
viewport.pageHeight = pageHeight;
}
}
return this._viewport;
},
updateViewport: function(aReset, aZoomLevel) {
if (!aZoomLevel)
aZoomLevel = this.getDefaultZoomLevel();
let win = this.browser.contentWindow;
let zoom = (aReset ? aZoomLevel : this._viewport.zoom);
let xpos = ((aReset && win) ? win.scrollX * zoom : this._viewport.x);
let ypos = ((aReset && win) ? win.scrollY * zoom : this._viewport.y);
this.viewportExcess = { x: 0, y: 0 };
this.viewport = { x: xpos, y: ypos,
offsetX: 0, offsetY: 0,
width: this._viewport.width, height: this._viewport.height,
pageWidth: gScreenWidth, pageHeight: gScreenHeight,
zoom: zoom };
this.sendViewportUpdate();
return viewport;
},
sendViewportUpdate: function() {
if (BrowserApp.selectedTab != this)
return;
sendMessageToJava({
gecko: {
type: "Viewport:UpdateAndDraw"
}
});
if (!BrowserApp.isBrowserContentDocumentDisplayed())
return;
let message = this.getViewport();
message.type = "Viewport:Update";
let displayPort = sendMessageToJava({ gecko: message });
if (displayPort != null)
this.setDisplayPort(message.x, message.y, JSON.parse(displayPort));
},
handleEvent: function(aEvent) {
@ -1787,13 +1692,29 @@ Tab.prototype = {
if (target.defaultView != this.browser.contentWindow)
return;
// Sample the background color of the page and pass it along. (This is used to draw the
// checkerboard.) Right now we don't detect changes in the background color after this
// event fires; it's not clear that doing so is worth the effort.
var backgroundColor = null;
try {
let browser = this.selectedBrowser;
if (browser) {
let { contentDocument, contentWindow } = browser;
let computedStyle = contentWindow.getComputedStyle(contentDocument.body);
backgroundColor = computedStyle.backgroundColor;
}
} catch (e) {
// Ignore. Catching and ignoring exceptions here ensures that Talos succeeds.
}
sendMessageToJava({
gecko: {
type: "DOMContentLoaded",
tabID: this.id,
windowID: 0,
uri: this.browser.currentURI.spec,
title: this.browser.contentTitle
title: this.browser.contentTitle,
bgColor: backgroundColor
}
});
@ -1905,11 +1826,7 @@ Tab.prototype = {
case "scroll": {
let win = this.browser.contentWindow;
if (this.userScrollPos.x != win.scrollX || this.userScrollPos.y != win.scrollY) {
sendMessageToJava({
gecko: {
type: "Viewport:UpdateLater"
}
});
this.sendViewportUpdate();
}
break;
}
@ -2020,7 +1937,9 @@ Tab.prototype = {
sendMessageToJava(message);
if ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) == 0) {
this.updateViewport(true);
// XXX This code assumes that this is the earliest hook we have at which
// browser.contentDocument is changed to the new document we're loading
this.contentDocumentIsDisplayed = false;
} else {
this.sendViewportUpdate();
}
@ -2118,17 +2037,32 @@ Tab.prototype = {
}
ViewportHandler.setMetadataForDocument(this.browser.contentDocument, aMetadata);
this.updateViewportSize();
this.updateViewport(true);
},
/** Update viewport when the metadata or the window size changes. */
updateViewportSize: function updateViewportSize() {
// When this function gets called on window resize, we must execute
// this.sendViewportUpdate() so that refreshDisplayPort is called.
// Ensure that when making changes to this function that code path
// is not accidentally removed (the call to sendViewportUpdate() is
// at the very end).
if (window.outerWidth == 0 || window.outerHeight == 0) {
// this happens sometimes when starting up fennec. we don't want zero
// values corrupting our viewport numbers, so ignore this one.
return;
}
let oldScreenWidth = gScreenWidth;
gScreenWidth = window.outerWidth;
gScreenHeight = window.outerHeight;
let browser = this.browser;
if (!browser)
return;
let screenW = this._viewport.width;
let screenH = this._viewport.height;
let screenW = gScreenWidth;
let screenH = gScreenHeight;
let viewportW, viewportH;
let metadata = this.metadata;
@ -2160,10 +2094,10 @@ Tab.prototype = {
// Make sure the viewport height is not shorter than the window when
// the page is zoomed out to show its full width.
let minScale = this.getPageZoomLevel(screenW);
let minScale = this.getPageZoomLevel();
viewportH = Math.max(viewportH, screenH / minScale);
let oldBrowserWidth = parseInt(this.browser.style.minWidth);
let oldBrowserWidth = this.browserWidth;
this.setBrowserSize(viewportW, viewportH);
// Avoid having the scroll position jump around after device rotation.
@ -2171,24 +2105,21 @@ Tab.prototype = {
this.userScrollPos.x = win.scrollX;
this.userScrollPos.y = win.scrollY;
// If the browser width changes, we change the zoom proportionally. This ensures sensible
// behavior when rotating the device on pages with automatically-resizing viewports.
if (viewportW == oldBrowserWidth)
return;
let viewport = this.viewport;
let newZoom = oldBrowserWidth * viewport.zoom / viewportW;
this.updateViewport(true, newZoom);
},
getDefaultZoomLevel: function getDefaultZoomLevel() {
let md = this.metadata;
if ("defaultZoom" in md && md.defaultZoom)
return md.defaultZoom;
let browserWidth = parseInt(this.browser.style.minWidth);
return gScreenWidth / browserWidth;
// This change to the zoom accounts for all types of changes I can conceive:
// 1. screen size changes, CSS viewport does not (pages with no meta viewport
// or a fixed size viewport)
// 2. screen size changes, CSS viewport also does (pages with a device-width
// viewport)
// 3. screen size remains constant, but CSS viewport changes (meta viewport
// tag is added or removed)
// 4. neither screen size nor CSS viewport changes
//
// In all of these cases, we maintain how much actual content is visible
// within the screen width. Note that "actual content" may be different
// with respect to CSS pixels because of the CSS viewport size changing.
let zoomScale = (screenW * oldBrowserWidth) / (oldScreenWidth * viewportW);
this.setResolution(this._zoom * zoomScale, false);
this.sendViewportUpdate();
},
getPageZoomLevel: function getPageZoomLevel() {
@ -2197,14 +2128,16 @@ Tab.prototype = {
if (!this.browser.contentDocument || !this.browser.contentDocument.body)
return 1.0;
return this._viewport.width / this.browser.contentDocument.body.clientWidth;
return gScreenWidth / this.browser.contentDocument.body.clientWidth;
},
setBrowserSize: function(aWidth, aHeight) {
// Using min width/height so as not to conflict with the fullscreen style rule.
// See Bug #709813.
this.browser.style.minWidth = aWidth + "px";
this.browser.style.minHeight = aHeight + "px";
this.browserWidth = aWidth;
if (!this.browser.contentWindow)
return;
let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
cwu.setCSSViewport(aWidth, aHeight);
},
getRequestLoadContext: function(aRequest) {
@ -2232,12 +2165,24 @@ Tab.prototype = {
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case "document-shown":
case "before-first-paint":
// Is it on the top level?
let contentDocument = aSubject;
if (contentDocument == this.browser.contentDocument) {
// reset CSS viewport and zoom to default on new page
this.setBrowserSize(kDefaultCSSViewportWidth, kDefaultCSSViewportHeight);
this.setResolution(gScreenWidth / this.browserWidth, false);
// and then use the metadata to figure out how it needs to be updated
ViewportHandler.updateMetadata(this);
this.documentIdForCurrentViewport = ViewportHandler.getIdForDocument(contentDocument);
// The document element must have a display port on it whenever we are about to
// paint. This is the point just before the first paint, so we set the display port
// to a default value here. Once Java is aware of this document it will overwrite
// it with a better-calculated display port.
this.setDisplayPort(0, 0, {left: 0, top: 0, right: gScreenWidth, bottom: gScreenHeight });
BrowserApp.displayedDocumentChanged();
this.contentDocumentIsDisplayed = true;
}
break;
}
@ -2251,7 +2196,7 @@ Tab.prototype = {
},
get scale() {
return this.viewport.zoom;
return this._zoom;
},
QueryInterface: XPCOMUtils.generateQI([
@ -2276,6 +2221,26 @@ var BrowserEventHandler = {
},
observe: function(aSubject, aTopic, aData) {
if (aTopic == "dom-touch-listener-added") {
let tab = BrowserApp.getTabForWindow(aSubject);
if (!tab)
return;
sendMessageToJava({
gecko: {
type: "Tab:HasTouchListener",
tabID: tab.id
}
});
return;
}
// the remaining events are all dependent on the browser content document being the
// same as the browser displayed document. if they are not the same, we should ignore
// the event.
if (!BrowserApp.isBrowserContentDocumentDisplayed())
return;
if (aTopic == "Gesture:Scroll") {
// If we've lost our scrollable element, return. Don't cancel the
// override, as we probably don't want Java to handle panning until the
@ -2334,7 +2299,6 @@ var BrowserEventHandler = {
if (element && !SelectHelper.handleClick(element)) {
try {
let data = JSON.parse(aData);
[data.x, data.y] = ElementTouchHelper.toScreenCoords(element.ownerDocument.defaultView, data.x, data.y);
this._sendMouseEvent("mousemove", element, data.x, data.y);
this._sendMouseEvent("mousedown", element, data.x, data.y);
@ -2350,17 +2314,6 @@ var BrowserEventHandler = {
} else if (aTopic == "Gesture:DoubleTap") {
this._cancelTapHighlight();
this.onDoubleTap(aData);
} else if (aTopic == "dom-touch-listener-added") {
let tab = BrowserApp.getTabForWindow(aSubject);
if (!tab)
return;
sendMessageToJava({
gecko: {
type: "Tab:HasTouchListener",
tabID: tab.id
}
});
}
},
@ -2373,7 +2326,7 @@ var BrowserEventHandler = {
let win = BrowserApp.selectedBrowser.contentWindow;
let zoom = BrowserApp.selectedTab._viewport.zoom;
let zoom = BrowserApp.selectedTab._zoom;
let element = ElementTouchHelper.anyElementFromPoint(win, data.x, data.y);
if (!element) {
this._zoomOut();
@ -2392,7 +2345,7 @@ var BrowserEventHandler = {
const maxDifference = 20;
let rect = ElementTouchHelper.getBoundingContentRect(element);
let viewport = BrowserApp.selectedTab.viewport;
let viewport = BrowserApp.selectedTab.getViewport();
let vRect = new Rect(viewport.x, viewport.y, viewport.width, viewport.height);
let zoom = viewport.zoom;
@ -2483,7 +2436,6 @@ var BrowserEventHandler = {
let window = aElement.ownerDocument.defaultView;
try {
[aX, aY] = ElementTouchHelper.toBrowserCoords(window, aX, aY);
let cwu = window.top.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
aButton = aButton || 0;
cwu.sendMouseEventToWindow(aName, Math.round(aX), Math.round(aY), aButton, 1, 0, true);
@ -2590,38 +2542,7 @@ var BrowserEventHandler = {
const kReferenceDpi = 240; // standard "pixel" size used in some preferences
const ElementTouchHelper = {
toBrowserCoords: function(aWindow, aX, aY) {
if (!aWindow)
throw "Must provide a window";
let tab = BrowserApp.getTabForWindow(aWindow.top);
if (!tab)
throw "Unable to find a tab";
let viewport = tab.viewport;
return [
((aX - tab.viewportExcess.x) * viewport.zoom + viewport.offsetX),
((aY - tab.viewportExcess.y) * viewport.zoom + viewport.offsetY)
];
},
toScreenCoords: function(aWindow, aX, aY) {
if (!aWindow)
throw "Must provide a window";
let tab = BrowserApp.getTabForWindow(aWindow.top);
if (!tab)
throw "Unable to find a tab";
let viewport = tab.viewport;
return [
(aX - viewport.offsetX)/viewport.zoom + tab.viewportExcess.x,
(aY - viewport.offsetY)/viewport.zoom + tab.viewportExcess.y
];
},
anyElementFromPoint: function(aWindow, aX, aY) {
[aX, aY] = this.toScreenCoords(aWindow, aX, aY);
let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
let elem = cwu.elementFromPoint(aX, aY, false, true);
@ -2637,7 +2558,6 @@ const ElementTouchHelper = {
},
elementFromPoint: function(aWindow, aX, aY) {
[aX, aY] = this.toScreenCoords(aWindow, aX, aY);
// browser's elementFromPoint expect browser-relative client coordinates.
// subtract browser's scroll values to adjust
let cwu = aWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
@ -3032,7 +2952,7 @@ var FormAssistant = {
// the form assist popup.
_getElementPositionData: function _getElementPositionData(aElement) {
let rect = ElementTouchHelper.getBoundingContentRect(aElement);
let viewport = BrowserApp.selectedTab.viewport;
let viewport = BrowserApp.selectedTab.getViewport();
return { rect: [rect.x - (viewport.x / viewport.zoom),
rect.y - (viewport.y / viewport.zoom),
@ -3263,11 +3183,6 @@ var ViewportHandler = {
// ES6 weak map lets us avoid leaks.
_metadata: new WeakMap(),
// A list of document IDs, arbitrarily assigned. We use IDs to refer to content documents instead
// of strong references to avoid leaking them.
_documentIds: new WeakMap(),
_nextDocumentId: 0,
init: function init() {
addEventListener("DOMMetaAdded", this, false);
addEventListener("resize", this, false);
@ -3279,21 +3194,23 @@ var ViewportHandler = {
},
handleEvent: function handleEvent(aEvent) {
let target = aEvent.originalTarget;
let document = target.ownerDocument || target;
let browser = BrowserApp.getBrowserForDocument(document);
let tab = BrowserApp.getTabForBrowser(browser);
if (!tab)
return;
switch (aEvent.type) {
case "DOMMetaAdded":
if (target.name == "viewport")
let target = aEvent.originalTarget;
if (target.name != "viewport")
break;
let document = target.ownerDocument;
let browser = BrowserApp.getBrowserForDocument(document);
let tab = BrowserApp.getTabForBrowser(browser);
if (tab)
this.updateMetadata(tab);
break;
case "resize":
this.onResize();
// check dimensions changed to avoid infinite loop because updateViewportSize
// triggers a resize on the content window and will trigger this listener again
if (window.outerWidth != gScreenWidth || window.outerHeight != gScreenHeight)
BrowserApp.selectedTab.updateViewportSize();
break;
}
},
@ -3369,11 +3286,6 @@ var ViewportHandler = {
};
},
onResize: function onResize() {
for (let i = 0; i < BrowserApp.tabs.length; i++)
BrowserApp.tabs[i].updateViewportSize();
},
clamp: function(num, min, max) {
return Math.max(min, Math.min(max, num));
},
@ -3426,19 +3338,6 @@ var ViewportHandler = {
autoScale: true,
scaleRatio: ViewportHandler.getScaleRatio()
};
},
/**
* Returns a globally unique ID for the given content document. Using IDs to refer to documents
* allows content documents to be identified without any possibility of leaking them.
*/
getIdForDocument: function getIdForDocument(aDocument) {
let id = this._documentIds.get(aDocument, null);
if (id == null) {
id = this._nextDocumentId++;
this._documentIds.set(aDocument, id);
}
return id;
}
};
@ -4341,7 +4240,7 @@ OverscrollController.prototype = {
if (aCommand != "cmd_linePrevious" && aCommand != "cmd_scrollPageUp")
return false;
return (this.tab.viewport.y == 0);
return (this.tab.getViewport().y == 0);
},
isCommandEnabled : function isCommandEnabled(aCommand) {

View File

@ -236,6 +236,10 @@ pref("gfx.canvas.azure.enabled", true);
#endif
#endif
#ifdef ANDROID
pref("gfx.textures.poweroftwo.force-enabled", false);
#endif
pref("accessibility.browsewithcaret", false);
pref("accessibility.warn_on_browsewithcaret", true);
@ -3394,7 +3398,7 @@ pref("layers.acceleration.disabled", false);
// Whether to force acceleration on, ignoring blacklists.
pref("layers.acceleration.force-enabled", false);
pref("layers.acceleration.draw-fps", false);
pref("layers.acceleration.draw-fps", true);
pref("layers.offmainthreadcomposition.enabled", false);

View File

@ -322,7 +322,7 @@ SHELL_WRAPPER0(nativeInit)
SHELL_WRAPPER1(notifyGeckoOfEvent, jobject)
SHELL_WRAPPER0(processNextNativeEvent)
SHELL_WRAPPER1(setSurfaceView, jobject)
SHELL_WRAPPER1(setSoftwareLayerClient, jobject)
SHELL_WRAPPER2(setLayerClient, jobject, jint)
SHELL_WRAPPER0(onResume)
SHELL_WRAPPER0(onLowMemory)
SHELL_WRAPPER3(callObserver, jstring, jstring, jstring)
@ -334,6 +334,9 @@ SHELL_WRAPPER1(cameraCallbackBridge, jbyteArray)
SHELL_WRAPPER3(notifyBatteryChange, jdouble, jboolean, jdouble)
SHELL_WRAPPER3(notifySmsReceived, jstring, jstring, jlong)
SHELL_WRAPPER0(bindWidgetTexture)
SHELL_WRAPPER0(scheduleComposite)
SHELL_WRAPPER0(schedulePauseComposition)
SHELL_WRAPPER0(scheduleResumeComposition)
SHELL_WRAPPER3_WITH_RETURN(saveMessageInSentbox, jint, jstring, jstring, jlong)
SHELL_WRAPPER6(notifySmsSent, jint, jstring, jstring, jlong, jint, jlong)
SHELL_WRAPPER4(notifySmsDelivered, jint, jstring, jstring, jlong)
@ -635,7 +638,7 @@ loadGeckoLibs(const char *apkName)
GETFUNC(notifyGeckoOfEvent);
GETFUNC(processNextNativeEvent);
GETFUNC(setSurfaceView);
GETFUNC(setSoftwareLayerClient);
GETFUNC(setLayerClient);
GETFUNC(onResume);
GETFUNC(onLowMemory);
GETFUNC(callObserver);
@ -647,6 +650,9 @@ loadGeckoLibs(const char *apkName)
GETFUNC(notifyBatteryChange);
GETFUNC(notifySmsReceived);
GETFUNC(bindWidgetTexture);
GETFUNC(scheduleComposite);
GETFUNC(schedulePauseComposition);
GETFUNC(scheduleResumeComposition);
GETFUNC(saveMessageInSentbox);
GETFUNC(notifySmsSent);
GETFUNC(notifySmsDelivered);

View File

@ -35,6 +35,10 @@
*
* ***** END LICENSE BLOCK ***** */
#include "mozilla/Util.h"
#include "mozilla/layers/CompositorChild.h"
#include "mozilla/layers/CompositorParent.h"
#include <android/log.h>
#include <dlfcn.h>
@ -184,6 +188,13 @@ AndroidBridge::Init(JNIEnv *jEnv,
jStringClass = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("java/lang/String"));
#ifdef MOZ_JAVA_COMPOSITOR
jFlexSurfaceView = (jclass) jEnv->NewGlobalRef(jEnv->FindClass("org/mozilla/gecko/gfx/FlexibleGLSurfaceView"));
AndroidGLController::Init(jEnv);
AndroidEGLObject::Init(jEnv);
#endif
InitAndroidJavaWrappers(jEnv);
// jEnv should NOT be cached here by anything -- the jEnv here
@ -994,9 +1005,11 @@ AndroidBridge::SetSurfaceView(jobject obj)
}
void
AndroidBridge::SetSoftwareLayerClient(jobject obj)
AndroidBridge::SetLayerClient(jobject obj)
{
mSoftwareLayerClient.Init(obj);
AndroidGeckoLayerClient *client = new AndroidGeckoLayerClient();
client->Init(obj);
mLayerClient = client;
}
void
@ -1016,12 +1029,11 @@ AndroidBridge::CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoS
{
ALOG_BRIDGE("AndroidBridge::CallEglCreateWindowSurface");
JNIEnv *env = GetJNIEnv();
// Called off the main thread by the compositor
JNIEnv *env = GetJNIForThread();
if (!env)
return NULL;
AutoLocalJNIFrame jniFrame(env);
/*
* This is basically:
*
@ -1061,6 +1073,33 @@ AndroidBridge::CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoS
return (void*) realSurface;
}
static AndroidGLController sController;
void
AndroidBridge::RegisterCompositor()
{
ALOG_BRIDGE("AndroidBridge::RegisterCompositor");
JNIEnv *env = GetJNIForThread(); // called on the compositor thread
if (!env)
return;
AutoLocalJNIFrame jniFrame(env, 3);
jmethodID registerCompositor = env->GetStaticMethodID(jFlexSurfaceView, "registerCxxCompositor", "()Lorg/mozilla/gecko/gfx/GLController;");
jobject glController = env->CallStaticObjectMethod(jFlexSurfaceView, registerCompositor);
sController.Acquire(env, glController);
sController.SetGLVersion(2);
}
EGLSurface
AndroidBridge::ProvideEGLSurface()
{
sController.WaitForValidSurface();
return sController.ProvideEGLSurface();
}
bool
AndroidBridge::GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt)
{
@ -1822,6 +1861,54 @@ AndroidBridge::IsTablet()
return env->CallStaticBooleanMethod(mGeckoAppShellClass, jIsTablet);
}
void
AndroidBridge::SetCompositorParent(mozilla::layers::CompositorParent* aCompositorParent,
::base::Thread* aCompositorThread)
{
#ifdef MOZ_JAVA_COMPOSITOR
nsWindow::SetCompositorParent(aCompositorParent, aCompositorThread);
#endif
}
void
AndroidBridge::SetFirstPaintViewport(float aOffsetX, float aOffsetY, float aZoom, float aPageWidth, float aPageHeight)
{
AndroidGeckoLayerClient *client = mLayerClient;
if (!client)
return;
client->SetFirstPaintViewport(aOffsetX, aOffsetY, aZoom, aPageWidth, aPageHeight);
}
void
AndroidBridge::SetPageSize(float aZoom, float aPageWidth, float aPageHeight)
{
AndroidGeckoLayerClient *client = mLayerClient;
if (!client)
return;
client->SetPageSize(aZoom, aPageWidth, aPageHeight);
}
void
AndroidBridge::GetViewTransform(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY)
{
AndroidGeckoLayerClient *client = mLayerClient;
if (!client)
return;
client->GetViewTransform(aScrollOffset, aScaleX, aScaleY);
}
AndroidBridge::AndroidBridge()
: mLayerClient(NULL)
{
}
AndroidBridge::~AndroidBridge()
{
}
/* Implementation file */
NS_IMPL_ISUPPORTS1(nsAndroidBridge, nsIAndroidBridge)

View File

@ -49,11 +49,13 @@
#include "nsIObserver.h"
#include "nsThreadUtils.h"
#include "AndroidFlexViewWrapper.h"
#include "AndroidJavaWrappers.h"
#include "nsIMutableArray.h"
#include "nsIMIMEInfo.h"
#include "nsColor.h"
#include "BasicLayers.h"
#include "gfxRect.h"
#include "nsIAndroidBridge.h"
@ -71,6 +73,10 @@ extern "C" JNIEnv * GetJNIForThread();
extern bool mozilla_AndroidBridge_SetMainThread(void *);
extern jclass GetGeckoAppShellClass();
namespace base {
class Thread;
} // end namespace base
namespace mozilla {
namespace hal {
@ -84,6 +90,10 @@ struct SmsFilterData;
} // namespace sms
} // namespace dom
namespace layers {
class CompositorParent;
} // namespace layers
// The order and number of the members in this structure must correspond
// to the attrsAppearance array in GeckoAppShell.getSystemColors()
typedef struct AndroidSystemColors {
@ -110,6 +120,11 @@ public:
NOTIFY_IME_FOCUSCHANGE = 3
};
enum {
LAYER_CLIENT_TYPE_NONE = 0,
LAYER_CLIENT_TYPE_GL = 2 // AndroidGeckoGLLayerClient
};
static AndroidBridge *ConstructBridge(JNIEnv *jEnv,
jclass jGeckoAppShellClass);
@ -174,8 +189,8 @@ public:
void ScheduleRestart();
void SetSoftwareLayerClient(jobject jobj);
AndroidGeckoSoftwareLayerClient &GetSoftwareLayerClient() { return mSoftwareLayerClient; }
void SetLayerClient(jobject jobj);
AndroidGeckoLayerClient &GetLayerClient() { return *mLayerClient; }
void SetSurfaceView(jobject jobj);
AndroidGeckoSurfaceView& SurfaceView() { return mSurfaceView; }
@ -314,6 +329,10 @@ public:
/* See GLHelpers.java as to why this is needed */
void *CallEglCreateWindowSurface(void *dpy, void *config, AndroidGeckoSurfaceView& surfaceView);
// Switch Java to composite with the Gecko Compositor thread
void RegisterCompositor();
EGLSurface ProvideEGLSurface();
bool GetStaticStringField(const char *classID, const char *field, nsAString &result);
bool GetStaticIntField(const char *className, const char *fieldName, PRInt32* aInt);
@ -385,6 +404,12 @@ public:
void EnableNetworkNotifications();
void DisableNetworkNotifications();
void SetCompositorParent(mozilla::layers::CompositorParent* aCompositorParent,
base::Thread* aCompositorThread);
void SetFirstPaintViewport(float aOffsetX, float aOffsetY, float aZoom, float aPageWidth, float aPageHeight);
void SetPageSize(float aZoom, float aPageWidth, float aPageHeight);
void GetViewTransform(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY);
jobject CreateSurface();
void DestroySurface(jobject surface);
void ShowSurface(jobject surface, const gfxRect& aRect, bool aInverted, bool aBlend);
@ -402,12 +427,15 @@ protected:
// the GeckoSurfaceView
AndroidGeckoSurfaceView mSurfaceView;
AndroidGeckoSoftwareLayerClient mSoftwareLayerClient;
AndroidGeckoLayerClient *mLayerClient;
// the GeckoAppShell java class
jclass mGeckoAppShellClass;
AndroidBridge() { }
AndroidBridge();
~AndroidBridge();
bool Init(JNIEnv *jEnv, jclass jGeckoApp);
bool mOpenedGraphicsLibraries;
@ -497,6 +525,9 @@ protected:
jclass jEGLContextClass;
jclass jEGL10Class;
jclass jFlexSurfaceView;
jmethodID jRegisterCompositorMethod;
// some convinient types to have around
jclass jStringClass;

View File

@ -0,0 +1,96 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011-2012
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#include "AndroidFlexViewWrapper.h"
#include "nsDebug.h"
#define ASSERT_THREAD() \
NS_ASSERTION((void*)pthread_self() == mThread, "Something is calling AndroidGLController from the wrong thread!")
static jfieldID jEGLSurfacePointerField = 0;
void AndroidEGLObject::Init(JNIEnv* aJEnv) {
jclass jClass;
jClass = reinterpret_cast<jclass>
(aJEnv->NewGlobalRef(aJEnv->FindClass("com/google/android/gles_jni/EGLSurfaceImpl")));
jEGLSurfacePointerField = aJEnv->GetFieldID(jClass, "mEGLSurface", "I");
}
jmethodID AndroidGLController::jSetGLVersionMethod = 0;
jmethodID AndroidGLController::jWaitForValidSurfaceMethod = 0;
jmethodID AndroidGLController::jProvideEGLSurfaceMethod = 0;
void
AndroidGLController::Init(JNIEnv *aJEnv)
{
jclass jClass = reinterpret_cast<jclass>(aJEnv->NewGlobalRef(aJEnv->FindClass("org/mozilla/gecko/gfx/GLController")));
jSetGLVersionMethod = aJEnv->GetMethodID(jClass, "setGLVersion", "(I)V");
jProvideEGLSurfaceMethod = aJEnv->GetMethodID(jClass, "provideEGLSurface",
"()Ljavax/microedition/khronos/egl/EGLSurface;");
jWaitForValidSurfaceMethod = aJEnv->GetMethodID(jClass, "waitForValidSurface", "()V");
}
void
AndroidGLController::Acquire(JNIEnv* aJEnv, jobject aJObj)
{
mJEnv = aJEnv;
mThread = (void*)pthread_self();
mJObj = aJEnv->NewGlobalRef(aJObj);
}
void
AndroidGLController::SetGLVersion(int aVersion)
{
ASSERT_THREAD();
mJEnv->CallVoidMethod(mJObj, jSetGLVersionMethod, aVersion);
}
EGLSurface
AndroidGLController::ProvideEGLSurface()
{
ASSERT_THREAD();
jobject jObj = mJEnv->CallObjectMethod(mJObj, jProvideEGLSurfaceMethod);
return reinterpret_cast<EGLSurface>(mJEnv->GetIntField(jObj, jEGLSurfacePointerField));
}
void
AndroidGLController::WaitForValidSurface()
{
ASSERT_THREAD();
mJEnv->CallVoidMethod(mJObj, jWaitForValidSurfaceMethod);
}

View File

@ -0,0 +1,75 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is Mozilla Android code.
*
* The Initial Developer of the Original Code is Mozilla Foundation.
* Portions created by the Initial Developer are Copyright (C) 2011-2012
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Patrick Walton <pcwalton@mozilla.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef AndroidFlexViewWrapper_h__
#define AndroidFlexViewWrapper_h__
#include <jni.h>
#include <cassert>
#include <cstdlib>
#include <pthread.h>
#include <android/log.h>
class AndroidEGLObject {
public:
static void Init(JNIEnv* aJEnv);
};
typedef void *EGLSurface;
class AndroidGLController {
public:
static void Init(JNIEnv* aJEnv);
void Acquire(JNIEnv *aJEnv, jobject aJObj);
void SetGLVersion(int aVersion);
EGLSurface ProvideEGLSurface();
void WaitForValidSurface();
private:
static jmethodID jSetGLVersionMethod;
static jmethodID jWaitForValidSurfaceMethod;
static jmethodID jProvideEGLSurfaceMethod;
// the JNIEnv for the compositor thread
JNIEnv *mJEnv;
void *mThread;
jobject mJObj;
};
#endif

View File

@ -47,11 +47,8 @@
#define EGL_NATIVE_BUFFER_ANDROID 0x3140
#define EGL_IMAGE_PRESERVED_KHR 0x30D2
typedef void* EGLContext;
typedef void* EGLImageKHR;
typedef void* EGLClientBuffer;
typedef void* EGLDisplay;
typedef void *EGLContext;
typedef void *EGLDisplay;
typedef PRUint32 EGLenum;
typedef PRInt32 EGLint;
typedef PRUint32 EGLBoolean;

View File

@ -41,6 +41,9 @@
#include "gfxASurface.h"
#include "nsRect.h"
typedef void* EGLImageKHR;
typedef void* EGLClientBuffer;
namespace mozilla {
/**

View File

@ -78,7 +78,7 @@ extern "C" {
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_nativeInit(JNIEnv *, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyGeckoOfEvent(JNIEnv *, jclass, jobject event);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_processNextNativeEvent(JNIEnv *, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setSoftwareLayerClient(JNIEnv *jenv, jclass, jobject sv);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setLayerClient(JNIEnv *jenv, jclass, jobject sv);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_setSurfaceView(JNIEnv *jenv, jclass, jobject sv);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onResume(JNIEnv *, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_onLowMemory(JNIEnv *, jclass);
@ -104,8 +104,11 @@ extern "C" {
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_notifyReadingMessageListFailed(JNIEnv* jenv, jclass, jint, jint, jlong);
#ifdef MOZ_JAVA_COMPOSITOR
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_bindWidgetTexture(JNIEnv* jenv, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_scheduleComposite(JNIEnv* jenv, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_schedulePauseComposition(JNIEnv* jenv, jclass);
NS_EXPORT void JNICALL Java_org_mozilla_gecko_GeckoAppShell_scheduleResumeComposition(JNIEnv* jenv, jclass);
#endif
}
@ -142,9 +145,9 @@ Java_org_mozilla_gecko_GeckoAppShell_setSurfaceView(JNIEnv *jenv, jclass, jobjec
}
NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_setSoftwareLayerClient(JNIEnv *jenv, jclass, jobject obj)
Java_org_mozilla_gecko_GeckoAppShell_setLayerClient(JNIEnv *jenv, jclass, jobject obj)
{
AndroidBridge::Bridge()->SetSoftwareLayerClient(jenv->NewGlobalRef(obj));
AndroidBridge::Bridge()->SetLayerClient(jenv->NewGlobalRef(obj));
}
NS_EXPORT void JNICALL
@ -889,9 +892,21 @@ Java_org_mozilla_gecko_GeckoAppShell_notifyReadingMessageListFailed(JNIEnv* jenv
#ifdef MOZ_JAVA_COMPOSITOR
NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_bindWidgetTexture(JNIEnv* jenv, jclass)
Java_org_mozilla_gecko_GeckoAppShell_scheduleComposite(JNIEnv*, jclass)
{
nsWindow::BindToTexture();
nsWindow::ScheduleComposite();
}
NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_schedulePauseComposition(JNIEnv*, jclass)
{
nsWindow::SchedulePauseComposition();
}
NS_EXPORT void JNICALL
Java_org_mozilla_gecko_GeckoAppShell_scheduleResumeComposition(JNIEnv*, jclass)
{
nsWindow::ScheduleResumeComposition();
}
#endif

View File

@ -92,11 +92,27 @@ jmethodID AndroidLocation::jGetBearingMethod = 0;
jmethodID AndroidLocation::jGetSpeedMethod = 0;
jmethodID AndroidLocation::jGetTimeMethod = 0;
jclass AndroidGeckoSoftwareLayerClient::jGeckoSoftwareLayerClientClass = 0;
jmethodID AndroidGeckoSoftwareLayerClient::jLockBufferMethod = 0;
jmethodID AndroidGeckoSoftwareLayerClient::jUnlockBufferMethod = 0;
jmethodID AndroidGeckoSoftwareLayerClient::jBeginDrawingMethod = 0;
jmethodID AndroidGeckoSoftwareLayerClient::jEndDrawingMethod = 0;
jclass AndroidGeckoLayerClient::jGeckoLayerClientClass = 0;
jmethodID AndroidGeckoLayerClient::jBeginDrawingMethod = 0;
jmethodID AndroidGeckoLayerClient::jEndDrawingMethod = 0;
jmethodID AndroidGeckoLayerClient::jSetFirstPaintViewport = 0;
jmethodID AndroidGeckoLayerClient::jSetPageSize = 0;
jmethodID AndroidGeckoLayerClient::jGetViewTransformMethod = 0;
jmethodID AndroidGeckoLayerClient::jCreateFrameMethod = 0;
jmethodID AndroidGeckoLayerClient::jActivateProgramMethod = 0;
jmethodID AndroidGeckoLayerClient::jDeactivateProgramMethod = 0;
jclass AndroidLayerRendererFrame::jLayerRendererFrameClass = 0;
jmethodID AndroidLayerRendererFrame::jBeginDrawingMethod = 0;
jmethodID AndroidLayerRendererFrame::jDrawBackgroundMethod = 0;
jmethodID AndroidLayerRendererFrame::jDrawForegroundMethod = 0;
jmethodID AndroidLayerRendererFrame::jEndDrawingMethod = 0;
jclass AndroidViewTransform::jViewTransformClass = 0;
jfieldID AndroidViewTransform::jXField = 0;
jfieldID AndroidViewTransform::jYField = 0;
jfieldID AndroidViewTransform::jScaleField = 0;
jclass AndroidGeckoSurfaceView::jGeckoSurfaceViewClass = 0;
jmethodID AndroidGeckoSurfaceView::jBeginDrawingMethod = 0;
jmethodID AndroidGeckoSurfaceView::jEndDrawingMethod = 0;
@ -126,7 +142,9 @@ mozilla::InitAndroidJavaWrappers(JNIEnv *jEnv)
AndroidPoint::InitPointClass(jEnv);
AndroidLocation::InitLocationClass(jEnv);
AndroidRect::InitRectClass(jEnv);
AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(jEnv);
AndroidGeckoLayerClient::InitGeckoLayerClientClass(jEnv);
AndroidLayerRendererFrame::InitLayerRendererFrameClass(jEnv);
AndroidViewTransform::InitViewTransformClass(jEnv);
AndroidGeckoSurfaceView::InitGeckoSurfaceViewClass(jEnv);
}
@ -245,18 +263,51 @@ AndroidRect::InitRectClass(JNIEnv *jEnv)
}
void
AndroidGeckoSoftwareLayerClient::InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv)
AndroidGeckoLayerClient::InitGeckoLayerClientClass(JNIEnv *jEnv)
{
#ifdef MOZ_JAVA_COMPOSITOR
initInit();
jGeckoSoftwareLayerClientClass =
getClassGlobalRef("org/mozilla/gecko/gfx/GeckoSoftwareLayerClient");
jGeckoLayerClientClass = getClassGlobalRef("org/mozilla/gecko/gfx/GeckoLayerClient");
jLockBufferMethod = getMethod("lockBuffer", "()Ljava/nio/ByteBuffer;");
jUnlockBufferMethod = getMethod("unlockBuffer", "()V");
jBeginDrawingMethod = getMethod("beginDrawing", "(IIIILjava/lang/String;Z)Landroid/graphics/Rect;");
jEndDrawingMethod = getMethod("endDrawing", "(IIII)V");
jBeginDrawingMethod = getMethod("beginDrawing", "(IILjava/lang/String;)Z");
jEndDrawingMethod = getMethod("endDrawing", "()V");
jSetFirstPaintViewport = getMethod("setFirstPaintViewport", "(FFFFF)V");
jSetPageSize = getMethod("setPageSize", "(FFF)V");
jGetViewTransformMethod = getMethod("getViewTransform",
"()Lorg/mozilla/gecko/gfx/ViewTransform;");
jCreateFrameMethod = getMethod("createFrame", "()Lorg/mozilla/gecko/gfx/LayerRenderer$Frame;");
jActivateProgramMethod = getMethod("activateProgram", "()V");
jDeactivateProgramMethod = getMethod("deactivateProgram", "()V");
#endif
}
void
AndroidLayerRendererFrame::InitLayerRendererFrameClass(JNIEnv *jEnv)
{
#ifdef MOZ_JAVA_COMPOSITOR
initInit();
jLayerRendererFrameClass = getClassGlobalRef("org/mozilla/gecko/gfx/LayerRenderer$Frame");
jBeginDrawingMethod = getMethod("beginDrawing", "()V");
jDrawBackgroundMethod = getMethod("drawBackground", "()V");
jDrawForegroundMethod = getMethod("drawForeground", "()V");
jEndDrawingMethod = getMethod("endDrawing", "()V");
#endif
}
void
AndroidViewTransform::InitViewTransformClass(JNIEnv *jEnv)
{
#ifdef MOZ_JAVA_COMPOSITOR
initInit();
jViewTransformClass = getClassGlobalRef("org/mozilla/gecko/gfx/ViewTransform");
jXField = getField("x", "F");
jYField = getField("y", "F");
jScaleField = getField("scale", "F");
#endif
}
@ -373,7 +424,7 @@ AndroidGeckoEvent::Init(JNIEnv *jenv, jobject jobj)
switch (mType) {
case SIZE_CHANGED:
ReadPointArray(mPoints, jenv, jPoints, 3);
ReadPointArray(mPoints, jenv, jPoints, 2);
break;
case KEY_EVENT:
@ -521,12 +572,40 @@ AndroidPoint::Init(JNIEnv *jenv, jobject jobj)
}
void
AndroidGeckoSoftwareLayerClient::Init(jobject jobj)
AndroidGeckoLayerClient::Init(jobject jobj)
{
NS_ASSERTION(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
wrapped_obj = jobj;
}
void
AndroidLayerRendererFrame::Init(jobject jobj)
{
if (!isNull()) {
Dispose();
}
wrapped_obj = GetJNIForThread()->NewGlobalRef(jobj);
}
void
AndroidLayerRendererFrame::Dispose()
{
if (isNull()) {
return;
}
GetJNIForThread()->DeleteGlobalRef(wrapped_obj);
wrapped_obj = 0;
}
void
AndroidViewTransform::Init(jobject jobj)
{
NS_ABORT_IF_FALSE(wrapped_obj == nsnull, "Init called on non-null wrapped_obj!");
wrapped_obj = jobj;
}
void
AndroidGeckoSurfaceView::Init(jobject jobj)
{
@ -577,83 +656,73 @@ AndroidGeckoSurfaceView::Draw2D(jobject buffer, int stride)
env->CallVoidMethod(wrapped_obj, jDraw2DBufferMethod, buffer, stride);
}
jobject
AndroidGeckoSoftwareLayerClient::LockBuffer()
{
NS_ASSERTION(!isNull(), "LockBuffer() called on null software layer client!");
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (!env)
return nsnull;
AndroidBridge::AutoLocalJNIFrame(env, 1);
return env->CallObjectMethod(wrapped_obj, jLockBufferMethod);
}
unsigned char *
AndroidGeckoSoftwareLayerClient::LockBufferBits()
{
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (!env)
return nsnull;
AndroidBridge::AutoLocalJNIFrame(env, 1);
jobject bufferObject = LockBuffer();
if (bufferObject != nsnull)
return reinterpret_cast<unsigned char *>(env->GetDirectBufferAddress(bufferObject));
return nsnull;
}
void
AndroidGeckoSoftwareLayerClient::UnlockBuffer()
{
NS_ASSERTION(!isNull(), "UnlockBuffer() called on null software layer client!");
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (!env)
return;
AndroidBridge::AutoLocalJNIFrame(env, 1);
env->CallVoidMethod(wrapped_obj, jUnlockBufferMethod);
}
bool
AndroidGeckoSoftwareLayerClient::BeginDrawing(int aWidth, int aHeight, int aTileWidth, int aTileHeight, nsIntRect &aDirtyRect, const nsAString &aMetadata, bool aHasDirectTexture)
AndroidGeckoLayerClient::BeginDrawing(int aWidth, int aHeight, const nsAString &aMetadata)
{
NS_ASSERTION(!isNull(), "BeginDrawing() called on null software layer client!");
NS_ASSERTION(!isNull(), "BeginDrawing() called on null layer client!");
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (!env)
return false;
AndroidBridge::AutoLocalJNIFrame(env, 1);
AndroidBridge::AutoLocalJNIFrame jniFrame(env);
jstring jMetadata = env->NewString(nsPromiseFlatString(aMetadata).get(), aMetadata.Length());
jobject rectObject = env->CallObjectMethod(wrapped_obj, jBeginDrawingMethod,
aWidth, aHeight, aTileWidth, aTileHeight,
jMetadata, aHasDirectTexture);
if (rectObject == nsnull)
return false;
AndroidRect rect(env, rectObject);
nsIntRect newDirtyRect = aDirtyRect.Intersect(nsIntRect(rect.Top(), rect.Left(),
rect.Width(), rect.Height()));
aDirtyRect.SetRect(newDirtyRect.x, newDirtyRect.y, newDirtyRect.width, newDirtyRect.height);
return true;
return env->CallBooleanMethod(wrapped_obj, jBeginDrawingMethod, aWidth, aHeight, jMetadata);
}
void
AndroidGeckoSoftwareLayerClient::EndDrawing(const nsIntRect &aRect)
AndroidGeckoLayerClient::EndDrawing()
{
NS_ASSERTION(!isNull(), "EndDrawing() called on null software layer client!");
NS_ASSERTION(!isNull(), "EndDrawing() called on null layer client!");
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (!env)
return;
AndroidBridge::AutoLocalJNIFrame(env, 1);
return env->CallVoidMethod(wrapped_obj, jEndDrawingMethod, aRect.x, aRect.y, aRect.width, aRect.height);
AndroidBridge::AutoLocalJNIFrame jniFrame(env);
return env->CallVoidMethod(wrapped_obj, jEndDrawingMethod);
}
void
AndroidGeckoLayerClient::SetFirstPaintViewport(float aOffsetX, float aOffsetY, float aZoom, float aPageWidth, float aPageHeight)
{
NS_ASSERTION(!isNull(), "SetFirstPaintViewport called on null layer client!");
JNIEnv *env = GetJNIForThread(); // this is called on the compositor thread
if (!env)
return;
AndroidBridge::AutoLocalJNIFrame jniFrame(env);
return env->CallVoidMethod(wrapped_obj, jSetFirstPaintViewport, aOffsetX, aOffsetY, aZoom, aPageWidth, aPageHeight);
}
void
AndroidGeckoLayerClient::SetPageSize(float aZoom, float aPageWidth, float aPageHeight)
{
NS_ASSERTION(!isNull(), "SetPageSize called on null layer client!");
JNIEnv *env = GetJNIForThread(); // this is called on the compositor thread
if (!env)
return;
AndroidBridge::AutoLocalJNIFrame jniFrame(env);
return env->CallVoidMethod(wrapped_obj, jSetPageSize, aZoom, aPageWidth, aPageHeight);
}
void
AndroidGeckoLayerClient::GetViewTransform(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY)
{
NS_ASSERTION(!isNull(), "GetViewTransform called on null layer client!");
JNIEnv *env = GetJNIForThread(); // this is called on the compositor thread
if (!env)
return;
AndroidViewTransform viewTransform;
AndroidBridge::AutoLocalJNIFrame jniFrame(env);
jobject viewTransformJObj = env->CallObjectMethod(wrapped_obj, jGetViewTransformMethod);
NS_ABORT_IF_FALSE(viewTransformJObj, "No view transform object!");
viewTransform.Init(viewTransformJObj);
aScrollOffset = nsIntPoint(viewTransform.GetX(), viewTransform.GetY());
aScaleX = aScaleY = viewTransform.GetScale();
}
jobject
@ -689,11 +758,120 @@ AndroidGeckoSurfaceView::GetSurface()
jobject
AndroidGeckoSurfaceView::GetSurfaceHolder()
{
JNIEnv *env = AndroidBridge::GetJNIEnv();
if (!env)
return nsnull;
return GetJNIForThread()->CallObjectMethod(wrapped_obj, jGetHolderMethod);
}
return env->CallObjectMethod(wrapped_obj, jGetHolderMethod);
void
AndroidGeckoLayerClient::CreateFrame(AndroidLayerRendererFrame& aFrame)
{
JNIEnv *env = GetJNIForThread();
NS_ABORT_IF_FALSE(env, "No JNI environment at CreateFrame()!");
if (!env) {
return;
}
jobject frameJObj = env->CallObjectMethod(wrapped_obj, jCreateFrameMethod);
NS_ABORT_IF_FALSE(frameJObj, "No frame object!");
aFrame.Init(frameJObj);
}
void
AndroidGeckoLayerClient::ActivateProgram()
{
JNIEnv *env = GetJNIForThread();
NS_ABORT_IF_FALSE(env, "No JNI environment at ActivateProgram()!");
if (!env) {
return;
}
env->CallVoidMethod(wrapped_obj, jActivateProgramMethod);
}
void
AndroidGeckoLayerClient::DeactivateProgram()
{
JNIEnv *env = GetJNIForThread();
NS_ABORT_IF_FALSE(env, "No JNI environment at DeactivateProgram()!");
if (!env) {
return;
}
env->CallVoidMethod(wrapped_obj, jDeactivateProgramMethod);
}
void
AndroidLayerRendererFrame::BeginDrawing()
{
JNIEnv *env = GetJNIForThread();
NS_ABORT_IF_FALSE(env, "No JNI environment at BeginDrawing()!");
if (!env) {
return;
}
env->CallVoidMethod(wrapped_obj, jBeginDrawingMethod);
}
void
AndroidLayerRendererFrame::DrawBackground()
{
JNIEnv *env = GetJNIForThread();
NS_ABORT_IF_FALSE(env, "No JNI environment at DrawBackground()!");
if (!env) {
return;
}
env->CallVoidMethod(wrapped_obj, jDrawBackgroundMethod);
}
void
AndroidLayerRendererFrame::DrawForeground()
{
JNIEnv *env = GetJNIForThread();
NS_ABORT_IF_FALSE(env, "No JNI environment at DrawForeground()!");
if (!env) {
return;
}
env->CallVoidMethod(wrapped_obj, jDrawForegroundMethod);
}
void
AndroidLayerRendererFrame::EndDrawing()
{
JNIEnv *env = GetJNIForThread();
NS_ABORT_IF_FALSE(env, "No JNI environment at EndDrawing()!");
if (!env) {
return;
}
env->CallVoidMethod(wrapped_obj, jEndDrawingMethod);
}
float
AndroidViewTransform::GetX()
{
JNIEnv *env = GetJNIForThread();
if (!env)
return 0.0f;
return env->GetFloatField(wrapped_obj, jXField);
}
float
AndroidViewTransform::GetY()
{
JNIEnv *env = GetJNIForThread();
if (!env)
return 0.0f;
return env->GetFloatField(wrapped_obj, jYField);
}
float
AndroidViewTransform::GetScale()
{
JNIEnv *env = GetJNIForThread();
if (!env)
return 0.0f;
return env->GetFloatField(wrapped_obj, jScaleField);
}
void

View File

@ -58,6 +58,8 @@
namespace mozilla {
class AndroidGeckoLayerClient;
void InitAndroidJavaWrappers(JNIEnv *jEnv);
/*
@ -151,31 +153,75 @@ protected:
static jfieldID jTopField;
};
class AndroidGeckoSoftwareLayerClient : public WrappedJavaObject {
class AndroidViewTransform : public WrappedJavaObject {
public:
static void InitGeckoSoftwareLayerClientClass(JNIEnv *jEnv);
void Init(jobject jobj);
AndroidGeckoSoftwareLayerClient() {}
AndroidGeckoSoftwareLayerClient(jobject jobj) { Init(jobj); }
static void InitViewTransformClass(JNIEnv *jEnv);
jobject LockBuffer();
unsigned char *LockBufferBits();
void UnlockBuffer();
bool BeginDrawing(int aWidth, int aHeight, int aTileWidth, int aTileHeight, nsIntRect &aDirtyRect, const nsAString &aMetadata, bool aHasDirectTexture);
void EndDrawing(const nsIntRect &aRect);
void Init(jobject jobj);
AndroidViewTransform() {}
AndroidViewTransform(jobject jobj) { Init(jobj); }
float GetX();
float GetY();
float GetScale();
private:
static jclass jGeckoSoftwareLayerClientClass;
static jmethodID jLockBufferMethod;
static jmethodID jUnlockBufferMethod;
protected:
static jmethodID jBeginDrawingMethod;
static jmethodID jEndDrawingMethod;
static jclass jViewTransformClass;
static jfieldID jXField;
static jfieldID jYField;
static jfieldID jScaleField;
};
class AndroidLayerRendererFrame : public WrappedJavaObject {
public:
static void InitLayerRendererFrameClass(JNIEnv *jEnv);
void Init(jobject jobj);
void Dispose();
void BeginDrawing();
void DrawBackground();
void DrawForeground();
void EndDrawing();
private:
static jclass jLayerRendererFrameClass;
static jmethodID jBeginDrawingMethod;
static jmethodID jDrawBackgroundMethod;
static jmethodID jDrawForegroundMethod;
static jmethodID jEndDrawingMethod;
};
class AndroidGeckoLayerClient : public WrappedJavaObject {
public:
static void InitGeckoLayerClientClass(JNIEnv *jEnv);
void Init(jobject jobj);
AndroidGeckoLayerClient() {}
AndroidGeckoLayerClient(jobject jobj) { Init(jobj); }
bool BeginDrawing(int aWidth, int aHeight, const nsAString &aMetadata);
void EndDrawing();
void SetFirstPaintViewport(float aOffsetX, float aOffsetY, float aZoom, float aPageWidth, float aPageHeight);
void SetPageSize(float aZoom, float aPageWidth, float aPageHeight);
void GetViewTransform(nsIntPoint& aScrollOffset, float& aScaleX, float& aScaleY);
void CreateFrame(AndroidLayerRendererFrame& aFrame);
void ActivateProgram();
void DeactivateProgram();
protected:
static jclass jGeckoLayerClientClass;
static jmethodID jBeginDrawingMethod;
static jmethodID jEndDrawingMethod;
static jmethodID jSetFirstPaintViewport;
static jmethodID jSetPageSize;
static jmethodID jGetViewTransformMethod;
static jmethodID jCreateFrameMethod;
static jmethodID jActivateProgramMethod;
static jmethodID jDeactivateProgramMethod;
};
class AndroidGeckoSurfaceView : public WrappedJavaObject
{

View File

@ -137,9 +137,6 @@ void AndroidMediaLayer::UpdatePosition(const gfxRect& aRect, float aZoomLevel) {
std::map<void*, SurfaceData*>::iterator it;
if (EnsureContentSurface())
AndroidBridge::Bridge()->ShowSurface(mContentData.surface, aRect, mInverted, true);
for (it = mVideoSurfaces.begin(); it != mVideoSurfaces.end(); it++) {
SurfaceData* data = it->second;
@ -152,6 +149,10 @@ void AndroidMediaLayer::UpdatePosition(const gfxRect& aRect, float aZoomLevel) {
scaledDimensions.width, scaledDimensions.height);
AndroidBridge::Bridge()->ShowSurface(data->surface, videoRect, mInverted, false);
}
if (EnsureContentSurface()) {
AndroidBridge::Bridge()->ShowSurface(mContentData.surface, aRect, mInverted, true);
}
}
void AndroidMediaLayer::SetVisible(bool aVisible) {

View File

@ -62,6 +62,7 @@ CPPSRCS = \
AndroidJavaWrappers.cpp \
AndroidBridge.cpp \
AndroidDirectTexture.cpp \
AndroidFlexViewWrapper.cpp \
AndroidGraphicBuffer.cpp \
AndroidJNI.cpp \
AndroidMediaLayer.cpp \
@ -91,7 +92,7 @@ XPIDLSRCS = \
SHARED_LIBRARY_LIBS = ../xpwidgets/libxpwidgets_s.a
EXPORTS = AndroidBridge.h AndroidJavaWrappers.h
EXPORTS = AndroidBridge.h AndroidJavaWrappers.h AndroidFlexViewWrapper.h
include $(topsrcdir)/config/rules.mk

View File

@ -481,7 +481,7 @@ nsAppShell::ProcessNextNativeEvent(bool mayWait)
break;
nsTArray<nsIntPoint> points = curEvent->Points();
NS_ASSERTION(points.Length() != 2, "Screenshot event does not have enough coordinates");
NS_ASSERTION(points.Length() == 2, "Screenshot event does not have enough coordinates");
bridge->TakeScreenshot(domWindow, 0, 0, points[0].x, points[0].y, points[1].x, points[1].y, curEvent->MetaState(), scale);
break;
}

View File

@ -45,6 +45,7 @@
#include "mozilla/dom/ContentChild.h"
#include "mozilla/unused.h"
#include "mozilla/Preferences.h"
#include "mozilla/layers/RenderTrace.h"
using mozilla::dom::ContentParent;
using mozilla::dom::ContentChild;
@ -88,7 +89,6 @@ NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget)
// The dimensions of the current android view
static gfxIntSize gAndroidBounds = gfxIntSize(0, 0);
static gfxIntSize gAndroidTileSize = gfxIntSize(0, 0);
static gfxIntSize gAndroidScreenBounds;
#ifdef ACCESSIBILITY
@ -96,14 +96,10 @@ bool nsWindow::sAccessibilityEnabled = false;
#endif
#ifdef MOZ_JAVA_COMPOSITOR
#include "mozilla/layers/CompositorChild.h"
#include "mozilla/layers/CompositorParent.h"
#include "mozilla/Mutex.h"
#include "nsThreadUtils.h"
#include "AndroidDirectTexture.h"
static AndroidDirectTexture* sDirectTexture = new AndroidDirectTexture(2048, 2048,
AndroidGraphicBuffer::UsageSoftwareWrite | AndroidGraphicBuffer::UsageTexture,
gfxASurface::ImageFormatRGB16_565);
#endif
@ -218,6 +214,9 @@ nsWindow::~nsWindow()
mRootAccessible = nsnull;
#endif
ALOG("nsWindow %p destructor", (void*)this);
AndroidBridge::Bridge()->SetCompositorParent(NULL, NULL);
}
bool
@ -766,8 +765,6 @@ nsWindow::GetLayerManager(PLayersChild*, LayersBackend, LayerManagerPersistence,
return mLayerManager;
}
printf_stderr("nsWindow::GetLayerManager\n");
nsWindow *topWindow = TopWindow();
if (!topWindow) {
@ -776,6 +773,20 @@ nsWindow::GetLayerManager(PLayersChild*, LayersBackend, LayerManagerPersistence,
return mLayerManager;
}
bool useCompositor =
Preferences::GetBool("layers.offmainthreadcomposition.enabled", false);
if (useCompositor) {
CreateCompositor();
if (mLayerManager) {
AndroidBridge::Bridge()->SetCompositorParent(mCompositorParent, mCompositorThread);
return mLayerManager;
}
// If we get here, then off main thread compositing failed to initialize.
sFailedToCreateGLContext = true;
}
mUseAcceleratedRendering = GetShouldAccelerate();
if (!mUseAcceleratedRendering ||
@ -786,102 +797,32 @@ nsWindow::GetLayerManager(PLayersChild*, LayersBackend, LayerManagerPersistence,
return mLayerManager;
}
if (!sGLContext) {
// the window we give doesn't matter here
sGLContext = mozilla::gl::GLContextProvider::CreateForWindow(this);
}
if (!mLayerManager) {
if (!sGLContext) {
// the window we give doesn't matter here
sGLContext = mozilla::gl::GLContextProvider::CreateForWindow(this);
}
if (sGLContext) {
nsRefPtr<mozilla::layers::LayerManagerOGL> layerManager =
new mozilla::layers::LayerManagerOGL(this);
if (sGLContext) {
nsRefPtr<mozilla::layers::LayerManagerOGL> layerManager =
new mozilla::layers::LayerManagerOGL(this);
if (layerManager && layerManager->Initialize(sGLContext))
mLayerManager = layerManager;
sValidSurface = true;
}
if (layerManager && layerManager->Initialize(sGLContext))
mLayerManager = layerManager;
sValidSurface = true;
}
if (!sGLContext || !mLayerManager) {
sGLContext = nsnull;
sFailedToCreateGLContext = true;
if (!sGLContext || !mLayerManager) {
sGLContext = nsnull;
sFailedToCreateGLContext = true;
mLayerManager = CreateBasicLayerManager();
mLayerManager = CreateBasicLayerManager();
}
}
return mLayerManager;
}
gfxASurface*
nsWindow::GetThebesSurface()
{
/* This is really a dummy surface; this is only used when doing reflow, because
* we need a RenderingContext to measure text against.
*/
// XXX this really wants to return already_AddRefed, but this only really gets used
// on direct assignment to a gfxASurface
return new gfxImageSurface(gfxIntSize(5,5), gfxImageSurface::ImageFormatRGB24);
}
#ifdef MOZ_JAVA_COMPOSITOR
void
nsWindow::BindToTexture()
{
sDirectTexture->Bind();
}
bool
nsWindow::HasDirectTexture()
{
static bool sTestedDirectTexture = false;
static bool sHasDirectTexture = false;
// If we already tested, return early
if (sTestedDirectTexture)
return sHasDirectTexture;
sTestedDirectTexture = true;
nsAutoString board;
AndroidGraphicBuffer* buffer = NULL;
unsigned char* bits = NULL;
if (AndroidGraphicBuffer::IsBlacklisted()) {
ALOG("device is blacklisted for direct texture");
goto cleanup;
}
buffer = new AndroidGraphicBuffer(512, 512,
AndroidGraphicBuffer::UsageSoftwareWrite | AndroidGraphicBuffer::UsageTexture,
gfxASurface::ImageFormatRGB16_565);
if (buffer->Lock(AndroidGraphicBuffer::UsageSoftwareWrite, &bits) != 0 || !bits) {
ALOG("failed to lock graphic buffer");
buffer->Unlock();
goto cleanup;
}
if (buffer->Unlock() != 0) {
ALOG("failed to unlock graphic buffer");
goto cleanup;
}
if (!buffer->Reallocate(1024, 1024, gfxASurface::ImageFormatRGB16_565)) {
ALOG("failed to reallocate graphic buffer");
goto cleanup;
}
sHasDirectTexture = true;
cleanup:
if (buffer)
delete buffer;
return sHasDirectTexture;
}
#endif
void
nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae)
{
@ -903,7 +844,7 @@ nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae)
}
case AndroidGeckoEvent::SIZE_CHANGED: {
nsTArray<nsIntPoint> points = ae->Points();
NS_ASSERTION(points.Length() != 3, "Size changed does not have enough coordinates");
NS_ASSERTION(points.Length() == 2, "Size changed does not have enough coordinates");
int nw = points[0].x;
int nh = points[0].y;
@ -923,9 +864,6 @@ nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae)
}
}
gAndroidTileSize.width = points[2].x;
gAndroidTileSize.height = points[2].y;
int newScreenWidth = points[1].x;
int newScreenHeight = points[1].y;
@ -1003,7 +941,9 @@ nsWindow::OnGlobalAndroidEvent(AndroidGeckoEvent *ae)
break;
case AndroidGeckoEvent::DRAW:
layers::renderTraceEventStart("Global draw start", "414141");
win->OnDraw(ae);
layers::renderTraceEventEnd("414141");
break;
case AndroidGeckoEvent::IME_EVENT:
@ -1079,6 +1019,7 @@ nsWindow::DrawTo(gfxASurface *targetSurface)
bool
nsWindow::DrawTo(gfxASurface *targetSurface, const nsIntRect &invalidRect)
{
mozilla::layers::RenderTraceScope trace("DrawTo", "717171");
if (!mIsVisible)
return false;
@ -1106,9 +1047,11 @@ nsWindow::DrawTo(gfxASurface *targetSurface, const nsIntRect &invalidRect)
switch (GetLayerManager(nsnull)->GetBackendType()) {
case LayerManager::LAYERS_BASIC: {
nsRefPtr<gfxContext> ctx = new gfxContext(targetSurface);
{
mozilla::layers::RenderTraceScope trace2("Basic DrawTo", "727272");
AutoLayerManagerSetup
setupLayerManager(this, ctx, BasicLayerManager::BUFFER_NONE);
@ -1128,6 +1071,7 @@ nsWindow::DrawTo(gfxASurface *targetSurface, const nsIntRect &invalidRect)
}
case LayerManager::LAYERS_OPENGL: {
static_cast<mozilla::layers::LayerManagerOGL*>(GetLayerManager(nsnull))->
SetClippingRegion(nsIntRegion(boundsRect));
@ -1187,11 +1131,14 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
return;
}
nsRefPtr<nsWindow> kungFuDeathGrip(this);
AndroidBridge::AutoLocalJNIFrame jniFrame;
#ifdef MOZ_JAVA_COMPOSITOR
// We haven't been given a window-size yet, so do nothing
if (gAndroidBounds.width <= 0 || gAndroidBounds.height <= 0)
if (gAndroidBounds.width <= 0 || gAndroidBounds.height <= 0) {
return;
}
/*
* Check to see whether the presentation shell corresponding to the document on the screen
@ -1201,6 +1148,7 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
nsCOMPtr<nsIAndroidDrawMetadataProvider> metadataProvider =
AndroidBridge::Bridge()->GetDrawMetadataProvider();
layers::renderTraceEventStart("Check supress", "424242");
bool paintingSuppressed = false;
if (metadataProvider) {
metadataProvider->PaintingSuppressed(&paintingSuppressed);
@ -1208,73 +1156,42 @@ nsWindow::OnDraw(AndroidGeckoEvent *ae)
if (paintingSuppressed) {
return;
}
layers::renderTraceEventEnd("Check supress", "424242");
layers::renderTraceEventStart("Get Drawable", "424343");
nsAutoString metadata;
if (metadataProvider) {
metadataProvider->GetDrawMetadata(metadata);
}
layers::renderTraceEventEnd("Get Drawable", "424343");
layers::renderTraceEventStart("Get surface", "424545");
static unsigned char bits2[32 * 32 * 2];
nsRefPtr<gfxImageSurface> targetSurface =
new gfxImageSurface(bits2, gfxIntSize(32, 32), 32 * 2,
gfxASurface::ImageFormatRGB16_565);
layers::renderTraceEventEnd("Get surface", "424545");
layers::renderTraceEventStart("Check Bridge", "434444");
nsIntRect dirtyRect = ae->Rect().Intersect(nsIntRect(0, 0, gAndroidBounds.width, gAndroidBounds.height));
AndroidGeckoSoftwareLayerClient &client =
AndroidBridge::Bridge()->GetSoftwareLayerClient();
if (!client.BeginDrawing(gAndroidBounds.width, gAndroidBounds.height,
gAndroidTileSize.width, gAndroidTileSize.height,
dirtyRect, metadata, HasDirectTexture())) {
AndroidGeckoLayerClient &client = AndroidBridge::Bridge()->GetLayerClient();
if (!client.BeginDrawing(gAndroidBounds.width, gAndroidBounds.height, metadata)) {
return;
}
layers::renderTraceEventEnd("Check Bridge", "434444");
unsigned char *bits = NULL;
if (HasDirectTexture()) {
if (sDirectTexture->Width() != gAndroidBounds.width ||
sDirectTexture->Height() != gAndroidBounds.height) {
sDirectTexture->Reallocate(gAndroidBounds.width, gAndroidBounds.height);
}
sDirectTexture->Lock(AndroidGraphicBuffer::UsageSoftwareWrite, dirtyRect, &bits);
layers::renderTraceEventStart("Widget draw to", "434646");
if (targetSurface->CairoStatus()) {
ALOG("### Failed to create a valid surface from the bitmap");
} else {
bits = client.LockBufferBits();
DrawTo(targetSurface, dirtyRect);
}
if (!bits) {
ALOG("### Failed to lock buffer");
} else {
// If tile size is 0,0, we assume we only have a single tile
int tileWidth = (gAndroidTileSize.width > 0) ? gAndroidTileSize.width : gAndroidBounds.width;
int tileHeight = (gAndroidTileSize.height > 0) ? gAndroidTileSize.height : gAndroidBounds.height;
layers::renderTraceEventEnd("Widget draw to", "434646");
int offset = 0;
for (int y = 0; y < gAndroidBounds.height; y += tileHeight) {
for (int x = 0; x < gAndroidBounds.width; x += tileWidth) {
int width = NS_MIN(tileWidth, gAndroidBounds.width - x);
int height = NS_MIN(tileHeight, gAndroidBounds.height - y);
nsRefPtr<gfxImageSurface> targetSurface =
new gfxImageSurface(bits + offset,
gfxIntSize(width, height),
width * 2,
gfxASurface::ImageFormatRGB16_565);
offset += width * height * 2;
if (targetSurface->CairoStatus()) {
ALOG("### Failed to create a valid surface from the bitmap");
break;
} else {
targetSurface->SetDeviceOffset(gfxPoint(-x, -y));
DrawTo(targetSurface, dirtyRect);
}
}
}
}
if (HasDirectTexture()) {
sDirectTexture->Unlock();
} else {
client.UnlockBuffer();
}
client.EndDrawing(dirtyRect);
layers::renderTraceEventStart("Widget end draw", "434747");
client.EndDrawing();
layers::renderTraceEventEnd("Widget end draw", "434747");
return;
#endif
@ -2342,3 +2259,72 @@ nsWindow::GetIMEUpdatePreference()
return nsIMEUpdatePreference(true, true);
}
#ifdef MOZ_JAVA_COMPOSITOR
void
nsWindow::DrawWindowUnderlay(LayerManager* aManager, nsIntRect aRect) {
AndroidBridge::AutoLocalJNIFrame jniFrame(GetJNIForThread());
AndroidGeckoLayerClient& client = AndroidBridge::Bridge()->GetLayerClient();
client.CreateFrame(mLayerRendererFrame);
client.ActivateProgram();
mLayerRendererFrame.BeginDrawing();
mLayerRendererFrame.DrawBackground();
client.DeactivateProgram();
}
void
nsWindow::DrawWindowOverlay(LayerManager* aManager, nsIntRect aRect) {
AndroidBridge::AutoLocalJNIFrame jniFrame(GetJNIForThread());
NS_ABORT_IF_FALSE(!mLayerRendererFrame.isNull(),
"Frame should have been created in DrawWindowUnderlay()!");
AndroidGeckoLayerClient& client = AndroidBridge::Bridge()->GetLayerClient();
client.ActivateProgram();
mLayerRendererFrame.DrawForeground();
mLayerRendererFrame.EndDrawing();
client.DeactivateProgram();
mLayerRendererFrame.Dispose();
}
// off-main-thread compositor fields and functions
nsRefPtr<mozilla::layers::CompositorParent> nsWindow::sCompositorParent = 0;
base::Thread * nsWindow::sCompositorThread = 0;
void
nsWindow::SetCompositorParent(mozilla::layers::CompositorParent* aCompositorParent,
::base::Thread* aCompositorThread)
{
sCompositorParent = aCompositorParent;
sCompositorThread = aCompositorThread;
}
void
nsWindow::ScheduleComposite()
{
if (sCompositorParent) {
sCompositorParent->ScheduleRenderOnCompositorThread();
}
}
void
nsWindow::SchedulePauseComposition()
{
if (sCompositorParent) {
sCompositorParent->SchedulePauseOnCompositorThread();
}
}
void
nsWindow::ScheduleResumeComposition()
{
if (sCompositorParent) {
sCompositorParent->ScheduleResumeOnCompositorThread();
}
}
#endif

View File

@ -47,12 +47,21 @@
#include "nsAccessible.h"
#endif
#ifdef MOZ_JAVA_COMPOSITOR
#include "AndroidJavaWrappers.h"
#include "Layers.h"
#endif
class gfxASurface;
class nsIdleService;
namespace mozilla {
class AndroidGeckoEvent;
class AndroidKeyEvent;
namespace layers {
class CompositorParent;
}
}
class nsWindow :
@ -168,7 +177,6 @@ public:
LayersBackend aBackendHint = LayerManager::LAYERS_NONE,
LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
bool* aAllowRetaining = nsnull);
gfxASurface* GetThebesSurface();
NS_IMETHOD ReparentNativeWidget(nsIWidget* aNewParent);
@ -177,8 +185,14 @@ public:
#endif
#ifdef MOZ_JAVA_COMPOSITOR
static void BindToTexture();
static bool HasDirectTexture();
virtual void DrawWindowUnderlay(LayerManager* aManager, nsIntRect aRect);
virtual void DrawWindowOverlay(LayerManager* aManager, nsIntRect aRect);
static void SetCompositorParent(mozilla::layers::CompositorParent* aCompositorParent,
::base::Thread* aCompositorThread);
static void ScheduleComposite();
static void SchedulePauseComposition();
static void ScheduleResumeComposition();
#endif
protected:
@ -246,6 +260,13 @@ private:
*/
nsAccessible *DispatchAccessibleEvent();
#endif // ACCESSIBILITY
#ifdef MOZ_JAVA_COMPOSITOR
mozilla::AndroidLayerRendererFrame mLayerRendererFrame;
static nsRefPtr<mozilla::layers::CompositorParent> sCompositorParent;
static base::Thread *sCompositorThread;
#endif
};
#endif /* NSWINDOW_H_ */

View File

@ -1085,6 +1085,14 @@ class nsIWidget : public nsISupports {
LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
bool* aAllowRetaining = nsnull) = 0;
/**
* Called before the LayerManager draws the layer tree.
*
* @param aManager The drawing LayerManager.
* @param aWidgetRect The current widget rect that is being drawn.
*/
virtual void DrawWindowUnderlay(LayerManager* aManager, nsIntRect aRect) = 0;
/**
* Called after the LayerManager draws the layer tree
*

View File

@ -846,8 +846,8 @@ nsBaseWidget::GetShouldAccelerate()
void nsBaseWidget::CreateCompositor()
{
mCompositorParent = new CompositorParent(this);
mCompositorThread = new Thread("CompositorThread");
mCompositorParent = new CompositorParent(this, mCompositorThread);
if (mCompositorThread->Start()) {
LayerManager* lm = CreateBasicLayerManager();
MessageLoop *childMessageLoop = mCompositorThread->message_loop();

View File

@ -133,6 +133,7 @@ public:
bool* aAllowRetaining = nsnull);
virtual void CreateCompositor();
virtual void DrawWindowUnderlay(LayerManager* aManager, nsIntRect aRect) {}
virtual void DrawWindowOverlay(LayerManager* aManager, nsIntRect aRect) {}
virtual void UpdateThemeGeometries(const nsTArray<ThemeGeometry>& aThemeGeometries) {}
virtual gfxASurface* GetThebesSurface();