Bug 921212 - Rotate buffer in place to avoid gralloc surface allocation. r=Bas

This commit is contained in:
Benoit Girard 2013-10-11 16:47:47 -04:00
parent b1dcc268e4
commit 1aa99b3bef
11 changed files with 323 additions and 18 deletions

View File

@ -578,6 +578,15 @@ public:
virtual TemporaryRef<SourceSurface> Snapshot() = 0;
virtual IntSize GetSize() = 0;
/**
* If possible returns the bits to this DrawTarget for direct manipulation. While
* the bits is locked any modifications to this DrawTarget is forbidden.
* Release takes the original data pointer for safety.
*/
virtual bool LockBits(uint8_t** aData, IntSize* aSize,
int32_t* aStride, SurfaceFormat* aFormat) { return false; }
virtual void ReleaseBits(uint8_t* aData) {}
/* Ensure that the DrawTarget backend has flushed all drawing operations to
* this draw target. This must be called before using the backing surface of
* this draw target outside of GFX 2D code.

View File

@ -399,6 +399,7 @@ NeedIntermediateSurface(const Pattern& aPattern, const DrawOptions& aOptions)
DrawTargetCairo::DrawTargetCairo()
: mContext(nullptr)
, mLockedBits(nullptr)
{
}
@ -408,6 +409,7 @@ DrawTargetCairo::~DrawTargetCairo()
if (mSurface) {
cairo_surface_destroy(mSurface);
}
MOZ_ASSERT(!mLockedBits);
}
IntSize
@ -433,6 +435,31 @@ DrawTargetCairo::Snapshot()
return mSnapshot;
}
bool
DrawTargetCairo::LockBits(uint8_t** aData, IntSize* aSize,
int32_t* aStride, SurfaceFormat* aFormat)
{
if (cairo_surface_get_type(mSurface) == CAIRO_SURFACE_TYPE_IMAGE) {
WillChange();
mLockedBits = cairo_image_surface_get_data(mSurface);
*aData = mLockedBits;
*aSize = GetSize();
*aStride = cairo_image_surface_get_stride(mSurface);
*aFormat = GetFormat();
return true;
}
return false;
}
void
DrawTargetCairo::ReleaseBits(uint8_t* aData)
{
MOZ_ASSERT(mLockedBits = aData);
mLockedBits = nullptr;
}
void
DrawTargetCairo::Flush()
{
@ -1139,6 +1166,7 @@ void
DrawTargetCairo::WillChange(const Path* aPath /* = nullptr */)
{
MarkSnapshotIndependent();
MOZ_ASSERT(!mLiveSurface);
}
void

View File

