blob: 1a54cb78e0ca40ca4fd205b3ad58a9df91664e52 [file] [log] [blame]
/*
* Copyright 2010, The Android Open Source Project
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#define LOG_TAG "GLUtils"
#define LOG_NDEBUG 1
#include "config.h"
#include "GLUtils.h"
#if USE(ACCELERATED_COMPOSITING)
#include "AndroidLog.h"
#include "BaseRenderer.h"
#include "TextureInfo.h"
#include "Tile.h"
#include "TilesManager.h"
#include "TransferQueue.h"
#include <android/native_window.h>
#include <gui/SurfaceTexture.h>
#include <wtf/CurrentTime.h>
// We will limit GL error logging for LOG_VOLUME_PER_CYCLE times every
// LOG_VOLUME_PER_CYCLE seconds.
#define LOG_CYCLE 30.0
#define LOG_VOLUME_PER_CYCLE 20
struct ANativeWindowBuffer;
namespace WebCore {
using namespace android;
/////////////////////////////////////////////////////////////////////////////////////////
// Matrix utilities
/////////////////////////////////////////////////////////////////////////////////////////
void GLUtils::toGLMatrix(GLfloat* flattened, const TransformationMatrix& m)
{
flattened[0] = m.m11(); // scaleX
flattened[1] = m.m12(); // skewY
flattened[2] = m.m13();
flattened[3] = m.m14(); // persp0
flattened[4] = m.m21(); // skewX
flattened[5] = m.m22(); // scaleY
flattened[6] = m.m23();
flattened[7] = m.m24(); // persp1
flattened[8] = m.m31();
flattened[9] = m.m32();
flattened[10] = m.m33();
flattened[11] = m.m34();
flattened[12] = m.m41(); // transX
flattened[13] = m.m42(); // transY
flattened[14] = m.m43();
flattened[15] = m.m44(); // persp2
}
void GLUtils::toSkMatrix(SkMatrix& matrix, const TransformationMatrix& m)
{
matrix[0] = m.m11(); // scaleX
matrix[1] = m.m21(); // skewX
matrix[2] = m.m41(); // transX
matrix[3] = m.m12(); // skewY
matrix[4] = m.m22(); // scaleY
matrix[5] = m.m42(); // transY
matrix[6] = m.m14(); // persp0
matrix[7] = m.m24(); // persp1
matrix[8] = m.m44(); // persp2
}
void GLUtils::setOrthographicMatrix(TransformationMatrix& ortho, float left, float top,
float right, float bottom, float nearZ, float farZ)
{
float deltaX = right - left;
float deltaY = top - bottom;
float deltaZ = farZ - nearZ;
if (!deltaX || !deltaY || !deltaZ)
return;
ortho.setM11(2.0f / deltaX);
ortho.setM41(-(right + left) / deltaX);
ortho.setM22(2.0f / deltaY);
ortho.setM42(-(top + bottom) / deltaY);
ortho.setM33(-2.0f / deltaZ);
ortho.setM43(-(nearZ + farZ) / deltaZ);
}
bool GLUtils::has3dTransform(const TransformationMatrix& matrix)
{
return matrix.m13() != 0 || matrix.m23() != 0
|| matrix.m31() != 0 || matrix.m32() != 0
|| matrix.m33() != 1 || matrix.m34() != 0
|| matrix.m43() != 0;
}
/////////////////////////////////////////////////////////////////////////////////////////
// GL & EGL error checks
/////////////////////////////////////////////////////////////////////////////////////////
double GLUtils::m_previousLogTime = 0;
int GLUtils::m_currentLogCounter = 0;
bool GLUtils::allowGLLog()
{
if (m_currentLogCounter < LOG_VOLUME_PER_CYCLE) {
m_currentLogCounter++;
return true;
}
// when we are in Log cycle and over the log limit, just return false
double currentTime = WTF::currentTime();
double delta = currentTime - m_previousLogTime;
bool inLogCycle = (delta <= LOG_CYCLE) && (delta > 0);
if (inLogCycle)
return false;
// When we are out of Log Cycle and over the log limit, we need to reset
// the counter and timer.
m_previousLogTime = currentTime;
m_currentLogCounter = 0;
return false;
}
static void crashIfOOM(GLint errorCode)
{
const GLint OOM_ERROR_CODE = 0x505;
if (errorCode == OOM_ERROR_CODE) {
ALOGE("ERROR: Fatal OOM detected.");
CRASH();
}
}
void GLUtils::checkEglError(const char* op, EGLBoolean returnVal)
{
if (returnVal != EGL_TRUE) {
#ifndef DEBUG
if (allowGLLog())
#endif
ALOGE("EGL ERROR - %s() returned %d\n", op, returnVal);
}
for (EGLint error = eglGetError(); error != EGL_SUCCESS; error = eglGetError()) {
#ifndef DEBUG
if (allowGLLog())
#endif
ALOGE("after %s() eglError (0x%x)\n", op, error);
crashIfOOM(error);
}
}
bool GLUtils::checkGlError(const char* op)
{
bool ret = false;
for (GLint error = glGetError(); error; error = glGetError()) {
#ifndef DEBUG
if (allowGLLog())
#endif
ALOGE("GL ERROR - after %s() glError (0x%x)\n", op, error);
crashIfOOM(error);
ret = true;
}
return ret;
}
bool GLUtils::checkGlErrorOn(void* p, const char* op)
{
bool ret = false;
for (GLint error = glGetError(); error; error = glGetError()) {
#ifndef DEBUG
if (allowGLLog())
#endif
ALOGE("GL ERROR on %x - after %s() glError (0x%x)\n", p, op, error);
crashIfOOM(error);
ret = true;
}
return ret;
}
void GLUtils::checkSurfaceTextureError(const char* functionName, int status)
{
if (status != NO_ERROR) {
#ifndef DEBUG
if (allowGLLog())
#endif
ALOGE("ERROR at calling %s status is (%d)", functionName, status);
}
}
/////////////////////////////////////////////////////////////////////////////////////////
// GL & EGL extension checks
/////////////////////////////////////////////////////////////////////////////////////////
bool GLUtils::isEGLImageSupported()
{
const char* eglExtensions = eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS);
const char* glExtensions = reinterpret_cast<const char*>(glGetString(GL_EXTENSIONS));
return eglExtensions && glExtensions
&& strstr(eglExtensions, "EGL_KHR_image_base")
&& strstr(eglExtensions, "EGL_KHR_gl_texture_2D_image")
&& strstr(glExtensions, "GL_OES_EGL_image");
}
bool GLUtils::isEGLFenceSyncSupported()
{
const char* eglExtensions = eglQueryString(eglGetCurrentDisplay(), EGL_EXTENSIONS);
return eglExtensions && strstr(eglExtensions, "EGL_KHR_fence_sync");
}
/////////////////////////////////////////////////////////////////////////////////////////
// Textures utilities
/////////////////////////////////////////////////////////////////////////////////////////
static GLenum getInternalFormat(SkBitmap::Config config)
{
switch (config) {
case SkBitmap::kA8_Config:
return GL_ALPHA;
case SkBitmap::kARGB_4444_Config:
return GL_RGBA;
case SkBitmap::kARGB_8888_Config:
return GL_RGBA;
case SkBitmap::kRGB_565_Config:
return GL_RGB;
default:
return -1;
}
}
static GLenum getType(SkBitmap::Config config)
{
switch (config) {
case SkBitmap::kA8_Config:
return GL_UNSIGNED_BYTE;
case SkBitmap::kARGB_4444_Config:
return GL_UNSIGNED_SHORT_4_4_4_4;
case SkBitmap::kARGB_8888_Config:
return GL_UNSIGNED_BYTE;
case SkBitmap::kIndex8_Config:
return -1; // No type for compressed data.
case SkBitmap::kRGB_565_Config:
return GL_UNSIGNED_SHORT_5_6_5;
default:
return -1;
}
}
static EGLConfig defaultPbufferConfig(EGLDisplay display)
{
EGLConfig config;
EGLint numConfigs;
static const EGLint configAttribs[] = {
EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL_NONE
};
eglChooseConfig(display, configAttribs, &config, 1, &numConfigs);
GLUtils::checkEglError("eglPbufferConfig");
if (numConfigs != 1)
ALOGI("eglPbufferConfig failed (%d)\n", numConfigs);
return config;
}
static EGLSurface createPbufferSurface(EGLDisplay display, const EGLConfig& config,
EGLint* errorCode)
{
const EGLint attribList[] = {
EGL_WIDTH, 1,
EGL_HEIGHT, 1,
EGL_NONE
};
EGLSurface surface = eglCreatePbufferSurface(display, config, attribList);
if (errorCode)
*errorCode = eglGetError();
else
GLUtils::checkEglError("eglCreatePbufferSurface");
if (surface == EGL_NO_SURFACE)
return EGL_NO_SURFACE;
return surface;
}
void GLUtils::deleteTexture(GLuint* texture)
{
glDeleteTextures(1, texture);
GLUtils::checkGlError("glDeleteTexture");
*texture = 0;
}
GLuint GLUtils::createSampleColorTexture(int r, int g, int b)
{
GLuint texture;
glGenTextures(1, &texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLubyte pixels[4 *3] = {
r, g, b,
r, g, b,
r, g, b,
r, g, b
};
glBindTexture(GL_TEXTURE_2D, texture);
GLUtils::checkGlError("glBindTexture");
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
GLUtils::checkGlError("glTexImage2D");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
return texture;
}
GLuint GLUtils::createSampleTexture()
{
GLuint texture;
glGenTextures(1, &texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLubyte pixels[4 *3] = {
255, 0, 0,
0, 255, 0,
0, 0, 255,
255, 255, 0
};
glBindTexture(GL_TEXTURE_2D, texture);
GLUtils::checkGlError("glBindTexture");
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, 2, 2, 0, GL_RGB, GL_UNSIGNED_BYTE, pixels);
GLUtils::checkGlError("glTexImage2D");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
return texture;
}
GLuint GLUtils::createTileGLTexture(int width, int height)
{
GLuint texture;
glGenTextures(1, &texture);
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
GLubyte* pixels = 0;
#ifdef DEBUG
int length = width * height * 4;
pixels = new GLubyte[length];
for (int i = 0; i < length; i++)
pixels[i] = i % 256;
#endif
glBindTexture(GL_TEXTURE_2D, texture);
GLUtils::checkGlError("glBindTexture");
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels);
GLUtils::checkGlError("glTexImage2D");
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
#ifdef DEBUG
delete pixels;
#endif
return texture;
}
bool GLUtils::isPureColorBitmap(const SkBitmap& bitmap, Color& pureColor)
{
// If the bitmap is the pure color, skip the transfer step, and update the Tile Info.
// This check is taking < 1ms if we do full bitmap check per tile.
// TODO: use the SkPicture to determine whether or not a tile is single color.
TRACE_METHOD();
pureColor = Color(Color::transparent);
bitmap.lockPixels();
bool sameColor = true;
int bitmapWidth = bitmap.width();
// Create a row of pure color using the first pixel.
// TODO: improve the perf here, by either picking a random pixel, or
// creating an array of rows with pre-defined commonly used color, add
// smart LUT to speed things up if possible.
int* firstPixelPtr = static_cast<int*> (bitmap.getPixels());
int* pixelsRow = new int[bitmapWidth];
for (int i = 0; i < bitmapWidth; i++)
pixelsRow[i] = (*firstPixelPtr);
// Then compare the pure color row with each row of the bitmap.
for (int j = 0; j < bitmap.height(); j++) {
if (memcmp(pixelsRow, &firstPixelPtr[bitmapWidth * j], 4 * bitmapWidth)) {
sameColor = false;
break;
}
}
delete pixelsRow;
pixelsRow = 0;
if (sameColor) {
unsigned char* rgbaPtr = static_cast<unsigned char*>(bitmap.getPixels());
pureColor = Color(rgbaPtr[0], rgbaPtr[1], rgbaPtr[2], rgbaPtr[3]);
ALOGV("sameColor tile found , %x at (%d, %d, %d, %d)",
*firstPixelPtr, rgbaPtr[0], rgbaPtr[1], rgbaPtr[2], rgbaPtr[3]);
}
bitmap.unlockPixels();
return sameColor;
}
// Return true when the tile is pure color.
bool GLUtils::skipTransferForPureColor(const TileRenderInfo* renderInfo,
const SkBitmap& bitmap)
{
bool skipTransfer = false;
Tile* tilePtr = renderInfo->baseTile;
if (tilePtr) {
TileTexture* tileTexture = tilePtr->backTexture();
// Check the bitmap, and make everything ready here.
if (tileTexture && renderInfo->isPureColor) {
// update basetile's info
// Note that we are skipping the whole TransferQueue.
renderInfo->textureInfo->m_width = bitmap.width();
renderInfo->textureInfo->m_height = bitmap.height();
renderInfo->textureInfo->m_internalFormat = GL_RGBA;
TilesManager::instance()->transferQueue()->addItemInPureColorQueue(renderInfo);
skipTransfer = true;
}
}
return skipTransfer;
}
void GLUtils::paintTextureWithBitmap(const TileRenderInfo* renderInfo,
SkBitmap& bitmap)
{
if (!renderInfo)
return;
const SkSize& requiredSize = renderInfo->tileSize;
TextureInfo* textureInfo = renderInfo->textureInfo;
if (skipTransferForPureColor(renderInfo, bitmap))
return;
if (requiredSize.equals(textureInfo->m_width, textureInfo->m_height))
GLUtils::updateQueueWithBitmap(renderInfo, bitmap);
else {
if (!requiredSize.equals(bitmap.width(), bitmap.height())) {
ALOGV("The bitmap size (%d,%d) does not equal the texture size (%d,%d)",
bitmap.width(), bitmap.height(),
requiredSize.width(), requiredSize.height());
}
GLUtils::updateQueueWithBitmap(renderInfo, bitmap);
textureInfo->m_width = bitmap.width();
textureInfo->m_height = bitmap.height();
textureInfo->m_internalFormat = GL_RGBA;
}
}
void GLUtils::updateQueueWithBitmap(const TileRenderInfo* renderInfo, SkBitmap& bitmap)
{
if (!renderInfo
|| !renderInfo->textureInfo
|| !renderInfo->baseTile)
return;
TilesManager::instance()->transferQueue()->updateQueueWithBitmap(renderInfo, bitmap);
}
bool GLUtils::updateSharedSurfaceTextureWithBitmap(ANativeWindow* anw, const SkBitmap& bitmap)
{
TRACE_METHOD();
SkAutoLockPixels alp(bitmap);
if (!bitmap.getPixels())
return false;
ANativeWindow_Buffer buffer;
if (ANativeWindow_lock(anw, &buffer, 0))
return false;
if (buffer.width < bitmap.width() || buffer.height < bitmap.height()) {
ALOGW("bitmap (%dx%d) too large for buffer (%dx%d)!",
bitmap.width(), bitmap.height(),
buffer.width, buffer.height);
ANativeWindow_unlockAndPost(anw);
return false;
}
uint8_t* img = (uint8_t*)buffer.bits;
int row;
int bpp = 4; // Now we only deal with RGBA8888 format.
bitmap.lockPixels();
uint8_t* bitmapOrigin = static_cast<uint8_t*>(bitmap.getPixels());
if (buffer.stride != bitmap.width())
// Copied line by line since we need to handle the offsets and stride.
for (row = 0 ; row < bitmap.height(); row ++) {
uint8_t* dst = &(img[buffer.stride * row * bpp]);
uint8_t* src = &(bitmapOrigin[bitmap.width() * row * bpp]);
memcpy(dst, src, bpp * bitmap.width());
}
else
memcpy(img, bitmapOrigin, bpp * bitmap.width() * bitmap.height());
bitmap.unlockPixels();
ANativeWindow_unlockAndPost(anw);
return true;
}
void GLUtils::createTextureWithBitmap(GLuint texture, const SkBitmap& bitmap, GLint filter)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glBindTexture(GL_TEXTURE_2D, texture);
GLUtils::checkGlError("glBindTexture");
SkBitmap::Config config = bitmap.getConfig();
int internalformat = getInternalFormat(config);
int type = getType(config);
bitmap.lockPixels();
glTexImage2D(GL_TEXTURE_2D, 0, internalformat, bitmap.width(), bitmap.height(),
0, internalformat, type, bitmap.getPixels());
bitmap.unlockPixels();
if (GLUtils::checkGlError("glTexImage2D")) {
#ifndef DEBUG
if (allowGLLog())
#endif
ALOGE("GL ERROR: glTexImage2D parameters are : textureId %d,"
" bitmap.width() %d, bitmap.height() %d,"
" internalformat 0x%x, type 0x%x, bitmap.getPixels() %p",
texture, bitmap.width(), bitmap.height(), internalformat, type,
bitmap.getPixels());
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
}
void GLUtils::updateTextureWithBitmap(GLuint texture, const SkBitmap& bitmap,
const IntRect& inval, GLint filter)
{
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
glBindTexture(GL_TEXTURE_2D, texture);
GLUtils::checkGlError("glBindTexture");
SkBitmap::Config config = bitmap.getConfig();
int internalformat = getInternalFormat(config);
int type = getType(config);
bitmap.lockPixels();
if (inval.isEmpty()) {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, bitmap.width(), bitmap.height(),
internalformat, type, bitmap.getPixels());
} else {
glTexSubImage2D(GL_TEXTURE_2D, 0, inval.x(), inval.y(), inval.width(), inval.height(),
internalformat, type, bitmap.getPixels());
}
bitmap.unlockPixels();
if (GLUtils::checkGlError("glTexSubImage2D")) {
#ifndef DEBUG
if (allowGLLog())
#endif
ALOGE("GL ERROR: glTexSubImage2D parameters are : textureId %d,"
" bitmap.width() %d, bitmap.height() %d,"
" internalformat 0x%x, type 0x%x, bitmap.getPixels() %p",
texture, bitmap.width(), bitmap.height(), internalformat, type,
bitmap.getPixels());
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
}
void GLUtils::createEGLImageFromTexture(GLuint texture, EGLImageKHR* image)
{
EGLClientBuffer buffer = reinterpret_cast<EGLClientBuffer>(texture);
static const EGLint attr[] = { EGL_IMAGE_PRESERVED_KHR, EGL_TRUE, EGL_NONE };
*image = eglCreateImageKHR(eglGetCurrentDisplay(), eglGetCurrentContext(),
EGL_GL_TEXTURE_2D_KHR, buffer, attr);
GLUtils::checkEglError("eglCreateImage", (*image != EGL_NO_IMAGE_KHR));
}
void GLUtils::createTextureFromEGLImage(GLuint texture, EGLImageKHR image, GLint filter)
{
glBindTexture(GL_TEXTURE_2D, texture);
GLUtils::checkGlError("glBindTexture");
glEGLImageTargetTexture2DOES(GL_TEXTURE_2D, (GLeglImageOES)image);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, filter);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, filter);
}
void GLUtils::convertToTransformationMatrix(const float* matrix, TransformationMatrix& transformMatrix)
{
transformMatrix.setMatrix(
matrix[0], matrix[1], matrix[2], matrix[3],
matrix[4], matrix[5], matrix[6], matrix[7],
matrix[8], matrix[9], matrix[10], matrix[11],
matrix[12], matrix[13], matrix[14], matrix[15]);
}
void GLUtils::clearBackgroundIfOpaque(const Color* backgroundColor)
{
if (!backgroundColor->hasAlpha()) {
if (TilesManager::instance()->invertedScreen()) {
float color = 1.0 - ((((float) backgroundColor->red() / 255.0) +
((float) backgroundColor->green() / 255.0) +
((float) backgroundColor->blue() / 255.0)) / 3.0);
glClearColor(color, color, color, 1);
} else {
glClearColor((float)backgroundColor->red() / 255.0,
(float)backgroundColor->green() / 255.0,
(float)backgroundColor->blue() / 255.0, 1);
}
glClear(GL_COLOR_BUFFER_BIT);
}
}
bool GLUtils::deepCopyBitmapSubset(const SkBitmap& sourceBitmap,
SkBitmap& subset, int leftOffset, int topOffset)
{
sourceBitmap.lockPixels();
subset.lockPixels();
char* srcPixels = (char*) sourceBitmap.getPixels();
char* dstPixels = (char*) subset.getPixels();
if (!dstPixels || !srcPixels || !subset.lockPixelsAreWritable()) {
ALOGD("no pixels :( %p, %p (writable=%d)", srcPixels, dstPixels,
subset.lockPixelsAreWritable());
subset.unlockPixels();
sourceBitmap.unlockPixels();
return false;
}
int srcRowSize = sourceBitmap.rowBytes();
int destRowSize = subset.rowBytes();
for (int i = 0; i < subset.height(); i++) {
int srcOffset = (i + topOffset) * srcRowSize;
srcOffset += (leftOffset * sourceBitmap.bytesPerPixel());
int dstOffset = i * destRowSize;
memcpy(dstPixels + dstOffset, srcPixels + srcOffset, destRowSize);
}
subset.unlockPixels();
sourceBitmap.unlockPixels();
return true;
}
} // namespace WebCore
#endif // USE(ACCELERATED_COMPOSITING)