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 <stddef.h>
8 #include <stdint.h>
9 
10 #include "base/base64.h"
11 #include "base/time/time.h"
12 #include "base/values.h"
13 #include "cc/base/math_util.h"
14 #include "content/public/renderer/chrome_object_extensions_utils.h"
15 #include "content/public/renderer/v8_value_converter.h"
16 #include "content/renderer/render_thread_impl.h"
17 #include "gin/arguments.h"
18 #include "gin/data_object_builder.h"
19 #include "gin/handle.h"
20 #include "gin/object_template_builder.h"
21 #include "skia/ext/benchmarking_canvas.h"
22 #include "skia/ext/legacy_display_globals.h"
23 #include "third_party/blink/public/web/blink.h"
24 #include "third_party/blink/public/web/web_array_buffer.h"
25 #include "third_party/blink/public/web/web_array_buffer_converter.h"
26 #include "third_party/blink/public/web/web_local_frame.h"
27 #include "third_party/skia/include/core/SkCanvas.h"
28 #include "third_party/skia/include/core/SkColorPriv.h"
29 #include "third_party/skia/include/core/SkGraphics.h"
30 #include "third_party/skia/include/core/SkPicture.h"
31 #include "third_party/skia/include/core/SkStream.h"
32 #include "ui/gfx/codec/jpeg_codec.h"
33 #include "ui/gfx/codec/png_codec.h"
34 #include "ui/gfx/geometry/rect_conversions.h"
35 #include "ui/gfx/skia_util.h"
36 #include "v8/include/v8.h"
37 
38 namespace content {
39 
40 namespace {
41 
42 class Picture {
43  public:
44   gfx::Rect layer_rect;
45   sk_sp<SkPicture> picture;
46 };
47 
ParsePictureArg(v8::Isolate * isolate,v8::Local<v8::Value> arg)48 std::unique_ptr<base::Value> ParsePictureArg(v8::Isolate* isolate,
49                                              v8::Local<v8::Value> arg) {
50   return content::V8ValueConverter::Create()->FromV8Value(
51       arg, isolate->GetCurrentContext());
52 }
53 
CreatePictureFromEncodedString(const std::string & encoded)54 std::unique_ptr<Picture> CreatePictureFromEncodedString(
55     const std::string& encoded) {
56   std::string decoded;
57   base::Base64Decode(encoded, &decoded);
58   sk_sp<SkPicture> skpicture =
59       SkPicture::MakeFromData(decoded.data(), decoded.size());
60   if (!skpicture)
61     return nullptr;
62 
63   std::unique_ptr<Picture> picture(new Picture);
64   picture->layer_rect = gfx::SkIRectToRect(skpicture->cullRect().roundOut());
65   picture->picture = std::move(skpicture);
66   return picture;
67 }
68 
ParsePictureStr(v8::Isolate * isolate,v8::Local<v8::Value> arg)69 std::unique_ptr<Picture> ParsePictureStr(v8::Isolate* isolate,
70                                          v8::Local<v8::Value> arg) {
71   std::unique_ptr<base::Value> picture_value = ParsePictureArg(isolate, arg);
72   if (!picture_value)
73     return nullptr;
74   // Decode the picture from base64.
75   std::string encoded;
76   if (!picture_value->GetAsString(&encoded))
77     return nullptr;
78   return CreatePictureFromEncodedString(encoded);
79 }
80 
ParsePictureHash(v8::Isolate * isolate,v8::Local<v8::Value> arg)81 std::unique_ptr<Picture> ParsePictureHash(v8::Isolate* isolate,
82                                           v8::Local<v8::Value> arg) {
83   std::unique_ptr<base::Value> picture_value = ParsePictureArg(isolate, arg);
84   if (!picture_value)
85     return nullptr;
86   const base::DictionaryValue* value = nullptr;
87   if (!picture_value->GetAsDictionary(&value))
88     return nullptr;
89   // Decode the picture from base64.
90   std::string encoded;
91   if (!value->GetString("skp64", &encoded))
92     return nullptr;
93   return CreatePictureFromEncodedString(encoded);
94 }
95 
96 class PicturePlaybackController : public SkPicture::AbortCallback {
97  public:
PicturePlaybackController(const skia::BenchmarkingCanvas & canvas,size_t count)98   PicturePlaybackController(const skia::BenchmarkingCanvas& canvas,
99                             size_t count)
100       : canvas_(canvas), playback_count_(count) {}
101 
abort()102   bool abort() override { return canvas_.CommandCount() > playback_count_; }
103 
104  private:
105   const skia::BenchmarkingCanvas& canvas_;
106   size_t playback_count_;
107 };
108 
109 }  // namespace
110 
111 gin::WrapperInfo SkiaBenchmarking::kWrapperInfo = {gin::kEmbedderNativeGin};
112 
113 // static
Install(blink::WebLocalFrame * frame)114 void SkiaBenchmarking::Install(blink::WebLocalFrame* frame) {
115   v8::Isolate* isolate = blink::MainThreadIsolate();
116   v8::HandleScope handle_scope(isolate);
117   v8::Local<v8::Context> context = frame->MainWorldScriptContext();
118   if (context.IsEmpty())
119     return;
120 
121   v8::Context::Scope context_scope(context);
122 
123   gin::Handle<SkiaBenchmarking> controller =
124       gin::CreateHandle(isolate, new SkiaBenchmarking());
125   if (controller.IsEmpty())
126     return;
127 
128   v8::Local<v8::Object> chrome = GetOrCreateChromeObject(isolate, context);
129   chrome
130       ->Set(context, gin::StringToV8(isolate, "skiaBenchmarking"),
131             controller.ToV8())
132       .Check();
133 }
134 
135 // static
Initialize()136 void SkiaBenchmarking::Initialize() {
137   DCHECK(RenderThreadImpl::current());
138   // FIXME: remove this after Skia updates SkGraphics::Init() to be
139   //        thread-safe and idempotent.
140   static bool skia_initialized = false;
141   if (!skia_initialized) {
142     LOG(WARNING) << "Enabling unsafe Skia benchmarking extension.";
143     SkGraphics::Init();
144     skia_initialized = true;
145   }
146 }
147 
SkiaBenchmarking()148 SkiaBenchmarking::SkiaBenchmarking() {
149   Initialize();
150 }
151 
~SkiaBenchmarking()152 SkiaBenchmarking::~SkiaBenchmarking() {}
153 
GetObjectTemplateBuilder(v8::Isolate * isolate)154 gin::ObjectTemplateBuilder SkiaBenchmarking::GetObjectTemplateBuilder(
155     v8::Isolate* isolate) {
156   return gin::Wrappable<SkiaBenchmarking>::GetObjectTemplateBuilder(isolate)
157       .SetMethod("rasterize", &SkiaBenchmarking::Rasterize)
158       .SetMethod("getOps", &SkiaBenchmarking::GetOps)
159       .SetMethod("getOpTimings", &SkiaBenchmarking::GetOpTimings)
160       .SetMethod("getInfo", &SkiaBenchmarking::GetInfo);
161 }
162 
Rasterize(gin::Arguments * args)163 void SkiaBenchmarking::Rasterize(gin::Arguments* args) {
164   v8::Isolate* isolate = args->isolate();
165   if (args->PeekNext().IsEmpty())
166     return;
167   v8::Local<v8::Value> picture_handle;
168   args->GetNext(&picture_handle);
169   std::unique_ptr<Picture> picture = ParsePictureHash(isolate, picture_handle);
170   if (!picture.get())
171     return;
172 
173   double scale = 1.0;
174   gfx::Rect clip_rect(picture->layer_rect);
175   int stop_index = -1;
176 
177   v8::Local<v8::Context> context = isolate->GetCurrentContext();
178   if (!args->PeekNext().IsEmpty()) {
179     v8::Local<v8::Value> params;
180     args->GetNext(&params);
181     std::unique_ptr<base::Value> params_value =
182         content::V8ValueConverter::Create()->FromV8Value(params, context);
183 
184     const base::DictionaryValue* params_dict = nullptr;
185     if (params_value.get() && params_value->GetAsDictionary(&params_dict)) {
186       params_dict->GetDouble("scale", &scale);
187       params_dict->GetInteger("stop", &stop_index);
188 
189       const base::Value* clip_value = nullptr;
190       if (params_dict->Get("clip", &clip_value))
191         cc::MathUtil::FromValue(clip_value, &clip_rect);
192     }
193   }
194 
195   clip_rect.Intersect(picture->layer_rect);
196   gfx::Rect snapped_clip = gfx::ScaleToEnclosingRect(clip_rect, scale);
197 
198   SkBitmap bitmap;
199   if (!bitmap.tryAllocN32Pixels(snapped_clip.width(), snapped_clip.height()))
200     return;
201   bitmap.eraseARGB(0, 0, 0, 0);
202 
203   SkCanvas canvas(bitmap, SkSurfaceProps{});
204   canvas.translate(SkIntToScalar(-clip_rect.x()),
205                    SkIntToScalar(-clip_rect.y()));
206   canvas.clipRect(gfx::RectToSkRect(snapped_clip));
207   canvas.scale(scale, scale);
208   canvas.translate(picture->layer_rect.x(), picture->layer_rect.y());
209 
210   skia::BenchmarkingCanvas benchmarking_canvas(&canvas);
211   size_t playback_count =
212       (stop_index < 0) ? std::numeric_limits<size_t>::max() : stop_index;
213   PicturePlaybackController controller(benchmarking_canvas, playback_count);
214   picture->picture->playback(&benchmarking_canvas, &controller);
215 
216   blink::WebArrayBuffer buffer =
217       blink::WebArrayBuffer::Create(bitmap.computeByteSize(), 1);
218   uint32_t* packed_pixels = reinterpret_cast<uint32_t*>(bitmap.getPixels());
219   uint8_t* buffer_pixels = reinterpret_cast<uint8_t*>(buffer.Data());
220   // Swizzle from native Skia format to RGBA as we copy out.
221   for (size_t i = 0; i < bitmap.computeByteSize(); i += 4) {
222     uint32_t c = packed_pixels[i >> 2];
223     buffer_pixels[i] = SkGetPackedR32(c);
224     buffer_pixels[i + 1] = SkGetPackedG32(c);
225     buffer_pixels[i + 2] = SkGetPackedB32(c);
226     buffer_pixels[i + 3] = SkGetPackedA32(c);
227   }
228 
229   args->Return(gin::DataObjectBuilder(isolate)
230                    .Set("width", snapped_clip.width())
231                    .Set("height", snapped_clip.height())
232                    .Set("data", blink::WebArrayBufferConverter::ToV8Value(
233                                     &buffer, context->Global(), isolate))
234                    .Build());
235 }
236 
GetOps(gin::Arguments * args)237 void SkiaBenchmarking::GetOps(gin::Arguments* args) {
238   v8::Isolate* isolate = args->isolate();
239   if (args->PeekNext().IsEmpty())
240     return;
241   v8::Local<v8::Value> picture_handle;
242   args->GetNext(&picture_handle);
243   std::unique_ptr<Picture> picture = ParsePictureHash(isolate, picture_handle);
244   if (!picture.get())
245     return;
246 
247   SkSurfaceProps props = skia::LegacyDisplayGlobals::GetSkSurfaceProps();
248   SkCanvas canvas(picture->layer_rect.width(), picture->layer_rect.height(),
249                   &props);
250   skia::BenchmarkingCanvas benchmarking_canvas(&canvas);
251   picture->picture->playback(&benchmarking_canvas);
252 
253   v8::Local<v8::Context> context = isolate->GetCurrentContext();
254 
255   args->Return(content::V8ValueConverter::Create()->ToV8Value(
256       &benchmarking_canvas.Commands(), context));
257 }
258 
GetOpTimings(gin::Arguments * args)259 void SkiaBenchmarking::GetOpTimings(gin::Arguments* args) {
260   v8::Isolate* isolate = args->isolate();
261   if (args->PeekNext().IsEmpty())
262     return;
263   v8::Local<v8::Value> picture_handle;
264   args->GetNext(&picture_handle);
265   std::unique_ptr<Picture> picture = ParsePictureHash(isolate, picture_handle);
266   if (!picture.get())
267     return;
268 
269   gfx::Rect bounds = picture->layer_rect;
270 
271   // Measure the total time by drawing straight into a bitmap-backed canvas.
272   SkBitmap bitmap;
273   bitmap.allocN32Pixels(bounds.width(), bounds.height());
274   SkCanvas bitmap_canvas(bitmap, SkSurfaceProps{});
275   bitmap_canvas.clear(SK_ColorTRANSPARENT);
276   base::TimeTicks t0 = base::TimeTicks::Now();
277   picture->picture->playback(&bitmap_canvas);
278   base::TimeDelta total_time = base::TimeTicks::Now() - t0;
279 
280   // Gather per-op timing info by drawing into a BenchmarkingCanvas.
281   SkCanvas canvas(bitmap, SkSurfaceProps{});
282   canvas.clear(SK_ColorTRANSPARENT);
283   skia::BenchmarkingCanvas benchmarking_canvas(&canvas);
284   picture->picture->playback(&benchmarking_canvas);
285 
286   v8::Local<v8::Context> context = isolate->GetCurrentContext();
287   v8::Local<v8::Array> op_times =
288       v8::Array::New(isolate, benchmarking_canvas.CommandCount());
289   for (size_t i = 0; i < benchmarking_canvas.CommandCount(); ++i) {
290     op_times
291         ->CreateDataProperty(
292             context, i,
293             v8::Number::New(isolate, benchmarking_canvas.GetTime(i)))
294         .Check();
295   }
296 
297   v8::Local<v8::Object> result = v8::Object::New(isolate);
298   result
299       ->Set(context,
300             v8::String::NewFromUtf8(isolate, "total_time",
301                                     v8::NewStringType::kInternalized)
302                 .ToLocalChecked(),
303             v8::Number::New(isolate, total_time.InMillisecondsF()))
304       .Check();
305   result
306       ->Set(context,
307             v8::String::NewFromUtf8(isolate, "cmd_times",
308                                     v8::NewStringType::kInternalized)
309                 .ToLocalChecked(),
310             op_times)
311       .Check();
312 
313   args->Return(result);
314 }
315 
GetInfo(gin::Arguments * args)316 void SkiaBenchmarking::GetInfo(gin::Arguments* args) {
317   v8::Isolate* isolate = args->isolate();
318   if (args->PeekNext().IsEmpty())
319     return;
320   v8::Local<v8::Value> picture_handle;
321   args->GetNext(&picture_handle);
322   std::unique_ptr<Picture> picture = ParsePictureStr(isolate, picture_handle);
323   if (!picture.get())
324     return;
325 
326   v8::Local<v8::Context> context = isolate->GetCurrentContext();
327   v8::Local<v8::Object> result = v8::Object::New(isolate);
328   result
329       ->Set(context,
330             v8::String::NewFromUtf8(isolate, "width",
331                                     v8::NewStringType::kInternalized)
332                 .ToLocalChecked(),
333             v8::Number::New(isolate, picture->layer_rect.width()))
334       .Check();
335   result
336       ->Set(context,
337             v8::String::NewFromUtf8(isolate, "height",
338                                     v8::NewStringType::kInternalized)
339                 .ToLocalChecked(),
340             v8::Number::New(isolate, picture->layer_rect.height()))
341       .Check();
342 
343   args->Return(result);
344 }
345 
346 } // namespace content
347