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(¶ms);
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(¶ms_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