Home | History | Annotate | Download | only in renderer
      1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "content/renderer/skia_benchmarking_extension.h"
      6 
      7 #include "base/time/time.h"
      8 #include "base/values.h"
      9 #include "cc/base/math_util.h"
     10 #include "cc/resources/picture.h"
     11 #include "content/public/renderer/v8_value_converter.h"
     12 #include "skia/ext/benchmarking_canvas.h"
     13 #include "third_party/WebKit/public/platform/WebArrayBuffer.h"
     14 #include "third_party/WebKit/public/web/WebFrame.h"
     15 #include "third_party/skia/include/core/SkCanvas.h"
     16 #include "third_party/skia/include/core/SkColorPriv.h"
     17 #include "third_party/skia/include/core/SkDevice.h"
     18 #include "third_party/skia/include/core/SkGraphics.h"
     19 #include "third_party/skia/src/utils/debugger/SkDebugCanvas.h"
     20 #include "third_party/skia/src/utils/debugger/SkDrawCommand.h"
     21 #include "ui/gfx/rect_conversions.h"
     22 #include "ui/gfx/skia_util.h"
     23 #include "v8/include/v8.h"
     24 
     25 using WebKit::WebFrame;
     26 
     27 namespace {
     28 
     29 const char kSkiaBenchmarkingExtensionName[] = "v8/SkiaBenchmarking";
     30 
     31 static scoped_refptr<cc::Picture> ParsePictureArg(v8::Handle<v8::Value> arg) {
     32   scoped_ptr<content::V8ValueConverter> converter(
     33       content::V8ValueConverter::create());
     34 
     35   v8::String::Value v8_picture(arg);
     36   scoped_ptr<base::Value> picture_value(
     37       converter->FromV8Value(arg, v8::Context::GetCurrent()));
     38   if (!picture_value)
     39     return NULL;
     40 
     41   return cc::Picture::CreateFromValue(picture_value.get());
     42 }
     43 
     44 class SkiaBenchmarkingWrapper : public v8::Extension {
     45  public:
     46   SkiaBenchmarkingWrapper() :
     47       v8::Extension(kSkiaBenchmarkingExtensionName,
     48         "if (typeof(chrome) == 'undefined') {"
     49         "  chrome = {};"
     50         "};"
     51         "if (typeof(chrome.skiaBenchmarking) == 'undefined') {"
     52         "  chrome.skiaBenchmarking = {};"
     53         "};"
     54         "chrome.skiaBenchmarking.rasterize = function(picture, params) {"
     55         "  /* "
     56         "     Rasterizes a Picture JSON-encoded by cc::Picture::AsValue()."
     57         "     @param {Object} picture A json-encoded cc::Picture."
     58         "     @param {"
     59         "                 'scale':    {Number},"
     60         "                 'stop':     {Number},"
     61         "                 'overdraw': {Boolean},"
     62         "                 'clip':     [Number, Number, Number, Number]"
     63         "     } (optional) Rasterization parameters."
     64         "     @returns {"
     65         "                 'width':    {Number},"
     66         "                 'height':   {Number},"
     67         "                 'data':     {ArrayBuffer}"
     68         "     }"
     69         "     @returns undefined if the arguments are invalid or the picture"
     70         "                        version is not supported."
     71         "   */"
     72         "  native function Rasterize();"
     73         "  return Rasterize(picture, params);"
     74         "};"
     75         "chrome.skiaBenchmarking.getOps = function(picture) {"
     76         "  /* "
     77         "     Extracts the Skia draw commands from a JSON-encoded cc::Picture"
     78         "     @param {Object} picture A json-encoded cc::Picture."
     79         "     @returns [{ 'cmd': {String}, 'info': [String, ...] }, ...]"
     80         "     @returns undefined if the arguments are invalid or the picture"
     81         "                        version is not supported."
     82         "   */"
     83         "  native function GetOps();"
     84         "  return GetOps(picture);"
     85         "};"
     86         "chrome.skiaBenchmarking.getOpTimings = function(picture) {"
     87         "  /* "
     88         "     Returns timing information for the given picture."
     89         "     @param {Object} picture A json-encoded cc::Picture."
     90         "     @returns { 'total_time': {Number}, 'cmd_times': [Number, ...] }"
     91         "     @returns undefined if the arguments are invalid or the picture"
     92         "                        version is not supported."
     93         "   */"
     94         "  native function GetOpTimings();"
     95         "  return GetOpTimings(picture);"
     96         "};"
     97         ) {
     98       content::SkiaBenchmarkingExtension::InitSkGraphics();
     99   }
    100 
    101   virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
    102       v8::Handle<v8::String> name) OVERRIDE {
    103     if (name->Equals(v8::String::New("Rasterize")))
    104       return v8::FunctionTemplate::New(Rasterize);
    105     if (name->Equals(v8::String::New("GetOps")))
    106       return v8::FunctionTemplate::New(GetOps);
    107     if (name->Equals(v8::String::New("GetOpTimings")))
    108       return v8::FunctionTemplate::New(GetOpTimings);
    109 
    110     return v8::Handle<v8::FunctionTemplate>();
    111   }
    112 
    113   static void Rasterize(const v8::FunctionCallbackInfo<v8::Value>& args) {
    114     if (args.Length() < 1)
    115       return;
    116 
    117     scoped_refptr<cc::Picture> picture = ParsePictureArg(args[0]);
    118     if (!picture.get())
    119       return;
    120 
    121     double scale = 1.0;
    122     gfx::Rect clip_rect(picture->LayerRect());
    123     int stop_index = -1;
    124     bool overdraw = false;
    125 
    126     if (args.Length() > 1) {
    127       scoped_ptr<content::V8ValueConverter> converter(
    128           content::V8ValueConverter::create());
    129       scoped_ptr<base::Value> params_value(
    130           converter->FromV8Value(args[1], v8::Context::GetCurrent()));
    131 
    132       const base::DictionaryValue* params_dict = NULL;
    133       if (params_value.get() && params_value->GetAsDictionary(&params_dict)) {
    134         params_dict->GetDouble("scale", &scale);
    135         params_dict->GetInteger("stop", &stop_index);
    136         params_dict->GetBoolean("overdraw", &overdraw);
    137 
    138         const base::Value* clip_value = NULL;
    139         if (params_dict->Get("clip", &clip_value))
    140           cc::MathUtil::FromValue(clip_value, &clip_rect);
    141       }
    142     }
    143 
    144     gfx::RectF clip(clip_rect);
    145     clip.Intersect(picture->LayerRect());
    146     clip.Scale(scale);
    147     gfx::Rect snapped_clip = gfx::ToEnclosingRect(clip);
    148 
    149     const int kMaxBitmapSize = 4096;
    150     if (snapped_clip.width() > kMaxBitmapSize
    151         || snapped_clip.height() > kMaxBitmapSize)
    152       return;
    153 
    154     SkBitmap bitmap;
    155     bitmap.setConfig(SkBitmap::kARGB_8888_Config, snapped_clip.width(),
    156                      snapped_clip.height());
    157     if (!bitmap.allocPixels())
    158       return;
    159     bitmap.eraseARGB(0, 0, 0, 0);
    160 
    161     SkCanvas canvas(bitmap);
    162     canvas.translate(SkFloatToScalar(-clip.x()),
    163                      SkFloatToScalar(-clip.y()));
    164     canvas.clipRect(gfx::RectToSkRect(snapped_clip));
    165     canvas.scale(scale, scale);
    166     canvas.translate(picture->LayerRect().x(),
    167                      picture->LayerRect().y());
    168 
    169     // First, build a debug canvas for the given picture.
    170     SkDebugCanvas debug_canvas(picture->LayerRect().width(),
    171                                picture->LayerRect().height());
    172     picture->Replay(&debug_canvas);
    173 
    174     // Raster the requested command subset into the bitmap-backed canvas.
    175     int last_index = debug_canvas.getSize() - 1;
    176     debug_canvas.setOverdrawViz(overdraw);
    177     debug_canvas.drawTo(&canvas, stop_index < 0
    178                         ? last_index
    179                         : std::min(last_index, stop_index));
    180 
    181     WebKit::WebArrayBuffer buffer =
    182         WebKit::WebArrayBuffer::create(bitmap.getSize(), 1);
    183     uint32* packed_pixels = reinterpret_cast<uint32*>(bitmap.getPixels());
    184     uint8* buffer_pixels = reinterpret_cast<uint8*>(buffer.data());
    185     // Swizzle from native Skia format to RGBA as we copy out.
    186     for (size_t i = 0; i < bitmap.getSize(); i += 4) {
    187         uint32 c = packed_pixels[i >> 2];
    188         buffer_pixels[i]     = SkGetPackedR32(c);
    189         buffer_pixels[i + 1] = SkGetPackedG32(c);
    190         buffer_pixels[i + 2] = SkGetPackedB32(c);
    191         buffer_pixels[i + 3] = SkGetPackedA32(c);
    192     }
    193 
    194     v8::Handle<v8::Object> result = v8::Object::New();
    195     result->Set(v8::String::New("width"),
    196                 v8::Number::New(snapped_clip.width()));
    197     result->Set(v8::String::New("height"),
    198                 v8::Number::New(snapped_clip.height()));
    199     result->Set(v8::String::New("data"), buffer.toV8Value());
    200 
    201     args.GetReturnValue().Set(result);
    202   }
    203 
    204   static void GetOps(const v8::FunctionCallbackInfo<v8::Value>& args) {
    205     if (args.Length() != 1)
    206       return;
    207 
    208     scoped_refptr<cc::Picture> picture = ParsePictureArg(args[0]);
    209     if (!picture.get())
    210       return;
    211 
    212     gfx::Rect bounds = picture->LayerRect();
    213     SkDebugCanvas canvas(bounds.width(), bounds.height());
    214     picture->Replay(&canvas);
    215 
    216     v8::Local<v8::Array> result = v8::Array::New(canvas.getSize());
    217     for (int i = 0; i < canvas.getSize(); ++i) {
    218       DrawType cmd_type = canvas.getDrawCommandAt(i)->getType();
    219       v8::Handle<v8::Object> cmd = v8::Object::New();
    220       cmd->Set(v8::String::New("cmd_type"), v8::Integer::New(cmd_type));
    221       cmd->Set(v8::String::New("cmd_string"), v8::String::New(
    222           SkDrawCommand::GetCommandString(cmd_type)));
    223 
    224       SkTDArray<SkString*>* info = canvas.getCommandInfo(i);
    225       DCHECK(info);
    226 
    227       v8::Local<v8::Array> v8_info = v8::Array::New(info->count());
    228       for (int j = 0; j < info->count(); ++j) {
    229         const SkString* info_str = (*info)[j];
    230         DCHECK(info_str);
    231         v8_info->Set(j, v8::String::New(info_str->c_str()));
    232       }
    233 
    234       cmd->Set(v8::String::New("info"), v8_info);
    235 
    236       result->Set(i, cmd);
    237     }
    238 
    239     args.GetReturnValue().Set(result);
    240   }
    241 
    242   static void GetOpTimings(const v8::FunctionCallbackInfo<v8::Value>& args) {
    243     if (args.Length() != 1)
    244       return;
    245 
    246     scoped_refptr<cc::Picture> picture = ParsePictureArg(args[0]);
    247     if (!picture.get())
    248       return;
    249 
    250     gfx::Rect bounds = picture->LayerRect();
    251 
    252     // Measure the total time by drawing straight into a bitmap-backed canvas.
    253     skia::RefPtr<SkDevice> device = skia::AdoptRef(SkNEW_ARGS(SkDevice,
    254         (SkBitmap::kARGB_8888_Config, bounds.width(), bounds.height())));
    255     SkCanvas bitmap_canvas(device.get());
    256     bitmap_canvas.clear(SK_ColorTRANSPARENT);
    257     base::TimeTicks t0 = base::TimeTicks::HighResNow();
    258     picture->Replay(&bitmap_canvas);
    259     base::TimeDelta total_time = base::TimeTicks::HighResNow() - t0;
    260 
    261     // Gather per-op timing info by drawing into a BenchmarkingCanvas.
    262     skia::BenchmarkingCanvas benchmarking_canvas(bounds.width(),
    263                                                  bounds.height());
    264     picture->Replay(&benchmarking_canvas);
    265 
    266     v8::Local<v8::Array> op_times =
    267             v8::Array::New(benchmarking_canvas.CommandCount());
    268     for (size_t i = 0; i < benchmarking_canvas.CommandCount(); ++i)
    269         op_times->Set(i, v8::Number::New(benchmarking_canvas.GetTime(i)));
    270 
    271     v8::Handle<v8::Object> result = v8::Object::New();
    272     result->Set(v8::String::New("total_time"),
    273                 v8::Number::New(total_time.InMillisecondsF()));
    274     result->Set(v8::String::New("cmd_times"), op_times);
    275 
    276     args.GetReturnValue().Set(result);
    277   }
    278 };
    279 
    280 } // namespace
    281 
    282 namespace content {
    283 
    284 v8::Extension* SkiaBenchmarkingExtension::Get() {
    285   return new SkiaBenchmarkingWrapper();
    286 }
    287 
    288 void SkiaBenchmarkingExtension::InitSkGraphics() {
    289     // Always call on the main render thread.
    290     // Does not need to be thread-safe, as long as the above holds.
    291     // FIXME: remove this after Skia updates SkGraphics::Init() to be
    292     //        thread-safe and idempotent.
    293     static bool skia_initialized = false;
    294     if (!skia_initialized) {
    295       SkGraphics::Init();
    296       skia_initialized = true;
    297     }
    298 }
    299 
    300 } // namespace content
    301