@ -60,6 +60,10 @@ public:
virtual TemporaryRef<SourceSurface> Snapshot();
virtual IntSize GetSize();
virtual bool LockBits(uint8_t** aData, IntSize* aSize,
int32_t* aStride, SurfaceFormat* aFormat);
virtual void ReleaseBits(uint8_t* aData);
virtual void Flush();
virtual void DrawSurface(SourceSurface *aSurface,
const Rect &aDest,
@ -184,6 +188,8 @@ private: // data
cairo_surface_t* mSurface;
IntSize mSize;
uint8_t* mLockedBits;
// The latest snapshot of this surface. This needs to be told when this
// target is modified. We keep it alive as a cache.
RefPtr<SourceSurfaceCairo> mSnapshot;

View File

@ -0,0 +1,63 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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/. */
#include <algorithm> // min & max
#include <cstdlib>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
void BufferUnrotate(uint8_t* aBuffer, int aByteWidth, int aHeight,
int aByteStride, int aXBoundary, int aYBoundary)
{
if (aXBoundary != 0) {
uint8_t* line = new uint8_t[aByteWidth];
uint32_t smallStart = 0;
uint32_t smallLen = aXBoundary;
uint32_t smallDest = aByteWidth - aXBoundary;
uint32_t largeStart = aXBoundary;
uint32_t largeLen = aByteWidth - aXBoundary;
uint32_t largeDest = 0;
if (aXBoundary > aByteWidth / 2) {
smallStart = aXBoundary;
smallLen = aByteWidth - aXBoundary;
smallDest = 0;
largeStart = 0;
largeLen = aXBoundary;
largeDest = smallLen;
}
for (int y = 0; y < aHeight; y++) {
int yOffset = y * aByteStride;
memcpy(line, &aBuffer[yOffset + smallStart], smallLen);
memmove(&aBuffer[yOffset + largeDest], &aBuffer[yOffset + largeStart], largeLen);
memcpy(&aBuffer[yOffset + smallDest], line, smallLen);
}
delete[] line;
}
if (aYBoundary != 0) {
uint32_t smallestHeight = std::min(aHeight - aYBoundary, aYBoundary);
uint32_t largestHeight = std::max(aHeight - aYBoundary, aYBoundary);
uint32_t smallOffset = 0;
uint32_t largeOffset = aYBoundary * aByteStride;
uint32_t largeDestOffset = 0;
uint32_t smallDestOffset = largestHeight * aByteStride;
if (aYBoundary > aHeight / 2) {
smallOffset = aYBoundary * aByteStride;
largeOffset = 0;
largeDestOffset = smallestHeight * aByteStride;
smallDestOffset = 0;
}
uint8_t* smallestSide = new uint8_t[aByteStride * smallestHeight];
memcpy(smallestSide, &aBuffer[smallOffset], aByteStride * smallestHeight);
memmove(&aBuffer[largeDestOffset], &aBuffer[largeOffset], aByteStride * largestHeight);
memcpy(&aBuffer[smallDestOffset], smallestSide, aByteStride * smallestHeight);
delete[] smallestSide;
}
}

View File

@ -0,0 +1,14 @@
/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* 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/. */
#ifndef GFX_BUFFERUNROTATE_H
#define GFX_BUFFERUNROTATE_H
#include "mozilla/Types.h"
void BufferUnrotate(uint8_t* aBuffer, int aByteWidth, int aHeight,
int aByteStride, int aXByteBoundary, int aYBoundary);
#endif // GFX_BUFFERUNROTATE_H

View File

@ -8,6 +8,7 @@
#include <algorithm> // for max
#include "BasicImplData.h" // for BasicImplData
#include "BasicLayersImpl.h" // for ToData
#include "BufferUnrotate.h" // for BufferUnrotate
#include "GeckoProfiler.h" // for PROFILER_LABEL
#include "Layers.h" // for ThebesLayer, Layer, etc
#include "gfxColor.h" // for gfxRGBA
@ -675,18 +676,54 @@ ThebesLayerBuffer::BeginPaint(ThebesLayer* aLayer, ContentType aContentType,
}
}
result.mDidSelfCopy = true;
mDidSelfCopy = true;
// Don't set destBuffer; we special-case self-copies, and
// just did the necessary work above.
mBufferRect = destBufferRect;
} else {
// We can't do a real self-copy because the buffer is rotated.
// So allocate a new buffer for the destination.
destBufferRect = ComputeBufferRect(neededRegion.GetBounds());
CreateBuffer(contentType, destBufferRect, bufferFlags,
getter_AddRefs(destBuffer), getter_AddRefs(destBufferOnWhite),
&destDTBuffer, &destDTBufferOnWhite);
if (!destBuffer && !destDTBuffer)
return result;
// With azure and a data surface perform an buffer unrotate
// (SelfCopy).
if (IsAzureBuffer()) {
unsigned char* data;
IntSize size;
int32_t stride;
SurfaceFormat format;
if (mDTBuffer->LockBits(&data, &size, &stride, &format)) {
uint8_t bytesPerPixel = BytesPerPixel(format);
BufferUnrotate(data,
size.width * bytesPerPixel,
size.height, stride,
newRotation.x * bytesPerPixel, newRotation.y);
mDTBuffer->ReleaseBits(data);
if (mode == Layer::SURFACE_COMPONENT_ALPHA) {
mDTBufferOnWhite->LockBits(&data, &size, &stride, &format);
uint8_t bytesPerPixel = BytesPerPixel(format);
BufferUnrotate(data,
size.width * bytesPerPixel,
size.height, stride,
newRotation.x * bytesPerPixel, newRotation.y);
mDTBufferOnWhite->ReleaseBits(data);
}
// Buffer unrotate moves all the pixels, note that
// we self copied for SyncBackToFrontBuffer
result.mDidSelfCopy = true;
mDidSelfCopy = true;
mBufferRect = destBufferRect;
mBufferRotation = nsIntPoint(0, 0);
}
}
if (!result.mDidSelfCopy) {
destBufferRect = ComputeBufferRect(neededRegion.GetBounds());
CreateBuffer(contentType, destBufferRect, bufferFlags,
getter_AddRefs(destBuffer), getter_AddRefs(destBufferOnWhite),
&destDTBuffer, &destDTBufferOnWhite);
if (!destBuffer && !destDTBuffer)
return result;
}
}
} else {
mBufferRect = destBufferRect;

View File

@ -146,6 +146,9 @@ protected:
* buffer at the other end, not 2D rotation!
*/
nsIntPoint mBufferRotation;
// When this is true it means that all pixels have moved inside the buffer.
// It's not possible to sync with another buffer without a full copy.
bool mDidSelfCopy;
};
/**

View File

@ -479,21 +479,13 @@ ContentClientDoubleBuffered::SyncFrontBufferToBackBuffer()
nsIntRegion updateRegion = mFrontUpdatedRegion;
int32_t xBoundary = mBufferRect.XMost() - mBufferRotation.x;
int32_t yBoundary = mBufferRect.YMost() - mBufferRotation.y;
// Figure out whether the area we want to copy wraps the edges of our buffer.
bool needFullCopy = (xBoundary < updateRegion.GetBounds().XMost() &&
xBoundary > updateRegion.GetBounds().x) ||
(yBoundary < updateRegion.GetBounds().YMost() &&
yBoundary > updateRegion.GetBounds().y);
// This is a tricky trade off, we're going to get stuff out of our
// frontbuffer now, but the next PaintThebes might throw it all (or mostly)
// away if the visible region has changed. This is why in reality we want
// this code integrated with PaintThebes to always do the optimal thing.
if (needFullCopy) {
if (mDidSelfCopy) {
mDidSelfCopy = false;
// We can't easily draw our front buffer into us, since we're going to be
// copying stuff around anyway it's easiest if we just move our situation
// to non-rotated while we're at it. If this situation occurs we'll have

View File

@ -194,6 +194,7 @@ CPP_SOURCES += [
'basic/BasicLayerManager.cpp',
'basic/BasicLayersImpl.cpp',
'basic/BasicThebesLayer.cpp',
'BufferUnrotate.cpp',
'client/CanvasClient.cpp',
'composite/CanvasLayerComposite.cpp',
'opengl/CanvasLayerOGL.cpp',

View File

@ -0,0 +1,151 @@
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* 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/. */
#include "gtest/gtest.h"
#include "BufferUnrotate.h"
static unsigned char* GenerateBuffer(int bytesPerPixel,
int width, int height,
int stride, int xBoundary, int yBoundary)
{
unsigned char* buffer = new unsigned char[stride*height];
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pos = ((yBoundary + y) % height) * stride +
((xBoundary + x) % width) * bytesPerPixel;
for (int i = 0; i < bytesPerPixel; i++) {
buffer[pos+i] = (x+y+i*2)%256;
}
}
}
return buffer;
}
static bool CheckBuffer(unsigned char* buffer, int bytesPerPixel,
int width, int height, int stride)
{
int xBoundary = 0;
int yBoundary = 0;
for (int y = 0; y < height; y++) {
for (int x = 0; x < width; x++) {
int pos = ((yBoundary + y) % height) * stride +
((xBoundary + x) % width) * bytesPerPixel;
for (int i = 0; i < bytesPerPixel; i++) {
if (buffer[pos+i] != (x+y+i*2)%256) {
printf("Buffer differs at %i, %i, is %i\n", x, y, (int)buffer[pos+i]);
return false;
}
}
}
}
return true;
}
TEST(Gfx, BufferUnrotateHorizontal) {
const int NUM_OF_TESTS = 8;
int bytesPerPixelList[2] = {2,4};
int width[NUM_OF_TESTS] = {100, 100, 99, 99, 100, 100, 99, 99};
int height[NUM_OF_TESTS] = {100, 99, 100, 99, 100, 99, 100, 99};
int xBoundary[NUM_OF_TESTS] = {30, 30, 30, 30, 31, 31, 31, 31};
int yBoundary[NUM_OF_TESTS] = {0, 0, 0, 0};
for (int bytesPerId = 0; bytesPerId < 2; bytesPerId++) {
int bytesPerPixel = bytesPerPixelList[bytesPerId];
int stride = 256 * bytesPerPixel;
for (int testId = 0; testId < NUM_OF_TESTS; testId++) {
unsigned char* buffer = GenerateBuffer(bytesPerPixel,
width[testId], height[testId], stride,
xBoundary[testId], yBoundary[testId]);
BufferUnrotate(buffer,
width[testId] * bytesPerPixel, height[testId], stride,
xBoundary[testId] * bytesPerPixel, yBoundary[testId]);
EXPECT_TRUE(CheckBuffer(buffer, bytesPerPixel,
width[testId], height[testId], stride));
delete[] buffer;
}
}
}
TEST(Gfx, BufferUnrotateVertical) {
const int NUM_OF_TESTS = 8;
int bytesPerPixelList[2] = {2,4};
int width[NUM_OF_TESTS] = {100, 100, 99, 99, 100, 100, 99, 99};
int height[NUM_OF_TESTS] = {100, 99, 100, 99, 100, 99, 100, 99};
int xBoundary[NUM_OF_TESTS] = {0, 0, 0, 0};
int yBoundary[NUM_OF_TESTS] = {30, 30, 30, 30, 31, 31, 31, 31};
for (int bytesPerId = 0; bytesPerId < 2; bytesPerId++) {
int bytesPerPixel = bytesPerPixelList[bytesPerId];
int stride = 256 * bytesPerPixel;
for (int testId = 0; testId < NUM_OF_TESTS; testId++) {
unsigned char* buffer = GenerateBuffer(bytesPerPixel,
width[testId], height[testId], stride,
xBoundary[testId], yBoundary[testId]);
BufferUnrotate(buffer, width[testId] * bytesPerPixel,
height[testId], stride,
xBoundary[testId] * bytesPerPixel, yBoundary[testId]);
EXPECT_TRUE(CheckBuffer(buffer, bytesPerPixel,
width[testId], height[testId], stride));
delete[] buffer;
}
}
}
TEST(Gfx, BufferUnrotateBoth) {
const int NUM_OF_TESTS = 16;
int bytesPerPixelList[2] = {2,4};
int width[NUM_OF_TESTS] = {100, 100, 99, 99, 100, 100, 99, 99, 100, 100, 99, 99, 100, 100, 99, 99};
int height[NUM_OF_TESTS] = {100, 99, 100, 99, 100, 99, 100, 99, 100, 99, 100, 99, 100, 99, 100, 99};
int xBoundary[NUM_OF_TESTS] = {30, 30, 30, 30, 31, 31, 31, 31, 30, 30, 30, 30, 31, 31, 31, 31};
int yBoundary[NUM_OF_TESTS] = {30, 30, 30, 30, 30, 30, 30, 30, 31, 31, 31, 31, 31, 31, 31, 31};
for (int bytesPerId = 0; bytesPerId < 2; bytesPerId++) {
int bytesPerPixel = bytesPerPixelList[bytesPerId];
int stride = 256 * bytesPerPixel;
for (int testId = 0; testId < NUM_OF_TESTS; testId++) {
unsigned char* buffer = GenerateBuffer(bytesPerPixel,
width[testId], height[testId], stride,
xBoundary[testId], yBoundary[testId]);
BufferUnrotate(buffer,
width[testId] * bytesPerPixel, height[testId], stride,
xBoundary[testId] * bytesPerPixel, yBoundary[testId]);
EXPECT_TRUE(CheckBuffer(buffer, bytesPerPixel,
width[testId], height[testId], stride));
delete[] buffer;
}
}
}
TEST(Gfx, BufferUnrotateUneven) {
const int NUM_OF_TESTS = 16;
int bytesPerPixelList[2] = {2,4};
int width[NUM_OF_TESTS] = {10, 100, 99, 39, 100, 40, 99, 39, 100, 50, 39, 99, 74, 60, 99, 39};
int height[NUM_OF_TESTS] = {100, 39, 10, 99, 10, 99, 40, 99, 73, 39, 100, 39, 67, 99, 84, 99};
int xBoundary[NUM_OF_TESTS] = {0, 0, 30, 30, 99, 31, 0, 31, 30, 30, 30, 30, 31, 31, 31, 38};
int yBoundary[NUM_OF_TESTS] = {30, 30, 0, 30, 0, 30, 0, 30, 31, 31, 31, 31, 31, 31, 31, 98};
for (int bytesPerId = 0; bytesPerId < 2; bytesPerId++) {
int bytesPerPixel = bytesPerPixelList[bytesPerId];
int stride = 256 * bytesPerPixel;
for (int testId = 0; testId < NUM_OF_TESTS; testId++) {
unsigned char* buffer = GenerateBuffer(bytesPerPixel,
width[testId], height[testId], stride,
xBoundary[testId], yBoundary[testId]);
BufferUnrotate(buffer,
width[testId]*bytesPerPixel, height[testId], stride,
xBoundary[testId]*bytesPerPixel, yBoundary[testId]);
EXPECT_TRUE(CheckBuffer(buffer, bytesPerPixel, width[testId], height[testId], stride));
delete[] buffer;
}
}
}

View File

@ -21,6 +21,7 @@ GTEST_CPP_SOURCES += [
'TestRegion.cpp',
'TestColorNames.cpp',
'TestTextures.cpp',
'TestBufferRotation.cpp',
]
# Because of gkmedia on windows we wont find these