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.
5 #include "content/renderer/skia_benchmarking_extension.h"
7 #include "base/base64.h"
8 #include "base/time/time.h"
9 #include "base/values.h"
10 #include "cc/base/math_util.h"
11 #include "cc/resources/picture.h"
12 #include "content/public/renderer/v8_value_converter.h"
13 #include "skia/ext/benchmarking_canvas.h"
14 #include "third_party/WebKit/public/platform/WebArrayBuffer.h"
15 #include "third_party/WebKit/public/web/WebFrame.h"
16 #include "third_party/skia/include/core/SkBitmapDevice.h"
17 #include "third_party/skia/include/core/SkCanvas.h"
18 #include "third_party/skia/include/core/SkColorPriv.h"
19 #include "third_party/skia/include/core/SkGraphics.h"
20 #include "third_party/skia/include/core/SkStream.h"
21 #include "third_party/skia/src/utils/debugger/SkDebugCanvas.h"
22 #include "third_party/skia/src/utils/debugger/SkDrawCommand.h"
23 #include "ui/gfx/rect_conversions.h"
24 #include "ui/gfx/skia_util.h"
25 #include "v8/include/v8.h"
27 using blink::WebFrame
;
31 const char kSkiaBenchmarkingExtensionName
[] = "v8/SkiaBenchmarking";
33 static scoped_ptr
<base::Value
> ParsePictureArg(v8::Isolate
* isolate
,
34 v8::Handle
<v8::Value
> arg
) {
35 scoped_ptr
<content::V8ValueConverter
> converter(
36 content::V8ValueConverter::create());
37 return scoped_ptr
<base::Value
>(
38 converter
->FromV8Value(arg
, isolate
->GetCurrentContext()));
41 static scoped_refptr
<cc::Picture
> ParsePictureStr(v8::Isolate
* isolate
,
42 v8::Handle
<v8::Value
> arg
) {
43 scoped_ptr
<base::Value
> picture_value
= ParsePictureArg(isolate
, arg
);
46 return cc::Picture::CreateFromSkpValue(picture_value
.get());
49 static scoped_refptr
<cc::Picture
> ParsePictureHash(v8::Isolate
* isolate
,
50 v8::Handle
<v8::Value
> arg
) {
51 scoped_ptr
<base::Value
> picture_value
= ParsePictureArg(isolate
, arg
);
54 return cc::Picture::CreateFromValue(picture_value
.get());
57 class SkiaBenchmarkingWrapper
: public v8::Extension
{
59 SkiaBenchmarkingWrapper() :
60 v8::Extension(kSkiaBenchmarkingExtensionName
,
61 "if (typeof(chrome) == 'undefined') {"
64 "if (typeof(chrome.skiaBenchmarking) == 'undefined') {"
65 " chrome.skiaBenchmarking = {};"
67 "chrome.skiaBenchmarking.rasterize = function(picture, params) {"
69 " Rasterizes a Picture JSON-encoded by cc::Picture::AsValue()."
70 " @param {Object} picture A json-encoded cc::Picture."
74 " 'overdraw': {Boolean},"
75 " 'clip': [Number, Number, Number, Number]"
76 " } (optional) Rasterization parameters."
79 " 'height': {Number},"
80 " 'data': {ArrayBuffer}"
82 " @returns undefined if the arguments are invalid or the picture"
83 " version is not supported."
85 " native function Rasterize();"
86 " return Rasterize(picture, params);"
88 "chrome.skiaBenchmarking.getOps = function(picture) {"
90 " Extracts the Skia draw commands from a JSON-encoded cc::Picture"
91 " @param {Object} picture A json-encoded cc::Picture."
92 " @returns [{ 'cmd': {String}, 'info': [String, ...] }, ...]"
93 " @returns undefined if the arguments are invalid or the picture"
94 " version is not supported."
96 " native function GetOps();"
97 " return GetOps(picture);"
99 "chrome.skiaBenchmarking.getOpTimings = function(picture) {"
101 " Returns timing information for the given picture."
102 " @param {Object} picture A json-encoded cc::Picture."
103 " @returns { 'total_time': {Number}, 'cmd_times': [Number, ...] }"
104 " @returns undefined if the arguments are invalid or the picture"
105 " version is not supported."
107 " native function GetOpTimings();"
108 " return GetOpTimings(picture);"
110 "chrome.skiaBenchmarking.getInfo = function(picture) {"
112 " Returns meta information for the given picture."
113 " @param {Object} picture A base64 encoded SKP."
114 " @returns { 'width': {Number}, 'height': {Number} }"
115 " @returns undefined if the picture version is not supported."
117 " native function GetInfo();"
118 " return GetInfo(picture);"
121 content::SkiaBenchmarkingExtension::InitSkGraphics();
124 virtual v8::Handle
<v8::FunctionTemplate
> GetNativeFunctionTemplate(
125 v8::Isolate
* isolate
,
126 v8::Handle
<v8::String
> name
) OVERRIDE
{
127 if (name
->Equals(v8::String::NewFromUtf8(isolate
, "Rasterize")))
128 return v8::FunctionTemplate::New(isolate
, Rasterize
);
129 if (name
->Equals(v8::String::NewFromUtf8(isolate
, "GetOps")))
130 return v8::FunctionTemplate::New(isolate
, GetOps
);
131 if (name
->Equals(v8::String::NewFromUtf8(isolate
, "GetOpTimings")))
132 return v8::FunctionTemplate::New(isolate
, GetOpTimings
);
133 if (name
->Equals(v8::String::NewFromUtf8(isolate
, "GetInfo")))
134 return v8::FunctionTemplate::New(isolate
, GetInfo
);
136 return v8::Handle
<v8::FunctionTemplate
>();
139 static void Rasterize(const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
140 if (args
.Length() < 1)
143 v8::Isolate
* isolate
= args
.GetIsolate();
144 scoped_refptr
<cc::Picture
> picture
= ParsePictureHash(isolate
, args
[0]);
149 gfx::Rect
clip_rect(picture
->LayerRect());
151 bool overdraw
= false;
153 if (args
.Length() > 1) {
154 scoped_ptr
<content::V8ValueConverter
> converter(
155 content::V8ValueConverter::create());
156 scoped_ptr
<base::Value
> params_value(
157 converter
->FromV8Value(args
[1], isolate
->GetCurrentContext()));
159 const base::DictionaryValue
* params_dict
= NULL
;
160 if (params_value
.get() && params_value
->GetAsDictionary(¶ms_dict
)) {
161 params_dict
->GetDouble("scale", &scale
);
162 params_dict
->GetInteger("stop", &stop_index
);
163 params_dict
->GetBoolean("overdraw", &overdraw
);
165 const base::Value
* clip_value
= NULL
;
166 if (params_dict
->Get("clip", &clip_value
))
167 cc::MathUtil::FromValue(clip_value
, &clip_rect
);
171 gfx::RectF
clip(clip_rect
);
172 clip
.Intersect(picture
->LayerRect());
174 gfx::Rect snapped_clip
= gfx::ToEnclosingRect(clip
);
176 const int kMaxBitmapSize
= 4096;
177 if (snapped_clip
.width() > kMaxBitmapSize
178 || snapped_clip
.height() > kMaxBitmapSize
)
182 bitmap
.setConfig(SkBitmap::kARGB_8888_Config
, snapped_clip
.width(),
183 snapped_clip
.height());
184 if (!bitmap
.allocPixels())
186 bitmap
.eraseARGB(0, 0, 0, 0);
188 SkCanvas
canvas(bitmap
);
189 canvas
.translate(SkFloatToScalar(-clip
.x()),
190 SkFloatToScalar(-clip
.y()));
191 canvas
.clipRect(gfx::RectToSkRect(snapped_clip
));
192 canvas
.scale(scale
, scale
);
193 canvas
.translate(picture
->LayerRect().x(),
194 picture
->LayerRect().y());
196 // First, build a debug canvas for the given picture.
197 SkDebugCanvas
debug_canvas(picture
->LayerRect().width(),
198 picture
->LayerRect().height());
199 picture
->Replay(&debug_canvas
);
201 // Raster the requested command subset into the bitmap-backed canvas.
202 int last_index
= debug_canvas
.getSize() - 1;
203 if (last_index
>= 0) {
204 debug_canvas
.setOverdrawViz(overdraw
);
205 debug_canvas
.drawTo(&canvas
, stop_index
< 0
207 : std::min(last_index
, stop_index
));
210 blink::WebArrayBuffer buffer
=
211 blink::WebArrayBuffer::create(bitmap
.getSize(), 1);
212 uint32
* packed_pixels
= reinterpret_cast<uint32
*>(bitmap
.getPixels());
213 uint8
* buffer_pixels
= reinterpret_cast<uint8
*>(buffer
.data());
214 // Swizzle from native Skia format to RGBA as we copy out.
215 for (size_t i
= 0; i
< bitmap
.getSize(); i
+= 4) {
216 uint32 c
= packed_pixels
[i
>> 2];
217 buffer_pixels
[i
] = SkGetPackedR32(c
);
218 buffer_pixels
[i
+ 1] = SkGetPackedG32(c
);
219 buffer_pixels
[i
+ 2] = SkGetPackedB32(c
);
220 buffer_pixels
[i
+ 3] = SkGetPackedA32(c
);
223 v8::Handle
<v8::Object
> result
= v8::Object::New(isolate
);
224 result
->Set(v8::String::NewFromUtf8(isolate
, "width"),
225 v8::Number::New(isolate
, snapped_clip
.width()));
226 result
->Set(v8::String::NewFromUtf8(isolate
, "height"),
227 v8::Number::New(isolate
, snapped_clip
.height()));
228 result
->Set(v8::String::NewFromUtf8(isolate
, "data"), buffer
.toV8Value());
230 args
.GetReturnValue().Set(result
);
233 static void GetOps(const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
234 if (args
.Length() != 1)
237 v8::Isolate
* isolate
= args
.GetIsolate();
238 scoped_refptr
<cc::Picture
> picture
= ParsePictureHash(isolate
, args
[0]);
242 gfx::Rect bounds
= picture
->LayerRect();
243 SkDebugCanvas
canvas(bounds
.width(), bounds
.height());
244 picture
->Replay(&canvas
);
246 v8::Local
<v8::Array
> result
= v8::Array::New(isolate
, canvas
.getSize());
247 for (int i
= 0; i
< canvas
.getSize(); ++i
) {
248 DrawType cmd_type
= canvas
.getDrawCommandAt(i
)->getType();
249 v8::Handle
<v8::Object
> cmd
= v8::Object::New(isolate
);
250 cmd
->Set(v8::String::NewFromUtf8(isolate
, "cmd_type"),
251 v8::Integer::New(isolate
, cmd_type
));
252 cmd
->Set(v8::String::NewFromUtf8(isolate
, "cmd_string"),
253 v8::String::NewFromUtf8(
254 isolate
, SkDrawCommand::GetCommandString(cmd_type
)));
256 SkTDArray
<SkString
*>* info
= canvas
.getCommandInfo(i
);
259 v8::Local
<v8::Array
> v8_info
= v8::Array::New(isolate
, info
->count());
260 for (int j
= 0; j
< info
->count(); ++j
) {
261 const SkString
* info_str
= (*info
)[j
];
263 v8_info
->Set(j
, v8::String::NewFromUtf8(isolate
, info_str
->c_str()));
266 cmd
->Set(v8::String::NewFromUtf8(isolate
, "info"), v8_info
);
271 args
.GetReturnValue().Set(result
);
274 static void GetOpTimings(const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
275 if (args
.Length() != 1)
278 v8::Isolate
* isolate
= args
.GetIsolate();
279 scoped_refptr
<cc::Picture
> picture
= ParsePictureHash(isolate
, args
[0]);
283 gfx::Rect bounds
= picture
->LayerRect();
285 // Measure the total time by drawing straight into a bitmap-backed canvas.
286 skia::RefPtr
<SkBaseDevice
> device
= skia::AdoptRef(
287 SkNEW_ARGS(SkBitmapDevice
,
288 (SkBitmap::kARGB_8888_Config
, bounds
.width(), bounds
.height())));
289 SkCanvas
bitmap_canvas(device
.get());
290 bitmap_canvas
.clear(SK_ColorTRANSPARENT
);
291 base::TimeTicks t0
= base::TimeTicks::HighResNow();
292 picture
->Replay(&bitmap_canvas
);
293 base::TimeDelta total_time
= base::TimeTicks::HighResNow() - t0
;
295 // Gather per-op timing info by drawing into a BenchmarkingCanvas.
296 skia::BenchmarkingCanvas
benchmarking_canvas(bounds
.width(),
298 picture
->Replay(&benchmarking_canvas
);
300 v8::Local
<v8::Array
> op_times
=
301 v8::Array::New(isolate
, benchmarking_canvas
.CommandCount());
302 for (size_t i
= 0; i
< benchmarking_canvas
.CommandCount(); ++i
)
303 op_times
->Set(i
, v8::Number::New(isolate
,
304 benchmarking_canvas
.GetTime(i
)));
306 v8::Handle
<v8::Object
> result
= v8::Object::New(isolate
);
307 result
->Set(v8::String::NewFromUtf8(isolate
, "total_time"),
308 v8::Number::New(isolate
, total_time
.InMillisecondsF()));
309 result
->Set(v8::String::NewFromUtf8(isolate
, "cmd_times"), op_times
);
311 args
.GetReturnValue().Set(result
);
314 static void GetInfo(const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
315 if (args
.Length() != 1)
318 v8::Isolate
* isolate
= args
.GetIsolate();
319 scoped_refptr
<cc::Picture
> picture
= ParsePictureStr(isolate
, args
[0]);
323 v8::Handle
<v8::Object
> result
= v8::Object::New(isolate
);
324 result
->Set(v8::String::NewFromUtf8(isolate
, "width"),
325 v8::Number::New(isolate
, picture
->LayerRect().width()));
326 result
->Set(v8::String::NewFromUtf8(isolate
, "height"),
327 v8::Number::New(isolate
, picture
->LayerRect().height()));
329 args
.GetReturnValue().Set(result
);
337 v8::Extension
* SkiaBenchmarkingExtension::Get() {
338 return new SkiaBenchmarkingWrapper();
341 void SkiaBenchmarkingExtension::InitSkGraphics() {
342 // Always call on the main render thread.
343 // Does not need to be thread-safe, as long as the above holds.
344 // FIXME: remove this after Skia updates SkGraphics::Init() to be
345 // thread-safe and idempotent.
346 static bool skia_initialized
= false;
347 if (!skia_initialized
) {
349 skia_initialized
= true;
353 } // namespace content