void FEMorphology::platformApplySoftware() { FilterEffect* in = inputEffect(0); Uint8ClampedArray* dstPixelArray = createPremultipliedImageResult(); if (!dstPixelArray) return; setIsAlphaImage(in->isAlphaImage()); if (m_radiusX <= 0 || m_radiusY <= 0) { dstPixelArray->zeroFill(); return; } Filter& filter = this->filter(); int radiusX = static_cast<int>(floorf(filter.applyHorizontalScale(m_radiusX))); int radiusY = static_cast<int>(floorf(filter.applyVerticalScale(m_radiusY))); IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArray = in->asPremultipliedImage(effectDrawingRect); PaintingData paintingData; paintingData.srcPixelArray = srcPixelArray.get(); paintingData.dstPixelArray = dstPixelArray; paintingData.width = effectDrawingRect.width(); paintingData.height = effectDrawingRect.height(); paintingData.radiusX = std::min(effectDrawingRect.width() - 1, radiusX); paintingData.radiusY = std::min(effectDrawingRect.height() - 1, radiusY); platformApply(&paintingData); }
void FEBlend::platformApplySoftware() { FilterEffect* in = inputEffect(0); FilterEffect* in2 = inputEffect(1); ASSERT(m_mode > FEBLEND_MODE_UNKNOWN); ASSERT(m_mode <= FEBLEND_MODE_LIGHTEN); Uint8ClampedArray* dstPixelArray = createPremultipliedImageResult(); if (!dstPixelArray) return; IntRect effectADrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArrayA = in->asPremultipliedImage(effectADrawingRect); IntRect effectBDrawingRect = requestedRegionOfInputImageData(in2->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArrayB = in2->asPremultipliedImage(effectBDrawingRect); unsigned pixelArrayLength = srcPixelArrayA->length(); ASSERT(pixelArrayLength == srcPixelArrayB->length()); for (unsigned pixelOffset = 0; pixelOffset < pixelArrayLength; pixelOffset += 4) { unsigned char alphaA = srcPixelArrayA->item(pixelOffset + 3); unsigned char alphaB = srcPixelArrayB->item(pixelOffset + 3); for (unsigned channel = 0; channel < 3; ++channel) { unsigned char colorA = srcPixelArrayA->item(pixelOffset + channel); unsigned char colorB = srcPixelArrayB->item(pixelOffset + channel); unsigned char result; switch (m_mode) { case FEBLEND_MODE_NORMAL: result = normal(colorA, colorB, alphaA, alphaB); break; case FEBLEND_MODE_MULTIPLY: result = multiply(colorA, colorB, alphaA, alphaB); break; case FEBLEND_MODE_SCREEN: result = screen(colorA, colorB, alphaA, alphaB); break; case FEBLEND_MODE_DARKEN: result = darken(colorA, colorB, alphaA, alphaB); break; case FEBLEND_MODE_LIGHTEN: result = lighten(colorA, colorB, alphaA, alphaB); break; case FEBLEND_MODE_UNKNOWN: default: result = 0; break; } dstPixelArray->set(pixelOffset + channel, result); } unsigned char alphaR = 255 - ((255 - alphaA) * (255 - alphaB)) / 255; dstPixelArray->set(pixelOffset + 3, alphaR); } }
void FETurbulence::applySoftware() { Uint8ClampedArray* pixelArray = createUnmultipliedImageResult(); if (!pixelArray) return; if (absolutePaintRect().isEmpty()) { pixelArray->zeroFill(); return; } PaintingData paintingData(m_seed, roundedIntSize(filterPrimitiveSubregion().size())); initPaint(paintingData); int optimalThreadNumber = (absolutePaintRect().width() * absolutePaintRect().height()) / s_minimalRectDimension; if (optimalThreadNumber > 1) { // Initialize parallel jobs ParallelJobs<FillRegionParameters> parallelJobs(&WebCore::FETurbulence::fillRegionWorker, optimalThreadNumber); // Fill the parameter array int i = parallelJobs.numberOfJobs(); if (i > 1) { // Split the job into "stepY"-sized jobs but there a few jobs that need to be slightly larger since // stepY * jobs < total size. These extras are handled by the remainder "jobsWithExtra". const int stepY = absolutePaintRect().height() / i; const int jobsWithExtra = absolutePaintRect().height() % i; int startY = 0; for (; i > 0; --i) { FillRegionParameters& params = parallelJobs.parameter(i-1); params.filter = this; params.pixelArray = pixelArray; params.paintingData = &paintingData; params.startY = startY; startY += i < jobsWithExtra ? stepY + 1 : stepY; params.endY = startY; params.baseFrequencyX = m_baseFrequencyX; params.baseFrequencyY = m_baseFrequencyY; } // Execute parallel jobs parallelJobs.execute(); return; } } // Fallback to single threaded mode if there is no room for a new thread or the paint area is too small. fillRegion(pixelArray, paintingData, 0, absolutePaintRect().height(), m_baseFrequencyX, m_baseFrequencyY); }
void FilterEffect::forceValidPreMultipliedPixels() { // Must operate on pre-multiplied results; other formats cannot have invalid pixels. if (!m_premultipliedImageResult) return; Uint8ClampedArray* imageArray = m_premultipliedImageResult.get(); unsigned char* pixelData = imageArray->data(); int pixelArrayLength = imageArray->length(); // We must have four bytes per pixel, and complete pixels ASSERT(!(pixelArrayLength % 4)); #if HAVE(ARM_NEON_INTRINSICS) if (pixelArrayLength >= 64) { unsigned char* lastPixel = pixelData + (pixelArrayLength & ~0x3f); do { // Increments pixelData by 64. uint8x16x4_t sixteenPixels = vld4q_u8(pixelData); sixteenPixels.val[0] = vminq_u8(sixteenPixels.val[0], sixteenPixels.val[3]); sixteenPixels.val[1] = vminq_u8(sixteenPixels.val[1], sixteenPixels.val[3]); sixteenPixels.val[2] = vminq_u8(sixteenPixels.val[2], sixteenPixels.val[3]); vst4q_u8(pixelData, sixteenPixels); pixelData += 64; } while (pixelData < lastPixel); pixelArrayLength &= 0x3f; if (!pixelArrayLength) return; } #endif int numPixels = pixelArrayLength / 4; // Iterate over each pixel, checking alpha and adjusting color components if necessary while (--numPixels >= 0) { // Alpha is the 4th byte in a pixel unsigned char a = *(pixelData + 3); // Clamp each component to alpha, and increment the pixel location for (int i = 0; i < 3; ++i) { if (*pixelData > a) *pixelData = a; ++pixelData; } // Increment for alpha ++pixelData; } }
bool FECustomFilter::applyShader() { Uint8ClampedArray* dstPixelArray = m_customFilterRenderer->premultipliedAlpha() ? createPremultipliedImageResult() : createUnmultipliedImageResult(); if (!dstPixelArray) return false; if (!prepareForDrawing()) return false; FilterEffect* in = inputEffect(0); IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); IntSize newContextSize(effectDrawingRect.size()); if (!resizeContextIfNeeded(newContextSize)) return false; bool needsInputTexture = m_customFilterRenderer->programNeedsInputTexture(); if (needsInputTexture) { RefPtr<Uint8ClampedArray> srcPixelArray = in->asUnmultipliedImage(effectDrawingRect); uploadInputTexture(srcPixelArray.get()); } drawFilterMesh(needsInputTexture ? m_inputTexture : 0); ASSERT(static_cast<size_t>(newContextSize.width() * newContextSize.height() * 4) == dstPixelArray->length()); m_context->readPixels(0, 0, newContextSize.width(), newContextSize.height(), GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, dstPixelArray->data()); return true; }
void FEDisplacementMap::applySoftware() { FilterEffect* in = inputEffect(0); FilterEffect* in2 = inputEffect(1); ASSERT(m_xChannelSelector != CHANNEL_UNKNOWN); ASSERT(m_yChannelSelector != CHANNEL_UNKNOWN); Uint8ClampedArray* dstPixelArray = createPremultipliedImageResult(); if (!dstPixelArray) return; IntRect effectADrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArrayA = in->asPremultipliedImage(effectADrawingRect); IntRect effectBDrawingRect = requestedRegionOfInputImageData(in2->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArrayB = in2->asUnmultipliedImage(effectBDrawingRect); ASSERT(srcPixelArrayA->length() == srcPixelArrayB->length()); Filter* filter = this->filter(); IntSize paintSize = absolutePaintRect().size(); float scaleX = filter->applyHorizontalScale(m_scale); float scaleY = filter->applyVerticalScale(m_scale); float scaleForColorX = scaleX / 255.0; float scaleForColorY = scaleY / 255.0; float scaledOffsetX = 0.5 - scaleX * 0.5; float scaledOffsetY = 0.5 - scaleY * 0.5; int stride = paintSize.width() * 4; for (int y = 0; y < paintSize.height(); ++y) { int line = y * stride; for (int x = 0; x < paintSize.width(); ++x) { int dstIndex = line + x * 4; int srcX = x + static_cast<int>(scaleForColorX * srcPixelArrayB->item(dstIndex + m_xChannelSelector - 1) + scaledOffsetX); int srcY = y + static_cast<int>(scaleForColorY * srcPixelArrayB->item(dstIndex + m_yChannelSelector - 1) + scaledOffsetY); for (unsigned channel = 0; channel < 4; ++channel) { if (srcX < 0 || srcX >= paintSize.width() || srcY < 0 || srcY >= paintSize.height()) { dstPixelArray->set(dstIndex + channel, static_cast<unsigned char>(0)); } else { unsigned char pixelValue = srcPixelArrayA->item(srcY * stride + srcX * 4 + channel); dstPixelArray->set(dstIndex + channel, pixelValue); } } } } }
void FETurbulence::platformApplySoftware() { Uint8ClampedArray* pixelArray = createUnmultipliedImageResult(); if (!pixelArray) return; if (absolutePaintRect().isEmpty()) { pixelArray->zeroFill(); return; } PaintingData paintingData(m_seed, roundedIntSize(filterPrimitiveSubregion().size())); initPaint(paintingData); int optimalThreadNumber = (absolutePaintRect().width() * absolutePaintRect().height()) / s_minimalRectDimension; if (optimalThreadNumber > 1) { // Initialize parallel jobs WTF::ParallelJobs<FillRegionParameters> parallelJobs(&WebCore::FETurbulence::fillRegionWorker, optimalThreadNumber); // Fill the parameter array int i = parallelJobs.numberOfJobs(); if (i > 1) { int startY = 0; int stepY = absolutePaintRect().height() / i; for (; i > 0; --i) { FillRegionParameters& params = parallelJobs.parameter(i-1); params.filter = this; params.pixelArray = pixelArray; params.paintingData = &paintingData; params.startY = startY; if (i != 1) { params.endY = startY + stepY; startY = startY + stepY; } else params.endY = absolutePaintRect().height(); } // Execute parallel jobs parallelJobs.execute(); return; } } // Fallback to single threaded mode if there is no room for a new thread or the paint area is too small. fillRegion(pixelArray, paintingData, 0, absolutePaintRect().height()); }
inline void FEGaussianBlur::platformApplyGeneric(Uint8ClampedArray* srcPixelArray, Uint8ClampedArray* tmpPixelArray, unsigned kernelSizeX, unsigned kernelSizeY, IntSize& paintSize) { int stride = 4 * paintSize.width(); int dxLeft = 0; int dxRight = 0; int dyLeft = 0; int dyRight = 0; Uint8ClampedArray* src = srcPixelArray; Uint8ClampedArray* dst = tmpPixelArray; for (int i = 0; i < 3; ++i) { if (kernelSizeX) { kernelPosition(i, kernelSizeX, dxLeft, dxRight); boxBlur(src, dst, kernelSizeX, dxLeft, dxRight, 4, stride, paintSize.width(), paintSize.height(), isAlphaImage()); swap(src, dst); } if (kernelSizeY) { kernelPosition(i, kernelSizeY, dyLeft, dyRight); boxBlur(src, dst, kernelSizeY, dyLeft, dyRight, stride, 4, paintSize.height(), paintSize.width(), isAlphaImage()); swap(src, dst); } } // The final result should be stored in srcPixelArray. if (dst == srcPixelArray) { ASSERT(src->length() == dst->length()); memcpy(dst->data(), src->data(), src->length()); } }
void FEMorphology::platformApplyGeneric(PaintingData* paintingData, int yStart, int yEnd) { Uint8ClampedArray* srcPixelArray = paintingData->srcPixelArray; Uint8ClampedArray* dstPixelArray = paintingData->dstPixelArray; const int radiusX = paintingData->radiusX; const int radiusY = paintingData->radiusY; const int width = paintingData->width; const int height = paintingData->height; ASSERT(radiusX <= width || radiusY <= height); ASSERT(yStart >= 0 && yEnd <= height && yStart < yEnd); Vector<unsigned char> extrema; for (int y = yStart; y < yEnd; ++y) { int yStartExtrema = std::max(0, y - radiusY); int yEndExtrema = std::min(height - 1, y + radiusY); for (unsigned colorChannel = 0; colorChannel < 4; ++colorChannel) { extrema.clear(); // Compute extremas for each columns for (int x = 0; x < radiusX; ++x) extrema.append(columnExtremum(srcPixelArray, x, yStartExtrema, yEndExtrema, width, colorChannel, m_type)); // Kernel is filled, get extrema of next column for (int x = 0; x < width; ++x) { if (x < width - radiusX) { int xEnd = std::min(x + radiusX, width - 1); extrema.append(columnExtremum(srcPixelArray, xEnd, yStartExtrema, yEndExtrema + 1, width, colorChannel, m_type)); } if (x > radiusX) extrema.remove(0); // The extrema original size = radiusX. // Number of new addition = width - radiusX. // Number of removals = width - radiusX - 1. ASSERT(extrema.size() >= static_cast<size_t>(radiusX + 1)); dstPixelArray->set(pixelArrayIndex(x, y, width, colorChannel), kernelExtremum(extrema, m_type)); } } } }
void FEMorphology::platformApplyGeneric(PaintingData* paintingData, int yStart, int yEnd) { Uint8ClampedArray* srcPixelArray = paintingData->srcPixelArray; Uint8ClampedArray* dstPixelArray = paintingData->dstPixelArray; const int width = paintingData->width; const int height = paintingData->height; const int effectWidth = width * 4; const int radiusX = paintingData->radiusX; const int radiusY = paintingData->radiusY; Vector<unsigned char> extrema; for (int y = yStart; y < yEnd; ++y) { int extremaStartY = std::max(0, y - radiusY); int extremaEndY = std::min(height - 1, y + radiusY); for (unsigned int clrChannel = 0; clrChannel < 4; ++clrChannel) { extrema.clear(); // Compute extremas for each columns for (int x = 0; x <= radiusX; ++x) { unsigned char columnExtrema = srcPixelArray->item(extremaStartY * effectWidth + 4 * x + clrChannel); for (int eY = extremaStartY + 1; eY < extremaEndY; ++eY) { unsigned char pixel = srcPixelArray->item(eY * effectWidth + 4 * x + clrChannel); if ((m_type == FEMORPHOLOGY_OPERATOR_ERODE && pixel <= columnExtrema) || (m_type == FEMORPHOLOGY_OPERATOR_DILATE && pixel >= columnExtrema)) { columnExtrema = pixel; } } extrema.append(columnExtrema); } // Kernel is filled, get extrema of next column for (int x = 0; x < width; ++x) { const int endX = std::min(x + radiusX, width - 1); unsigned char columnExtrema = srcPixelArray->item(extremaStartY * effectWidth + endX * 4 + clrChannel); for (int i = extremaStartY + 1; i <= extremaEndY; ++i) { unsigned char pixel = srcPixelArray->item(i * effectWidth + endX * 4 + clrChannel); if ((m_type == FEMORPHOLOGY_OPERATOR_ERODE && pixel <= columnExtrema) || (m_type == FEMORPHOLOGY_OPERATOR_DILATE && pixel >= columnExtrema)) columnExtrema = pixel; } if (x - radiusX >= 0) extrema.remove(0); if (x + radiusX <= width) extrema.append(columnExtrema); unsigned char entireExtrema = extrema[0]; for (unsigned kernelIndex = 1; kernelIndex < extrema.size(); ++kernelIndex) { if ((m_type == FEMORPHOLOGY_OPERATOR_ERODE && extrema[kernelIndex] <= entireExtrema) || (m_type == FEMORPHOLOGY_OPERATOR_DILATE && extrema[kernelIndex] >= entireExtrema)) entireExtrema = extrema[kernelIndex]; } dstPixelArray->set(y * effectWidth + 4 * x + clrChannel, entireExtrema); } } } }
bool FEBlend::applySoftwareNEON() { if (m_mode != WebBlendModeNormal && m_mode != WebBlendModeMultiply && m_mode != WebBlendModeScreen && m_mode != WebBlendModeDarken && m_mode != WebBlendModeLighten) return false; Uint8ClampedArray* dstPixelArray = createPremultipliedImageResult(); if (!dstPixelArray) return true; FilterEffect* in = inputEffect(0); FilterEffect* in2 = inputEffect(1); IntRect effectADrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArrayA = in->asPremultipliedImage(effectADrawingRect); IntRect effectBDrawingRect = requestedRegionOfInputImageData(in2->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArrayB = in2->asPremultipliedImage(effectBDrawingRect); unsigned pixelArrayLength = srcPixelArrayA->length(); ASSERT(pixelArrayLength == srcPixelArrayB->length()); if (pixelArrayLength >= 8) { platformApplyNEON(srcPixelArrayA->data(), srcPixelArrayB->data(), dstPixelArray->data(), pixelArrayLength); } else { // If there is just one pixel we expand it to two. ASSERT(pixelArrayLength > 0); uint32_t sourceA[2] = {0, 0}; uint32_t sourceBAndDest[2] = {0, 0}; sourceA[0] = reinterpret_cast<uint32_t*>(srcPixelArrayA->data())[0]; sourceBAndDest[0] = reinterpret_cast<uint32_t*>(srcPixelArrayB->data())[0]; platformApplyNEON(reinterpret_cast<uint8_t*>(sourceA), reinterpret_cast<uint8_t*>(sourceBAndDest), reinterpret_cast<uint8_t*>(sourceBAndDest), 8); reinterpret_cast<uint32_t*>(dstPixelArray->data())[0] = sourceBAndDest[0]; } return true; }
void FEBlend::platformApplySoftware() { FilterEffect* in = inputEffect(0); FilterEffect* in2 = inputEffect(1); ASSERT(m_mode > FEBLEND_MODE_UNKNOWN); ASSERT(m_mode <= FEBLEND_MODE_LIGHTEN); Uint8ClampedArray* dstPixelArray = createPremultipliedImageResult(); if (!dstPixelArray) return; IntRect effectADrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArrayA = in->asPremultipliedImage(effectADrawingRect); IntRect effectBDrawingRect = requestedRegionOfInputImageData(in2->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArrayB = in2->asPremultipliedImage(effectBDrawingRect); unsigned pixelArrayLength = srcPixelArrayA->length(); ASSERT(pixelArrayLength == srcPixelArrayB->length()); #if HAVE(ARM_NEON_INTRINSICS) if (pixelArrayLength >= 8) platformApplyNEON(srcPixelArrayA->data(), srcPixelArrayB->data(), dstPixelArray->data(), pixelArrayLength); else { // If there is just one pixel we expand it to two. ASSERT(pixelArrayLength > 0); uint32_t sourceA[2] = {0, 0}; uint32_t sourceBAndDest[2] = {0, 0}; sourceA[0] = reinterpret_cast<uint32_t*>(srcPixelArrayA->data())[0]; sourceBAndDest[0] = reinterpret_cast<uint32_t*>(srcPixelArrayB->data())[0]; platformApplyNEON(reinterpret_cast<uint8_t*>(sourceA), reinterpret_cast<uint8_t*>(sourceBAndDest), reinterpret_cast<uint8_t*>(sourceBAndDest), 8); reinterpret_cast<uint32_t*>(dstPixelArray->data())[0] = sourceBAndDest[0]; } #else platformApplyGeneric(srcPixelArrayA->data(), srcPixelArrayB->data(), dstPixelArray->data(), pixelArrayLength); #endif }
void FEComponentTransfer::platformApplySoftware() { FilterEffect* in = inputEffect(0); Uint8ClampedArray* pixelArray = createUnmultipliedImageResult(); if (!pixelArray) return; unsigned char rValues[256], gValues[256], bValues[256], aValues[256]; getValues(rValues, gValues, bValues, aValues); unsigned char* tables[] = { rValues, gValues, bValues, aValues }; IntRect drawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); in->copyUnmultipliedImage(pixelArray, drawingRect); unsigned pixelArrayLength = pixelArray->length(); for (unsigned pixelOffset = 0; pixelOffset < pixelArrayLength; pixelOffset += 4) { for (unsigned channel = 0; channel < 4; ++channel) { unsigned char c = pixelArray->item(pixelOffset + channel); pixelArray->set(pixelOffset + channel, tables[channel][c]); } } }
bool FECustomFilter::applyShader() { Uint8ClampedArray* dstPixelArray = createUnmultipliedImageResult(); if (!dstPixelArray) return false; FilterEffect* in = inputEffect(0); IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArray = in->asUnmultipliedImage(effectDrawingRect); IntSize newContextSize(effectDrawingRect.size()); bool hadContext = m_context; if (!m_context && !initializeContext()) return false; m_context->makeContextCurrent(); if (!hadContext || m_contextSize != newContextSize) resizeContext(newContextSize); #if !PLATFORM(BLACKBERRY) // BlackBerry defines its own Texture class. // Do not draw the filter if the input image cannot fit inside a single GPU texture. if (m_inputTexture->tiles().numTilesX() != 1 || m_inputTexture->tiles().numTilesY() != 1) return false; #endif // The shader had compiler errors. We cannot draw anything. if (!m_compiledProgram->isInitialized()) return false; m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_frameBuffer); m_context->viewport(0, 0, newContextSize.width(), newContextSize.height()); m_context->clearColor(0, 0, 0, 0); m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT | GraphicsContext3D::DEPTH_BUFFER_BIT); bindProgramAndBuffers(srcPixelArray.get()); m_context->drawElements(GraphicsContext3D::TRIANGLES, m_mesh->indicesCount(), GraphicsContext3D::UNSIGNED_SHORT, 0); unbindVertexAttributes(); ASSERT(static_cast<size_t>(newContextSize.width() * newContextSize.height() * 4) == dstPixelArray->length()); m_context->readPixels(0, 0, newContextSize.width(), newContextSize.height(), GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, dstPixelArray->data()); return true; }
void FECustomFilter::platformApplySoftware() { Uint8ClampedArray* dstPixelArray = createPremultipliedImageResult(); if (!dstPixelArray) return; FilterEffect* in = inputEffect(0); IntRect effectDrawingRect = requestedRegionOfInputImageData(in->absolutePaintRect()); RefPtr<Uint8ClampedArray> srcPixelArray = in->asPremultipliedImage(effectDrawingRect); IntSize newContextSize(effectDrawingRect.size()); bool hadContext = m_context; if (!m_context) initializeContext(); if (!hadContext || m_contextSize != newContextSize) resizeContext(newContextSize); // Do not draw the filter if the input image cannot fit inside a single GPU texture. if (m_inputTexture->tiles().numTilesX() != 1 || m_inputTexture->tiles().numTilesY() != 1) return; // The shader had compiler errors. We cannot draw anything. if (!m_shader->isInitialized()) return; m_context->bindFramebuffer(GraphicsContext3D::FRAMEBUFFER, m_frameBuffer); m_context->viewport(0, 0, newContextSize.width(), newContextSize.height()); m_context->clearColor(0, 0, 0, 0); m_context->clear(GraphicsContext3D::COLOR_BUFFER_BIT | GraphicsContext3D::DEPTH_BUFFER_BIT); bindProgramAndBuffers(srcPixelArray.get()); m_context->drawElements(GraphicsContext3D::TRIANGLES, m_mesh->indicesCount(), GraphicsContext3D::UNSIGNED_SHORT, 0); ASSERT(static_cast<size_t>(newContextSize.width() * newContextSize.height() * 4) == dstPixelArray->length()); m_context->readPixels(0, 0, newContextSize.width(), newContextSize.height(), GraphicsContext3D::RGBA, GraphicsContext3D::UNSIGNED_BYTE, dstPixelArray->data()); }