DEF_TEST(SkDeflateWStream, r) { SkRandom random(123456); for (int i = 0; i < 50; ++i) { uint32_t size = random.nextULessThan(10000); SkAutoTMalloc<uint8_t> buffer(size); for (uint32_t j = 0; j < size; ++j) { buffer[j] = random.nextU() & 0xff; } SkDynamicMemoryWStream dynamicMemoryWStream; { SkDeflateWStream deflateWStream(&dynamicMemoryWStream); uint32_t j = 0; while (j < size) { uint32_t writeSize = SkTMin(size - j, random.nextRangeU(1, 400)); if (!deflateWStream.write(&buffer[j], writeSize)) { ERRORF(r, "something went wrong."); return; } j += writeSize; } } SkAutoTDelete<SkStreamAsset> compressed( dynamicMemoryWStream.detachAsStream()); SkAutoTDelete<SkStreamAsset> decompressed(stream_inflate(compressed)); if (decompressed->getLength() != size) { ERRORF(r, "Decompression failed to get right size [%d]." " %u != %u", i, (unsigned)(decompressed->getLength()), (unsigned)size); SkString s = SkStringPrintf("/tmp/deftst_compressed_%d", i); SkFILEWStream o(s.c_str()); o.writeStream(compressed.get(), compressed->getLength()); compressed->rewind(); s = SkStringPrintf("/tmp/deftst_input_%d", i); SkFILEWStream o2(s.c_str()); o2.write(&buffer[0], size); continue; } uint32_t minLength = SkTMin(size, (uint32_t)(decompressed->getLength())); for (uint32_t i = 0; i < minLength; ++i) { uint8_t c; SkDEBUGCODE(size_t rb =)decompressed->read(&c, sizeof(uint8_t)); SkASSERT(sizeof(uint8_t) == rb); if (buffer[i] != c) { ERRORF(r, "Decompression failed at byte %u.", (unsigned)i); break; } } } }
static bool SK_WARN_UNUSED_RESULT flatten(const SkImage& image, Json::Value* target, bool sendBinaries) { if (sendBinaries) { SkData* encoded = image.encode(SkImageEncoder::kPNG_Type, 100); if (encoded == nullptr) { // PNG encode doesn't necessarily support all color formats, convert to a different // format size_t rowBytes = 4 * image.width(); void* buffer = sk_malloc_throw(rowBytes * image.height()); SkImageInfo dstInfo = SkImageInfo::Make(image.width(), image.height(), kN32_SkColorType, kPremul_SkAlphaType); if (!image.readPixels(dstInfo, buffer, rowBytes, 0, 0)) { SkDebugf("readPixels failed\n"); return false; } SkImage* converted = SkImage::NewRasterCopy(dstInfo, buffer, rowBytes); encoded = converted->encode(SkImageEncoder::kPNG_Type, 100); if (encoded == nullptr) { SkDebugf("image encode failed\n"); return false; } free(converted); free(buffer); } Json::Value bytes; encode_data(encoded->data(), encoded->size(), &bytes); (*target)[SKJSONCANVAS_ATTRIBUTE_BYTES] = bytes; encoded->unref(); } else { SkString description = SkStringPrintf("%dx%d pixel image", image.width(), image.height()); (*target)[SKJSONCANVAS_ATTRIBUTE_DESCRIPTION] = Json::Value(description.c_str()); } return true; }
void SkSVGDevice::drawBitmapCommon(const SkDraw& draw, const SkBitmap& bm, const SkPaint& paint) { SkAutoTUnref<const SkData> pngData( SkImageEncoder::EncodeData(bm, SkImageEncoder::kPNG_Type, SkImageEncoder::kDefaultQuality)); if (!pngData) { return; } size_t b64Size = SkBase64::Encode(pngData->data(), pngData->size(), NULL); SkAutoTMalloc<char> b64Data(b64Size); SkBase64::Encode(pngData->data(), pngData->size(), b64Data.get()); SkString svgImageData("data:image/png;base64,"); svgImageData.append(b64Data.get(), b64Size); SkString imageID = fResourceBucket->addImage(); { AutoElement defs("defs", fWriter); { AutoElement image("image", fWriter); image.addAttribute("id", imageID); image.addAttribute("width", bm.width()); image.addAttribute("height", bm.height()); image.addAttribute("xlink:href", svgImageData); } } { AutoElement imageUse("use", fWriter, fResourceBucket, draw, paint); imageUse.addAttribute("xlink:href", SkStringPrintf("#%s", imageID.c_str())); } }
void CommandSet::drawHelp(SkCanvas* canvas) { if (kNone_HelpMode == fHelpMode) { return; } // Sort commands for current mode: SkTQSort(fCommands.begin(), fCommands.end() - 1, kAlphabetical_HelpMode == fHelpMode ? compareCommandKey : compareCommandGroup); SkPaint bgPaint; bgPaint.setColor(0xC0000000); canvas->drawPaint(bgPaint); SkPaint paint; paint.setTextSize(16); paint.setAntiAlias(true); paint.setColor(0xFFFFFFFF); SkPaint groupPaint; groupPaint.setTextSize(18); groupPaint.setUnderlineText(true); groupPaint.setAntiAlias(true); groupPaint.setColor(0xFFFFFFFF); SkScalar x = SkIntToScalar(10); SkScalar y = SkIntToScalar(10); // Measure all key strings: SkScalar keyWidth = 0; for (Command& cmd : fCommands) { keyWidth = SkMaxScalar(keyWidth, paint.measureText(cmd.fKeyName.c_str(), cmd.fKeyName.size())); } keyWidth += paint.measureText(" ", 1); // If we're grouping by category, we'll be adding text height on every new group (including the // first), so no need to do that here. Otherwise, skip down so the first line is where we want. if (kGrouped_HelpMode != fHelpMode) { y += paint.getTextSize(); } // Print everything: SkString lastGroup; for (Command& cmd : fCommands) { if (kGrouped_HelpMode == fHelpMode && lastGroup != cmd.fGroup) { // Group change. Advance and print header: y += paint.getTextSize(); canvas->drawText(cmd.fGroup.c_str(), cmd.fGroup.size(), x, y, groupPaint); y += groupPaint.getTextSize() + 2; lastGroup = cmd.fGroup; } canvas->drawText(cmd.fKeyName.c_str(), cmd.fKeyName.size(), x, y, paint); SkString text = SkStringPrintf(": %s", cmd.fDescription.c_str()); canvas->drawText(text.c_str(), text.size(), x + keyWidth, y, paint); y += paint.getTextSize() + 2; } }
void SkJsonWriteBuffer::writeByteArray(const void* data, size_t size) { Json::Value jsonArray(Json::arrayValue); const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data); for (size_t i = 0; i < size; ++i) { SkString hexByte = SkStringPrintf("%02x", bytes[i]); jsonArray.append(hexByte.c_str()); } this->append("byteArray", jsonArray); }
static void assert_eql(skiatest::Reporter* reporter, const SkString& skString, const char* str, size_t len) { if (!eq(skString, str, len)) { REPORT_FAILURE(reporter, "", SkStringPrintf( "'%*s' != '%s'", len, str, skString.c_str())); } }
DEF_TEST(SkBlend_optsSqrtCheck, reporter) { for (int c = 0; c < 256; c++) { Sk4f i{(float)c}; Sk4f ii = i * i; Sk4f s = ii.sqrt() + 0.5f; Sk4f sf = s.floor(); REPORTER_ASSERT_MESSAGE( reporter, i[0] == sf[0], SkStringPrintf("i: %f, s: %f", i[0], sf[0])); } }
void JsonWriteBuffer::writeByteArray(const void* data, size_t size) { this->append("byteArray"); fWriter->beginArray(); const uint8_t* bytes = reinterpret_cast<const uint8_t*>(data); for (size_t i = 0; i < size; ++i) { SkString hexByte = SkStringPrintf("%02x", bytes[i]); fWriter->appendString(hexByte.c_str()); } fWriter->endArray(); }
SkString skiatest::Failure::toString() const { SkString result = SkStringPrintf("%s:%d\t", this->fileName, this->lineNo); if (!this->message.isEmpty()) { result.append(this->message); if (strlen(this->condition) > 0) { result.append(": "); } } result.append(this->condition); return result; }
static void sk_trace_dump_visitor(const SkResourceCache::Rec& rec, void* context) { SkTraceMemoryDump* dump = static_cast<SkTraceMemoryDump*>(context); SkString dump_name = SkStringPrintf("skia/sk_resource_cache/%s_%p", rec.getCategory(), &rec); SkDiscardableMemory* discardable = rec.diagnostic_only_getDiscardable(); if (discardable) { dump->setDiscardableMemoryBacking(dump_name.c_str(), *discardable); } else { dump->dumpNumericValue(dump_name.c_str(), "size", "bytes", rec.bytesUsed()); dump->setMemoryBacking(dump_name.c_str(), "malloc", nullptr); } }
static SkString pdf_date(const SkTime::DateTime& dt) { int timeZoneMinutes = SkToInt(dt.fTimeZoneMinutes); char timezoneSign = timeZoneMinutes >= 0 ? '+' : '-'; int timeZoneHours = SkTAbs(timeZoneMinutes) / 60; timeZoneMinutes = SkTAbs(timeZoneMinutes) % 60; return SkStringPrintf( "D:%04u%02u%02u%02u%02u%02u%c%02d'%02d'", static_cast<unsigned>(dt.fYear), static_cast<unsigned>(dt.fMonth), static_cast<unsigned>(dt.fDay), static_cast<unsigned>(dt.fHour), static_cast<unsigned>(dt.fMinute), static_cast<unsigned>(dt.fSecond), timezoneSign, timeZoneHours, timeZoneMinutes); }
void GrCCQuadraticShader::onEmitFragmentCode( GrGLSLFPFragmentBuilder* f, const char* outputCoverage) const { this->calcHullCoverage(&AccessCodeString(f), fCoord_fGrad.fsIn(), SkStringPrintf("%s.x", fEdge_fWind_fCorner.fsIn()).c_str(), outputCoverage); f->codeAppendf("%s *= half(%s.y);", outputCoverage, fEdge_fWind_fCorner.fsIn()); // Wind. if (kFloat4_GrSLType == fEdge_fWind_fCorner.type()) { f->codeAppendf("%s = half(%s.z * %s.w) + %s;", // Attenuated corner coverage. outputCoverage, fEdge_fWind_fCorner.fsIn(), fEdge_fWind_fCorner.fsIn(), outputCoverage); } }
static void sk_trace_dump_visitor(const SkResourceCache::Rec& rec, void* context) { SkTraceMemoryDump* dump = static_cast<SkTraceMemoryDump*>(context); SkString dumpName = SkStringPrintf("skia/sk_resource_cache/%s_%p", rec.getCategory(), &rec); SkDiscardableMemory* discardable = rec.diagnostic_only_getDiscardable(); if (discardable) { dump->setDiscardableMemoryBacking(dumpName.c_str(), *discardable); // The discardable memory size will be calculated by dumper, but we also dump what we think // the size of object in memory is irrespective of whether object is live or dead. dump->dumpNumericValue(dumpName.c_str(), "discardable_size", "bytes", rec.bytesUsed()); } else { dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", rec.bytesUsed()); dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr); } }
SkString HumanizeMs(double ms) { if (ms > 60e+3) return SkStringPrintf("%.3gm", ms/60e+3); if (ms > 1e+3) return SkStringPrintf("%.3gs", ms/1e+3); if (ms < 1e-3) return SkStringPrintf("%.3gns", ms*1e+6); #ifdef SK_BUILD_FOR_WIN if (ms < 1) return SkStringPrintf("%.3gus", ms*1e+3); #else if (ms < 1) return SkStringPrintf("%.3gµs", ms*1e+3); #endif return SkStringPrintf("%.3gms", ms); }
/** * Write the raw encoded bitmap data to a file. */ static bool write_image_to_file(const void* buffer, size_t size, SkBitmap* bitmap) { SkASSERT(!FLAGS_writePath.isEmpty()); SkMemoryStream memStream(buffer, size); SkString outPath; SkImageDecoder::Format format = SkImageDecoder::GetStreamFormat(&memStream); SkString name = SkStringPrintf("%s_%d%s", gInputFileName.c_str(), gImageNo++, get_suffix_from_format(format)); SkString dir(FLAGS_writePath[0]); sk_tools::make_filepath(&outPath, dir, name); SkFILEWStream fileStream(outPath.c_str()); if (!(fileStream.isValid() && fileStream.write(buffer, size))) { SkDebugf("Failed to write encoded data to \"%s\"\n", outPath.c_str()); } // Put in a dummy bitmap. return SkImageDecoder::DecodeStream(&memStream, bitmap, SkBitmap::kNo_Config, SkImageDecoder::kDecodeBounds_Mode); }
int main(int argc, char** argv) { #if defined(SK_BUILD_FOR_MAC) CGColorSpaceRef cs = CGDisplayCopyColorSpace(CGMainDisplayID()); CFDataRef dataRef = CGColorSpaceCopyICCProfile(cs); const uint8_t* data = CFDataGetBytePtr(dataRef); size_t size = CFDataGetLength(dataRef); SkFILEWStream file("monitor_0.icc"); file.write(data, size); CFRelease(cs); CFRelease(dataRef); return 0; #elif defined(SK_BUILD_FOR_WIN) DISPLAY_DEVICE dd = { sizeof(DISPLAY_DEVICE) }; SkString outputFilename; // Chrome's code for this currently just gets the primary monitor's profile. This code iterates // over all attached monitors, so it's "better" in that sense. Making intelligent use of this // information (via things like MonitorFromWindow or MonitorFromRect to pick the correct // profile for a particular window or region of a window), is an exercise left to the reader. for (int i = 0; EnumDisplayDevices(NULL, i, &dd, 0); ++i) { if (dd.StateFlags & DISPLAY_DEVICE_ATTACHED_TO_DESKTOP) { // There are other helpful things in dd at this point: // dd.DeviceString has a longer name for the adapter // dd.StateFlags indicates primary display, mirroring, etc... HDC dc = CreateDC(NULL, dd.DeviceName, NULL, NULL); if (dc) { char icmPath[MAX_PATH + 1]; DWORD pathLength = MAX_PATH; if (GetICMProfile(dc, &pathLength, icmPath)) { // GetICMProfile just returns the path to the installed profile (not the data) outputFilename = SkStringPrintf("monitor_%d.icc", i); CopyFile(icmPath, outputFilename.c_str(), FALSE); } DeleteDC(dc); } } } return 0; #else SkDebugf("ERROR: Unsupported platform\n"); return 1; #endif }
void SkGlyphCache::DumpMemoryStatistics(SkTraceMemoryDump* dump) { dump->dumpNumericValue(gGlyphCacheDumpName, "size", "bytes", SkGraphics::GetFontCacheUsed()); dump->dumpNumericValue(gGlyphCacheDumpName, "budget_size", "bytes", SkGraphics::GetFontCacheLimit()); dump->dumpNumericValue(gGlyphCacheDumpName, "glyph_count", "objects", SkGraphics::GetFontCacheCountUsed()); dump->dumpNumericValue(gGlyphCacheDumpName, "budget_glyph_count", "objects", SkGraphics::GetFontCacheCountLimit()); if (dump->getRequestedDetails() == SkTraceMemoryDump::kLight_LevelOfDetail) { dump->setMemoryBacking(gGlyphCacheDumpName, "malloc", nullptr); return; } auto visitor = [&dump](const SkGlyphCache& cache) { const SkTypeface* face = cache.getScalerContext()->getTypeface(); const SkScalerContextRec& rec = cache.getScalerContext()->getRec(); SkString fontName; face->getFamilyName(&fontName); // Replace all special characters with '_'. for (size_t index = 0; index < fontName.size(); ++index) { if (!std::isalnum(fontName[index])) { fontName[index] = '_'; } } SkString dumpName = SkStringPrintf( "%s/%s_%d/%p", gGlyphCacheDumpName, fontName.c_str(), rec.fFontID, &cache); dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", cache.getMemoryUsed()); dump->dumpNumericValue(dumpName.c_str(), "glyph_count", "objects", cache.countCachedGlyphs()); dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr); }; ForEachStrike(visitor); }
static void sk_trace_dump_visitor(const SkGlyphCache& cache, void* context) { SkTraceMemoryDump* dump = static_cast<SkTraceMemoryDump*>(context); const SkTypeface* face = cache.getScalerContext()->getTypeface(); const SkScalerContextRec& rec = cache.getScalerContext()->getRec(); SkString fontName; face->getFamilyName(&fontName); // Replace all special characters with '_'. for (size_t index = 0; index < fontName.size(); ++index) { if (!std::isalnum(fontName[index])) { fontName[index] = '_'; } } SkString dumpName = SkStringPrintf("%s/%s_%d/%p", gGlyphCacheDumpName, fontName.c_str(), rec.fFontID, &cache); dump->dumpNumericValue(dumpName.c_str(), "size", "bytes", cache.getMemoryUsed()); dump->dumpNumericValue(dumpName.c_str(), "glyph_count", "objects", cache.countCachedGlyphs()); dump->setMemoryBacking(dumpName.c_str(), "malloc", nullptr); }
void SkSVGDevice::drawTextOnPath(const SkDraw&, const void* text, size_t len, const SkPath& path, const SkMatrix* matrix, const SkPaint& paint) { SkString pathID = fResourceBucket->addPath(); { AutoElement defs("defs", fWriter); AutoElement pathElement("path", fWriter); pathElement.addAttribute("id", pathID); pathElement.addPathAttributes(path); } { AutoElement textElement("text", fWriter); textElement.addTextAttributes(paint); if (matrix && !matrix->isIdentity()) { textElement.addAttribute("transform", svg_transform(*matrix)); } { AutoElement textPathElement("textPath", fWriter); textPathElement.addAttribute("xlink:href", SkStringPrintf("#%s", pathID.c_str())); if (paint.getTextAlign() != SkPaint::kLeft_Align) { SkASSERT(paint.getTextAlign() == SkPaint::kCenter_Align || paint.getTextAlign() == SkPaint::kRight_Align); textPathElement.addAttribute("startOffset", paint.getTextAlign() == SkPaint::kCenter_Align ? "50%" : "100%"); } SVGTextBuilder builder(text, len, paint, SkPoint::Make(0, 0), 0); textPathElement.addText(builder.text()); } } }
// Using known images, test that decoding into unpremul and premul behave as expected. DEF_TEST(ImageDecoding_unpremul, reporter) { SkString resourcePath = GetResourcePath(); if (resourcePath.isEmpty()) { SkDebugf("Could not run unpremul test because resourcePath not specified."); return; } const char* root = "half-transparent-white-pixel"; const char* suffixes[] = { ".png", ".webp" }; for (size_t i = 0; i < SK_ARRAY_COUNT(suffixes); ++i) { SkString basename = SkStringPrintf("%s%s", root, suffixes[i]); SkString fullName = SkOSPath::SkPathJoin(resourcePath.c_str(), basename.c_str()); SkBitmap bm; SkFILEStream stream(fullName.c_str()); if (!stream.isValid()) { SkDebugf("file %s missing from resource directoy %s\n", basename.c_str(), resourcePath.c_str()); continue; } // This should never fail since we know the images we're decoding. SkAutoTDelete<SkImageDecoder> decoder(SkImageDecoder::Factory(&stream)); REPORTER_ASSERT(reporter, NULL != decoder.get()); if (NULL == decoder.get()) { continue; } // Test unpremultiplied. We know what color this should result in. decoder->setRequireUnpremultipliedColors(true); bool success = decoder->decode(&stream, &bm, kN32_SkColorType, SkImageDecoder::kDecodePixels_Mode); REPORTER_ASSERT(reporter, success); if (!success) { continue; } REPORTER_ASSERT(reporter, bm.width() == 1 && bm.height() == 1); { SkAutoLockPixels alp(bm); REPORTER_ASSERT(reporter, bm.getAddr32(0, 0)[0] == 0x7fffffff); } success = stream.rewind(); REPORTER_ASSERT(reporter, success); if (!success) { continue; } // Test premultiplied. Once again, we know which color this should // result in. decoder->setRequireUnpremultipliedColors(false); success = decoder->decode(&stream, &bm, kN32_SkColorType, SkImageDecoder::kDecodePixels_Mode); REPORTER_ASSERT(reporter, success); if (!success) { continue; } REPORTER_ASSERT(reporter, bm.width() == 1 && bm.height() == 1); { SkAutoLockPixels alp(bm); REPORTER_ASSERT(reporter, bm.getAddr32(0, 0)[0] == 0x7f7f7f7f); } } }
Error HWUISink::draw(const Src& src, SkBitmap* dst, SkWStream*, SkString*) const { // Do all setup in this function because we don't know the size // for the RenderNode and RenderProxy during the constructor. // In practice this doesn't seem too expensive. const SkISize size = src.size(); // Based on android::SurfaceTexture_init() android::sp<android::IGraphicBufferProducer> producer; android::sp<android::IGraphicBufferConsumer> consumer; android::BufferQueue::createBufferQueue(&producer, &consumer); // Consumer setup android::sp<android::CpuConsumer> cpuConsumer = new android::CpuConsumer(consumer, 1); cpuConsumer->setName(android::String8("SkiaTestClient")); cpuConsumer->setDefaultBufferSize(size.width(), size.height()); // Producer setup android::sp<android::Surface> surface = new android::Surface(producer); native_window_set_buffers_dimensions(surface.get(), size.width(), size.height()); native_window_set_buffers_format(surface.get(), android::PIXEL_FORMAT_RGBA_8888); native_window_set_usage(surface.get(), GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_NEVER | GRALLOC_USAGE_HW_RENDER); // RenderNode setup based on hwui/tests/main.cpp:TreeContentAnimation SkAutoTDelete<android::uirenderer::RenderNode> rootNode (new android::uirenderer::RenderNode()); rootNode->incStrong(nullptr); // Values set here won't be applied until the framework has called // RenderNode::pushStagingPropertiesChanges() during RenderProxy::syncAndDrawFrame(). rootNode->mutateStagingProperties().setLeftTopRightBottom(0, 0, size.width(), size.height()); rootNode->setPropertyFieldsDirty(android::uirenderer::RenderNode::X | android::uirenderer::RenderNode::Y); rootNode->mutateStagingProperties().setClipToBounds(false); rootNode->setPropertyFieldsDirty(android::uirenderer::RenderNode::GENERIC); // RenderProxy setup based on hwui/tests/main.cpp:TreeContentAnimation ContextFactory factory; SkAutoTDelete<android::uirenderer::renderthread::RenderProxy> proxy (new android::uirenderer::renderthread::RenderProxy(false, rootNode, &factory)); proxy->loadSystemProperties(); proxy->initialize(surface.get()); float lightX = size.width() / 2.0f; android::uirenderer::Vector3 lightVector { lightX, dp(-200.0f), dp(800.0f) }; proxy->setup(size.width(), size.height(), lightVector, dp(800.0f), 255 * 0.075f, 255 * 0.15f, kDensity); // Do the draw SkAutoTDelete<android::uirenderer::DisplayListRenderer> renderer (new android::uirenderer::DisplayListRenderer()); renderer->setViewport(size.width(), size.height()); renderer->prepare(); renderer->clipRect(0, 0, size.width(), size.height(), SkRegion::Op::kReplace_Op); Error err = src.draw(renderer->asSkCanvas()); if (!err.isEmpty()) { return err; } renderer->finish(); rootNode->setStagingDisplayList(renderer->finishRecording()); proxy->syncAndDrawFrame(); proxy->fence(); // Capture pixels SkImageInfo destinationConfig = SkImageInfo::Make(size.width(), size.height(), kRGBA_8888_SkColorType, kPremul_SkAlphaType); dst->allocPixels(destinationConfig); sk_memset32((uint32_t*) dst->getPixels(), SK_ColorRED, size.width() * size.height()); android::CpuConsumer::LockedBuffer nativeBuffer; android::status_t retval = cpuConsumer->lockNextBuffer(&nativeBuffer); if (retval == android::BAD_VALUE) { SkDebugf("HWUISink::draw() got no buffer; returning transparent"); // No buffer ready to read - commonly triggered by dm sending us // a no-op source, or calling code that doesn't do anything on this // backend. dst->eraseColor(SK_ColorTRANSPARENT); return ""; } else if (retval) { return SkStringPrintf("Failed to lock buffer to read pixels: %d.", retval); } // Move the pixels into the destination SkBitmap SK_ALWAYSBREAK(nativeBuffer.format == android::PIXEL_FORMAT_RGBA_8888 && "Native buffer not RGBA!"); SkImageInfo nativeConfig = SkImageInfo::Make(nativeBuffer.width, nativeBuffer.height, kRGBA_8888_SkColorType, kPremul_SkAlphaType); // Android stride is in pixels, Skia stride is in bytes SkBitmap nativeWrapper; bool success = nativeWrapper.installPixels(nativeConfig, nativeBuffer.data, nativeBuffer.stride * 4); if (!success) { return "Failed to wrap HWUI buffer in a SkBitmap"; } SK_ALWAYSBREAK(dst->colorType() == kRGBA_8888_SkColorType && "Destination buffer not RGBA!"); success = nativeWrapper.readPixels(destinationConfig, dst->getPixels(), dst->rowBytes(), 0, 0); if (!success) { return "Failed to extract pixels from HWUI buffer"; } cpuConsumer->unlockBuffer(nativeBuffer); return ""; }
sk_sp<SkPDFObject> SkPDFMetadata::MakeXMPObject( const SkDocument::PDFMetadata& metadata, const UUID& doc, const UUID& instance) { static const char templateString[] = "<?xpacket begin=\"\" id=\"W5M0MpCehiHzreSzNTczkc9d\"?>\n" "<x:xmpmeta xmlns:x=\"adobe:ns:meta/\"\n" " x:xmptk=\"Adobe XMP Core 5.4-c005 78.147326, " "2012/08/23-13:03:03\">\n" "<rdf:RDF " "xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\">\n" "<rdf:Description rdf:about=\"\"\n" " xmlns:xmp=\"http://ns.adobe.com/xap/1.0/\"\n" " xmlns:dc=\"http://purl.org/dc/elements/1.1/\"\n" " xmlns:xmpMM=\"http://ns.adobe.com/xap/1.0/mm/\"\n" " xmlns:pdf=\"http://ns.adobe.com/pdf/1.3/\"\n" " xmlns:pdfaid=\"http://www.aiim.org/pdfa/ns/id/\">\n" "<pdfaid:part>2</pdfaid:part>\n" "<pdfaid:conformance>B</pdfaid:conformance>\n" "%s" // ModifyDate "%s" // CreateDate "%s" // xmp:CreatorTool "<dc:format>application/pdf</dc:format>\n" "%s" // dc:title "%s" // dc:description "%s" // author "%s" // keywords "<xmpMM:DocumentID>uuid:%s</xmpMM:DocumentID>\n" "<xmpMM:InstanceID>uuid:%s</xmpMM:InstanceID>\n" "%s" // pdf:Producer "%s" // pdf:Keywords "</rdf:Description>\n" "</rdf:RDF>\n" "</x:xmpmeta>\n" // Note: the standard suggests 4k of padding. "<?xpacket end=\"w\"?>\n"; SkString creationDate; SkString modificationDate; if (metadata.fCreation.fEnabled) { SkString tmp; metadata.fCreation.fDateTime.toISO8601(&tmp); SkASSERT(0 == count_xml_escape_size(tmp)); // YYYY-mm-ddTHH:MM:SS[+|-]ZZ:ZZ; no need to escape creationDate = SkStringPrintf("<xmp:CreateDate>%s</xmp:CreateDate>\n", tmp.c_str()); } if (metadata.fModified.fEnabled) { SkString tmp; metadata.fModified.fDateTime.toISO8601(&tmp); SkASSERT(0 == count_xml_escape_size(tmp)); modificationDate = SkStringPrintf( "<xmp:ModifyDate>%s</xmp:ModifyDate>\n", tmp.c_str()); } SkString title = escape_xml(metadata.fTitle, "<dc:title><rdf:Alt><rdf:li xml:lang=\"x-default\">", "</rdf:li></rdf:Alt></dc:title>\n"); SkString author = escape_xml(metadata.fAuthor, "<dc:creator><rdf:Bag><rdf:li>", "</rdf:li></rdf:Bag></dc:creator>\n"); // TODO: in theory, XMP can support multiple authors. Split on a delimiter? SkString subject = escape_xml( metadata.fSubject, "<dc:description><rdf:Alt><rdf:li xml:lang=\"x-default\">", "</rdf:li></rdf:Alt></dc:description>\n"); SkString keywords1 = escape_xml(metadata.fKeywords, "<dc:subject><rdf:Bag><rdf:li>", "</rdf:li></rdf:Bag></dc:subject>\n"); SkString keywords2 = escape_xml(metadata.fKeywords, "<pdf:Keywords>", "</pdf:Keywords>\n"); // TODO: in theory, keywords can be a list too. SkString producer("<pdf:Producer>" SKPDF_PRODUCER "</pdf:Producer>\n"); if (!metadata.fProducer.isEmpty()) { // TODO: register a developer prefix to make // <skia:SKPDF_CUSTOM_PRODUCER_KEY> a real XML tag. producer = escape_xml( metadata.fProducer, "<pdf:Producer>", "</pdf:Producer>\n<!-- <skia:" SKPDF_CUSTOM_PRODUCER_KEY ">" SKPDF_PRODUCER "</skia:" SKPDF_CUSTOM_PRODUCER_KEY "> -->\n"); } SkString creator = escape_xml(metadata.fCreator, "<xmp:CreatorTool>", "</xmp:CreatorTool>\n"); SkString documentID = uuid_to_string(doc); // no need to escape SkASSERT(0 == count_xml_escape_size(documentID)); SkString instanceID = uuid_to_string(instance); SkASSERT(0 == count_xml_escape_size(instanceID)); return sk_make_sp<PDFXMLObject>(SkStringPrintf( templateString, modificationDate.c_str(), creationDate.c_str(), creator.c_str(), title.c_str(), subject.c_str(), author.c_str(), keywords1.c_str(), documentID.c_str(), instanceID.c_str(), producer.c_str(), keywords2.c_str())); }
static SkString humanize(double ms) { if (FLAGS_verbose) { return SkStringPrintf("%llu", (uint64_t)(ms*1e6)); } return HumanizeMs(ms); }
static void TestString(skiatest::Reporter* reporter) { SkString a; SkString b((size_t)0); SkString c(""); SkString d(NULL, 0); REPORTER_ASSERT(reporter, a.isEmpty()); REPORTER_ASSERT(reporter, a == b && a == c && a == d); a.set("hello"); b.set("hellox", 5); c.set(a); d.resize(5); memcpy(d.writable_str(), "helloz", 5); REPORTER_ASSERT(reporter, !a.isEmpty()); REPORTER_ASSERT(reporter, a.size() == 5); REPORTER_ASSERT(reporter, a == b && a == c && a == d); REPORTER_ASSERT(reporter, a.equals("hello", 5)); REPORTER_ASSERT(reporter, a.equals("hello")); REPORTER_ASSERT(reporter, !a.equals("help")); SkString e(a); SkString f("hello"); SkString g("helloz", 5); REPORTER_ASSERT(reporter, a == e && a == f && a == g); b.set("world"); c = b; REPORTER_ASSERT(reporter, a != b && a != c && b == c); a.append(" world"); e.append("worldz", 5); e.insert(5, " "); f.set("world"); f.prepend("hello "); REPORTER_ASSERT(reporter, a.equals("hello world") && a == e && a == f); a.reset(); b.resize(0); REPORTER_ASSERT(reporter, a.isEmpty() && b.isEmpty() && a == b); a.set("a"); a.set("ab"); a.set("abc"); a.set("abcd"); a.set(""); a.appendS64(72036854775808LL, 0); REPORTER_ASSERT(reporter, a.equals("72036854775808")); a.set(""); a.appendS64(-1844674407370LL, 0); REPORTER_ASSERT(reporter, a.equals("-1844674407370")); a.set(""); a.appendS64(73709551616LL, 15); REPORTER_ASSERT(reporter, a.equals("000073709551616")); a.set(""); a.appendS64(-429496729612LL, 15); REPORTER_ASSERT(reporter, a.equals("-000429496729612")); static const struct { SkScalar fValue; const char* fString; } gRec[] = { { 0, "0" }, { SK_Scalar1, "1" }, { -SK_Scalar1, "-1" }, { SK_Scalar1/2, "0.5" }, #ifdef SK_SCALAR_IS_FLOAT #ifdef SK_BUILD_FOR_WIN { 3.4028234e38f, "3.4028235e+038" }, { -3.4028234e38f, "-3.4028235e+038" }, #else { 3.4028234e38f, "3.4028235e+38" }, { -3.4028234e38f, "-3.4028235e+38" }, #endif #endif }; for (size_t i = 0; i < SK_ARRAY_COUNT(gRec); i++) { a.reset(); a.appendScalar(gRec[i].fValue); REPORTER_ASSERT(reporter, a.size() <= SkStrAppendScalar_MaxSize); // SkDebugf(" received <%s> expected <%s>\n", a.c_str(), gRec[i].fString); REPORTER_ASSERT(reporter, a.equals(gRec[i].fString)); } REPORTER_ASSERT(reporter, SkStringPrintf("%i", 0).equals("0")); char buffer [40]; memset(buffer, 'a', 40); REPORTER_ASSERT(reporter, buffer[18] == 'a'); REPORTER_ASSERT(reporter, buffer[19] == 'a'); REPORTER_ASSERT(reporter, buffer[20] == 'a'); printfAnalog(buffer, 20, "%30d", 0); REPORTER_ASSERT(reporter, buffer[18] == ' '); REPORTER_ASSERT(reporter, buffer[19] == 0); REPORTER_ASSERT(reporter, buffer[20] == 'a'); }
DEF_TEST(Codec_frames, r) { #define kOpaque kOpaque_SkAlphaType #define kUnpremul kUnpremul_SkAlphaType #define kKeep SkCodecAnimation::DisposalMethod::kKeep #define kRestoreBG SkCodecAnimation::DisposalMethod::kRestoreBGColor #define kRestorePrev SkCodecAnimation::DisposalMethod::kRestorePrevious static const struct { const char* fName; int fFrameCount; // One less than fFramecount, since the first frame is always // independent. std::vector<int> fRequiredFrames; // Same, since the first frame should match getInfo std::vector<SkAlphaType> fAlphas; // The size of this one should match fFrameCount for animated, empty // otherwise. std::vector<int> fDurations; int fRepetitionCount; std::vector<SkCodecAnimation::DisposalMethod> fDisposalMethods; } gRecs[] = { { "images/required.gif", 7, { 0, 1, 2, 3, 4, 5 }, { kOpaque, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul }, { 100, 100, 100, 100, 100, 100, 100 }, 0, { kKeep, kRestoreBG, kKeep, kKeep, kKeep, kRestoreBG, kKeep } }, { "images/alphabetAnim.gif", 13, { SkCodec::kNone, 0, 0, 0, 0, 5, 6, SkCodec::kNone, SkCodec::kNone, 9, 10, 11 }, { kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul }, { 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100, 100 }, 0, { kKeep, kRestorePrev, kRestorePrev, kRestorePrev, kRestorePrev, kRestoreBG, kKeep, kRestoreBG, kRestoreBG, kKeep, kKeep, kRestoreBG, kKeep } }, { "images/randPixelsAnim2.gif", 4, // required frames { 0, 0, 1 }, // alphas { kOpaque, kOpaque, kOpaque }, // durations { 0, 1000, 170, 40 }, // repetition count 0, { kKeep, kKeep, kRestorePrev, kKeep } }, { "images/randPixelsAnim.gif", 13, // required frames { 0, 1, 2, 3, 4, 3, 6, 7, 7, 7, 9, 9 }, { kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul, kUnpremul }, // durations { 0, 1000, 170, 40, 220, 7770, 90, 90, 90, 90, 90, 90, 90 }, // repetition count 0, { kKeep, kKeep, kKeep, kKeep, kRestoreBG, kRestoreBG, kRestoreBG, kRestoreBG, kRestorePrev, kRestoreBG, kRestorePrev, kRestorePrev, kRestorePrev, } }, { "images/box.gif", 1, {}, {}, {}, 0, { kKeep } }, { "images/color_wheel.gif", 1, {}, {}, {}, 0, { kKeep } }, { "images/test640x479.gif", 4, { 0, 1, 2 }, { kOpaque, kOpaque, kOpaque }, { 200, 200, 200, 200 }, SkCodec::kRepetitionCountInfinite, { kKeep, kKeep, kKeep, kKeep } }, { "images/colorTables.gif", 2, { 0 }, { kOpaque }, { 1000, 1000 }, 5, { kKeep, kKeep } }, { "images/arrow.png", 1, {}, {}, {}, 0, {} }, { "images/google_chrome.ico", 1, {}, {}, {}, 0, {} }, { "images/brickwork-texture.jpg", 1, {}, {}, {}, 0, {} }, #if defined(SK_CODEC_DECODES_RAW) && (!defined(_WIN32)) { "images/dng_with_preview.dng", 1, {}, {}, {}, 0, {} }, #endif { "images/mandrill.wbmp", 1, {}, {}, {}, 0, {} }, { "images/randPixels.bmp", 1, {}, {}, {}, 0, {} }, { "images/yellow_rose.webp", 1, {}, {}, {}, 0, {} }, { "images/webp-animated.webp", 3, { 0, 1 }, { kOpaque, kOpaque }, { 1000, 500, 1000 }, SkCodec::kRepetitionCountInfinite, { kKeep, kKeep, kKeep } }, { "images/blendBG.webp", 7, { 0, SkCodec::kNone, SkCodec::kNone, SkCodec::kNone, 4, 4 }, { kOpaque, kOpaque, kUnpremul, kOpaque, kUnpremul, kUnpremul }, { 525, 500, 525, 437, 609, 729, 444 }, 7, { kKeep, kKeep, kKeep, kKeep, kKeep, kKeep, kKeep } }, { "images/required.webp", 7, { 0, 1, 1, SkCodec::kNone, 4, 4 }, { kOpaque, kUnpremul, kUnpremul, kOpaque, kOpaque, kOpaque }, { 100, 100, 100, 100, 100, 100, 100 }, 1, { kKeep, kRestoreBG, kKeep, kKeep, kKeep, kRestoreBG, kKeep } }, }; #undef kOpaque #undef kUnpremul #undef kKeep #undef kRestorePrev #undef kRestoreBG for (const auto& rec : gRecs) { sk_sp<SkData> data(GetResourceAsData(rec.fName)); if (!data) { // Useful error statement, but sometimes people run tests without // resources, and they do not want to see these messages. //ERRORF(r, "Missing resources? Could not find '%s'", rec.fName); continue; } std::unique_ptr<SkCodec> codec(SkCodec::MakeFromData(data)); if (!codec) { ERRORF(r, "Failed to create an SkCodec from '%s'", rec.fName); continue; } { SkCodec::FrameInfo frameInfo; REPORTER_ASSERT(r, !codec->getFrameInfo(0, &frameInfo)); } const int repetitionCount = codec->getRepetitionCount(); if (repetitionCount != rec.fRepetitionCount) { ERRORF(r, "%s repetition count does not match! expected: %i\tactual: %i", rec.fName, rec.fRepetitionCount, repetitionCount); } const int expected = rec.fFrameCount; if (rec.fRequiredFrames.size() + 1 != static_cast<size_t>(expected)) { ERRORF(r, "'%s' has wrong number entries in fRequiredFrames; expected: %i\tactual: %i", rec.fName, expected - 1, rec.fRequiredFrames.size()); continue; } if (expected > 1) { if (rec.fDurations.size() != static_cast<size_t>(expected)) { ERRORF(r, "'%s' has wrong number entries in fDurations; expected: %i\tactual: %i", rec.fName, expected, rec.fDurations.size()); continue; } if (rec.fAlphas.size() + 1 != static_cast<size_t>(expected)) { ERRORF(r, "'%s' has wrong number entries in fAlphas; expected: %i\tactual: %i", rec.fName, expected - 1, rec.fAlphas.size()); continue; } if (rec.fDisposalMethods.size() != static_cast<size_t>(expected)) { ERRORF(r, "'%s' has wrong number entries in fDisposalMethods; " "expected %i\tactual: %i", rec.fName, expected, rec.fDisposalMethods.size()); continue; } } enum class TestMode { kVector, kIndividual, }; for (auto mode : { TestMode::kVector, TestMode::kIndividual }) { // Re-create the codec to reset state and test parsing. codec = SkCodec::MakeFromData(data); int frameCount; std::vector<SkCodec::FrameInfo> frameInfos; switch (mode) { case TestMode::kVector: frameInfos = codec->getFrameInfo(); // getFrameInfo returns empty set for non-animated. frameCount = frameInfos.empty() ? 1 : frameInfos.size(); break; case TestMode::kIndividual: frameCount = codec->getFrameCount(); break; } if (frameCount != expected) { ERRORF(r, "'%s' expected frame count: %i\tactual: %i", rec.fName, expected, frameCount); continue; } // From here on, we are only concerned with animated images. if (1 == frameCount) { continue; } for (int i = 0; i < frameCount; i++) { SkCodec::FrameInfo frameInfo; switch (mode) { case TestMode::kVector: frameInfo = frameInfos[i]; break; case TestMode::kIndividual: REPORTER_ASSERT(r, codec->getFrameInfo(i, nullptr)); REPORTER_ASSERT(r, codec->getFrameInfo(i, &frameInfo)); break; } if (rec.fDurations[i] != frameInfo.fDuration) { ERRORF(r, "%s frame %i's durations do not match! expected: %i\tactual: %i", rec.fName, i, rec.fDurations[i], frameInfo.fDuration); } auto to_string = [](SkAlphaType alpha) { switch (alpha) { case kUnpremul_SkAlphaType: return "unpremul"; case kOpaque_SkAlphaType: return "opaque"; default: SkASSERT(false); return "unknown"; } }; auto expectedAlpha = 0 == i ? codec->getInfo().alphaType() : rec.fAlphas[i-1]; auto alpha = frameInfo.fAlphaType; if (expectedAlpha != alpha) { ERRORF(r, "%s's frame %i has wrong alpha type! expected: %s\tactual: %s", rec.fName, i, to_string(expectedAlpha), to_string(alpha)); } if (0 == i) { REPORTER_ASSERT(r, frameInfo.fRequiredFrame == SkCodec::kNone); } else if (rec.fRequiredFrames[i-1] != frameInfo.fRequiredFrame) { ERRORF(r, "%s's frame %i has wrong dependency! expected: %i\tactual: %i", rec.fName, i, rec.fRequiredFrames[i-1], frameInfo.fRequiredFrame); } REPORTER_ASSERT(r, frameInfo.fDisposalMethod == rec.fDisposalMethods[i]); } if (TestMode::kIndividual == mode) { // No need to test decoding twice. continue; } // Compare decoding in multiple ways: // - Start from scratch for each frame. |codec| will have to decode the required frame // (and any it depends on) to decode. This is stored in |cachedFrames|. // - Provide the frame that a frame depends on, so |codec| just has to blend. // - Provide a frame after the required frame, which will be covered up by the newest // frame. // All should look the same. std::vector<SkBitmap> cachedFrames(frameCount); const auto info = codec->getInfo().makeColorType(kN32_SkColorType); auto decode = [&](SkBitmap* bm, int index, int cachedIndex) { auto decodeInfo = info; if (index > 0) { decodeInfo = info.makeAlphaType(frameInfos[index].fAlphaType); } bm->allocPixels(decodeInfo); if (cachedIndex != SkCodec::kNone) { // First copy the pixels from the cached frame const bool success = sk_tool_utils::copy_to(bm, kN32_SkColorType, cachedFrames[cachedIndex]); REPORTER_ASSERT(r, success); } SkCodec::Options opts; opts.fFrameIndex = index; opts.fPriorFrame = cachedIndex; const auto result = codec->getPixels(decodeInfo, bm->getPixels(), bm->rowBytes(), &opts); if (cachedIndex != SkCodec::kNone && restore_previous(frameInfos[cachedIndex])) { if (result == SkCodec::kInvalidParameters) { return true; } ERRORF(r, "Using a kRestorePrevious frame as fPriorFrame should fail"); return false; } if (result != SkCodec::kSuccess) { ERRORF(r, "Failed to decode frame %i from %s when providing prior frame %i, " "error %i", index, rec.fName, cachedIndex, result); } return result == SkCodec::kSuccess; }; for (int i = 0; i < frameCount; i++) { SkBitmap& cachedFrame = cachedFrames[i]; if (!decode(&cachedFrame, i, SkCodec::kNone)) { continue; } const auto reqFrame = frameInfos[i].fRequiredFrame; if (reqFrame == SkCodec::kNone) { // Nothing to compare against. continue; } for (int j = reqFrame; j < i; j++) { SkBitmap frame; if (restore_previous(frameInfos[j])) { (void) decode(&frame, i, j); continue; } if (!decode(&frame, i, j)) { continue; } // Now verify they're equal. const size_t rowLen = info.bytesPerPixel() * info.width(); for (int y = 0; y < info.height(); y++) { const void* cachedAddr = cachedFrame.getAddr(0, y); SkASSERT(cachedAddr != nullptr); const void* addr = frame.getAddr(0, y); SkASSERT(addr != nullptr); const bool lineMatches = memcmp(cachedAddr, addr, rowLen) == 0; if (!lineMatches) { SkString name = SkStringPrintf("cached_%i", i); write_bm(name.c_str(), cachedFrame); name = SkStringPrintf("frame_%i", i); write_bm(name.c_str(), frame); ERRORF(r, "%s's frame %i is different (starting from line %i) when " "providing prior frame %i!", rec.fName, i, y, j); break; } } } } } } }
ClipMaskBench(const char name[], const MaskMakerFunc maskMaker) : fName(SkStringPrintf("clipmask_%s", name)) , fClip(maskMaker(kSize)) {}
static SkString mismatch_message(std::string resourceName, int x, int y, uint32_t src, uint32_t good, uint32_t bad) { return SkStringPrintf( "%s - missmatch at %d, %d src: %08x good: %08x bad: %08x", resourceName.c_str(), x, y, src, good, bad); }
void SkJsonWriteBuffer::append(const char* type, const Json::Value& value) { SkString fullName = SkStringPrintf("%02d_%s", fJson.size(), type); fJson[fullName.c_str()] = value; }
int tool_main(int argc, char** argv) { SetupCrashHandler(); SkCommandLineFlags::Parse(argc, argv); #if SK_ENABLE_INST_COUNT if (FLAGS_leaks) { gPrintInstCount = true; } #endif SkAutoGraphics ag; // First, parse some flags. BenchLogger logger; if (FLAGS_logFile.count()) { logger.SetLogFile(FLAGS_logFile[0]); } LoggerResultsWriter logWriter(logger, FLAGS_timeFormat[0]); MultiResultsWriter writer; writer.add(&logWriter); SkAutoTDelete<JSONResultsWriter> jsonWriter; if (FLAGS_outResultsFile.count()) { jsonWriter.reset(SkNEW(JSONResultsWriter(FLAGS_outResultsFile[0]))); writer.add(jsonWriter.get()); } // Instantiate after all the writers have been added to writer so that we // call close() before their destructors are called on the way out. CallEnd<MultiResultsWriter> ender(writer); const uint8_t alpha = FLAGS_forceBlend ? 0x80 : 0xFF; SkTriState::State dither = SkTriState::kDefault; for (size_t i = 0; i < 3; i++) { if (strcmp(SkTriState::Name[i], FLAGS_forceDither[0]) == 0) { dither = static_cast<SkTriState::State>(i); } } BenchMode benchMode = kNormal_BenchMode; for (size_t i = 0; i < SK_ARRAY_COUNT(BenchMode_Name); i++) { if (strcmp(FLAGS_mode[0], BenchMode_Name[i]) == 0) { benchMode = static_cast<BenchMode>(i); } } SkTDArray<int> configs; bool runDefaultConfigs = false; // Try user-given configs first. for (int i = 0; i < FLAGS_config.count(); i++) { for (int j = 0; j < static_cast<int>(SK_ARRAY_COUNT(gConfigs)); ++j) { if (0 == strcmp(FLAGS_config[i], gConfigs[j].name)) { *configs.append() = j; } else if (0 == strcmp(FLAGS_config[i], kDefaultsConfigStr)) { runDefaultConfigs = true; } } } // If there weren't any, fill in with defaults. if (runDefaultConfigs) { for (int i = 0; i < static_cast<int>(SK_ARRAY_COUNT(gConfigs)); ++i) { if (gConfigs[i].runByDefault) { *configs.append() = i; } } } // Filter out things we can't run. if (kNormal_BenchMode != benchMode) { // Non-rendering configs only run in normal mode for (int i = 0; i < configs.count(); ++i) { const Config& config = gConfigs[configs[i]]; if (Benchmark::kNonRendering_Backend == config.backend) { configs.remove(i, 1); --i; } } } #if SK_SUPPORT_GPU for (int i = 0; i < configs.count(); ++i) { const Config& config = gConfigs[configs[i]]; if (Benchmark::kGPU_Backend == config.backend) { GrContext* context = gContextFactory.get(config.contextType); if (NULL == context) { SkDebugf("GrContext could not be created for config %s. Config will be skipped.\n", config.name); configs.remove(i); --i; continue; } if (config.sampleCount > context->getMaxSampleCount()){ SkDebugf( "Sample count (%d) for config %s is not supported. Config will be skipped.\n", config.sampleCount, config.name); configs.remove(i); --i; continue; } } } #endif // All flags should be parsed now. Report our settings. if (FLAGS_runOnce) { logger.logError("bench was run with --runOnce, so we're going to hide the times." " It's for your own good!\n"); } writer.option("mode", FLAGS_mode[0]); writer.option("alpha", SkStringPrintf("0x%02X", alpha).c_str()); writer.option("antialias", SkStringPrintf("%d", FLAGS_forceAA).c_str()); writer.option("filter", SkStringPrintf("%d", FLAGS_forceFilter).c_str()); writer.option("dither", SkTriState::Name[dither]); writer.option("rotate", SkStringPrintf("%d", FLAGS_rotate).c_str()); writer.option("scale", SkStringPrintf("%d", FLAGS_scale).c_str()); writer.option("clip", SkStringPrintf("%d", FLAGS_clip).c_str()); #if defined(SK_BUILD_FOR_WIN32) writer.option("system", "WIN32"); #elif defined(SK_BUILD_FOR_MAC) writer.option("system", "MAC"); #elif defined(SK_BUILD_FOR_ANDROID) writer.option("system", "ANDROID"); #elif defined(SK_BUILD_FOR_UNIX) writer.option("system", "UNIX"); #else writer.option("system", "other"); #endif #if defined(SK_DEBUG) writer.option("build", "DEBUG"); #else writer.option("build", "RELEASE"); #endif // Set texture cache limits if non-default. for (size_t i = 0; i < SK_ARRAY_COUNT(gConfigs); ++i) { #if SK_SUPPORT_GPU const Config& config = gConfigs[i]; if (Benchmark::kGPU_Backend != config.backend) { continue; } GrContext* context = gContextFactory.get(config.contextType); if (NULL == context) { continue; } size_t bytes; int count; context->getResourceCacheLimits(&count, &bytes); if (-1 != FLAGS_gpuCacheBytes) { bytes = static_cast<size_t>(FLAGS_gpuCacheBytes); } if (-1 != FLAGS_gpuCacheCount) { count = FLAGS_gpuCacheCount; } context->setResourceCacheLimits(count, bytes); #endif } // Run each bench in each configuration it supports and we asked for. Iter iter; Benchmark* bench; while ((bench = iter.next()) != NULL) { SkAutoTUnref<Benchmark> benchUnref(bench); if (SkCommandLineFlags::ShouldSkip(FLAGS_match, bench->getName())) { continue; } bench->setForceAlpha(alpha); bench->setForceAA(FLAGS_forceAA); bench->setForceFilter(FLAGS_forceFilter); bench->setDither(dither); bench->preDraw(); bool loggedBenchName = false; for (int i = 0; i < configs.count(); ++i) { const int configIndex = configs[i]; const Config& config = gConfigs[configIndex]; if (!bench->isSuitableFor(config.backend)) { continue; } GrContext* context = NULL; #if SK_SUPPORT_GPU SkGLContextHelper* glContext = NULL; if (Benchmark::kGPU_Backend == config.backend) { context = gContextFactory.get(config.contextType); if (NULL == context) { continue; } glContext = gContextFactory.getGLContext(config.contextType); } #endif SkAutoTUnref<SkCanvas> canvas; SkAutoTUnref<SkPicture> recordFrom; SkPictureRecorder recorderTo; const SkIPoint dim = bench->getSize(); SkAutoTUnref<SkSurface> surface; if (Benchmark::kNonRendering_Backend != config.backend) { surface.reset(make_surface(config.fColorType, dim, config.backend, config.sampleCount, context)); if (!surface.get()) { logger.logError(SkStringPrintf( "Device creation failure for config %s. Will skip.\n", config.name)); continue; } switch(benchMode) { case kDeferredSilent_BenchMode: case kDeferred_BenchMode: canvas.reset(SkDeferredCanvas::Create(surface.get())); break; case kRecord_BenchMode: canvas.reset(SkRef(recorderTo.beginRecording(dim.fX, dim.fY))); break; case kPictureRecord_BenchMode: { SkPictureRecorder recorderFrom; bench->draw(1, recorderFrom.beginRecording(dim.fX, dim.fY)); recordFrom.reset(recorderFrom.endRecording()); canvas.reset(SkRef(recorderTo.beginRecording(dim.fX, dim.fY))); break; } case kNormal_BenchMode: canvas.reset(SkRef(surface->getCanvas())); break; default: SkASSERT(false); } } if (NULL != canvas) { canvas->clear(SK_ColorWHITE); if (FLAGS_clip) { perform_clip(canvas, dim.fX, dim.fY); } if (FLAGS_scale) { perform_scale(canvas, dim.fX, dim.fY); } if (FLAGS_rotate) { perform_rotate(canvas, dim.fX, dim.fY); } } if (!loggedBenchName) { loggedBenchName = true; writer.bench(bench->getName(), dim.fX, dim.fY); } #if SK_SUPPORT_GPU SkGLContextHelper* contextHelper = NULL; if (Benchmark::kGPU_Backend == config.backend) { contextHelper = gContextFactory.getGLContext(config.contextType); } BenchTimer timer(contextHelper); #else BenchTimer timer; #endif double previous = std::numeric_limits<double>::infinity(); bool converged = false; // variables used to compute loopsPerFrame double frameIntervalTime = 0.0f; int frameIntervalTotalLoops = 0; bool frameIntervalComputed = false; int loopsPerFrame = 0; int loopsPerIter = 0; if (FLAGS_verbose) { SkDebugf("%s %s: ", bench->getName(), config.name); } if (!FLAGS_dryRun) { do { // Ramp up 1 -> 2 -> 4 -> 8 -> 16 -> ... -> ~1 billion. loopsPerIter = (loopsPerIter == 0) ? 1 : loopsPerIter * 2; if (loopsPerIter >= (1<<30) || timer.fWall > FLAGS_maxMs) { // If you find it takes more than a billion loops to get up to 20ms of runtime, // you've got a computer clocked at several THz or have a broken benchmark. ;) // "1B ought to be enough for anybody." logger.logError(SkStringPrintf( "\nCan't get %s %s to converge in %dms (%d loops)", bench->getName(), config.name, FLAGS_maxMs, loopsPerIter)); break; } if ((benchMode == kRecord_BenchMode || benchMode == kPictureRecord_BenchMode)) { // Clear the recorded commands so that they do not accumulate. canvas.reset(SkRef(recorderTo.beginRecording(dim.fX, dim.fY))); } timer.start(); // Inner loop that allows us to break the run into smaller // chunks (e.g. frames). This is especially useful for the GPU // as we can flush and/or swap buffers to keep the GPU from // queuing up too much work. for (int loopCount = loopsPerIter; loopCount > 0; ) { // Save and restore around each call to draw() to guarantee a pristine canvas. SkAutoCanvasRestore saveRestore(canvas, true/*also save*/); int loops; if (frameIntervalComputed && loopCount > loopsPerFrame) { loops = loopsPerFrame; loopCount -= loopsPerFrame; } else { loops = loopCount; loopCount = 0; } if (benchMode == kPictureRecord_BenchMode) { recordFrom->draw(canvas); } else { bench->draw(loops, canvas); } if (kDeferredSilent_BenchMode == benchMode) { static_cast<SkDeferredCanvas*>(canvas.get())->silentFlush(); } else if (NULL != canvas) { canvas->flush(); } #if SK_SUPPORT_GPU // swap drawing buffers on each frame to prevent the GPU // from queuing up too much work if (NULL != glContext) { glContext->swapBuffers(); } #endif } // Stop truncated timers before GL calls complete, and stop the full timers after. timer.truncatedEnd(); #if SK_SUPPORT_GPU if (NULL != glContext) { context->flush(); SK_GL(*glContext, Finish()); } #endif timer.end(); // setup the frame interval for subsequent iterations if (!frameIntervalComputed) { frameIntervalTime += timer.fWall; frameIntervalTotalLoops += loopsPerIter; if (frameIntervalTime >= FLAGS_minMs) { frameIntervalComputed = true; loopsPerFrame = (int)(((double)frameIntervalTotalLoops / frameIntervalTime) * FLAGS_minMs); if (loopsPerFrame < 1) { loopsPerFrame = 1; } // SkDebugf(" %s has %d loops in %f ms (normalized to %d)\n", // bench->getName(), frameIntervalTotalLoops, // timer.fWall, loopsPerFrame); } } const double current = timer.fWall / loopsPerIter; if (FLAGS_verbose && current > previous) { SkDebugf("↑"); } if (FLAGS_verbose) { SkDebugf("%.3g ", current); } converged = HasConverged(previous, current, timer.fWall); previous = current; } while (!FLAGS_runOnce && !converged); } if (FLAGS_verbose) { SkDebugf("\n"); } if (!FLAGS_dryRun && FLAGS_outDir.count() && Benchmark::kNonRendering_Backend != config.backend) { SkAutoTUnref<SkImage> image(surface->newImageSnapshot()); if (image.get()) { saveFile(bench->getName(), config.name, FLAGS_outDir[0], image); } } if (FLAGS_runOnce) { // Let's not mislead ourselves by looking at Debug build or single iteration bench times! continue; } // Normalize to ms per 1000 iterations. const double normalize = 1000.0 / loopsPerIter; const struct { char shortName; const char* longName; double ms; } times[] = { {'w', "msecs", normalize * timer.fWall}, {'W', "Wmsecs", normalize * timer.fTruncatedWall}, {'c', "cmsecs", normalize * timer.fCpu}, {'C', "Cmsecs", normalize * timer.fTruncatedCpu}, {'g', "gmsecs", normalize * timer.fGpu}, }; writer.config(config.name); for (size_t i = 0; i < SK_ARRAY_COUNT(times); i++) { if (strchr(FLAGS_timers[0], times[i].shortName) && times[i].ms > 0) { writer.timer(times[i].longName, times[i].ms); } } } } #if SK_SUPPORT_GPU gContextFactory.destroyContexts(); #endif return 0; }
void JsonWriteBuffer::append(const char* type) { SkString fullName = SkStringPrintf("%02d_%s", fCount++, type); fWriter->appendName(fullName.c_str()); }