1 // Copyright (c) 2012 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/gpu/gpu_benchmarking_extension.h"
9 #include "base/base64.h"
10 #include "base/file_util.h"
11 #include "base/files/file_path.h"
12 #include "base/memory/scoped_vector.h"
13 #include "base/strings/string_number_conversions.h"
14 #include "cc/layers/layer.h"
15 #include "content/common/browser_rendering_stats.h"
16 #include "content/common/gpu/gpu_rendering_stats.h"
17 #include "content/public/renderer/render_thread.h"
18 #include "content/public/renderer/v8_value_converter.h"
19 #include "content/renderer/gpu/render_widget_compositor.h"
20 #include "content/renderer/render_thread_impl.h"
21 #include "content/renderer/render_view_impl.h"
22 #include "content/renderer/skia_benchmarking_extension.h"
23 #include "third_party/WebKit/public/web/WebFrame.h"
24 #include "third_party/WebKit/public/web/WebImageCache.h"
25 #include "third_party/WebKit/public/web/WebView.h"
26 #include "third_party/skia/include/core/SkData.h"
27 #include "third_party/skia/include/core/SkGraphics.h"
28 #include "third_party/skia/include/core/SkPicture.h"
29 #include "third_party/skia/include/core/SkPixelRef.h"
30 #include "third_party/skia/include/core/SkStream.h"
31 #include "ui/gfx/codec/png_codec.h"
32 #include "v8/include/v8.h"
33 #include "webkit/renderer/compositor_bindings/web_rendering_stats_impl.h"
35 using WebKit::WebCanvas;
36 using WebKit::WebFrame;
37 using WebKit::WebImageCache;
38 using WebKit::WebPrivatePtr;
39 using WebKit::WebRenderingStatsImpl;
40 using WebKit::WebSize;
41 using WebKit::WebView;
43 const char kGpuBenchmarkingExtensionName[] = "v8/GpuBenchmarking";
45 static SkData* EncodeBitmapToData(size_t* offset, const SkBitmap& bm) {
46 SkPixelRef* pr = bm.pixelRef();
48 SkData* data = pr->refEncodedData();
50 *offset = bm.pixelRefOffset();
54 std::vector<unsigned char> vector;
55 if (gfx::PNGCodec::EncodeBGRASkBitmap(bm, false, &vector)) {
56 return SkData::NewWithCopy(&vector.front() , vector.size());
63 class SkPictureSerializer {
65 explicit SkPictureSerializer(const base::FilePath& dirpath)
68 // Let skia register known effect subclasses. This basically enables
69 // reflection on those subclasses required for picture serialization.
70 content::SkiaBenchmarkingExtension::InitSkGraphics();
73 // Recursively serializes the layer tree.
74 // Each layer in the tree is serialized into a separate skp file
75 // in the given directory.
76 void Serialize(const cc::Layer* layer) {
77 const cc::LayerList& children = layer->children();
78 for (size_t i = 0; i < children.size(); ++i) {
79 Serialize(children[i].get());
82 skia::RefPtr<SkPicture> picture = layer->GetPicture();
86 // Serialize picture to file.
87 // TODO(alokp): Note that for this to work Chrome needs to be launched with
88 // --no-sandbox command-line flag. Get rid of this limitation.
90 std::string filename = "layer_" + base::IntToString(layer_id_++) + ".skp";
91 std::string filepath = dirpath_.AppendASCII(filename).MaybeAsASCII();
92 DCHECK(!filepath.empty());
93 SkFILEWStream file(filepath.c_str());
94 DCHECK(file.isValid());
95 picture->serialize(&file, &EncodeBitmapToData);
99 base::FilePath dirpath_;
103 class RenderingStatsEnumerator : public cc::RenderingStats::Enumerator {
105 RenderingStatsEnumerator(v8::Handle<v8::Object> stats_object)
106 : stats_object(stats_object) { }
108 virtual void AddInt64(const char* name, int64 value) OVERRIDE {
109 stats_object->Set(v8::String::New(name), v8::Number::New(value));
112 virtual void AddDouble(const char* name, double value) OVERRIDE {
113 stats_object->Set(v8::String::New(name), v8::Number::New(value));
116 virtual void AddInt(const char* name, int value) OVERRIDE {
117 stats_object->Set(v8::String::New(name), v8::Integer::New(value));
120 virtual void AddTimeDeltaInSecondsF(const char* name,
121 const base::TimeDelta& value) OVERRIDE {
122 stats_object->Set(v8::String::New(name),
123 v8::Number::New(value.InSecondsF()));
127 v8::Handle<v8::Object> stats_object;
136 class CallbackAndContext : public base::RefCounted<CallbackAndContext> {
138 CallbackAndContext(v8::Isolate* isolate,
139 v8::Handle<v8::Function> callback,
140 v8::Handle<v8::Context> context)
141 : isolate_(isolate) {
142 callback_.Reset(isolate_, callback);
143 context_.Reset(isolate_, context);
146 v8::Isolate* isolate() {
150 v8::Handle<v8::Function> GetCallback() {
151 return v8::Local<v8::Function>::New(isolate_, callback_);
154 v8::Handle<v8::Context> GetContext() {
155 return v8::Local<v8::Context>::New(isolate_, context_);
159 friend class base::RefCounted<CallbackAndContext>;
161 virtual ~CallbackAndContext() {
166 v8::Isolate* isolate_;
167 v8::Persistent<v8::Function> callback_;
168 v8::Persistent<v8::Context> context_;
169 DISALLOW_COPY_AND_ASSIGN(CallbackAndContext);
172 class GpuBenchmarkingContext {
174 GpuBenchmarkingContext()
177 render_view_impl_(NULL),
180 bool Init(bool init_compositor) {
181 web_frame_ = WebFrame::frameForCurrentContext();
185 web_view_ = web_frame_->view();
191 render_view_impl_ = RenderViewImpl::FromWebView(web_view_);
192 if (!render_view_impl_) {
198 if (!init_compositor)
201 compositor_ = render_view_impl_->compositor();
205 render_view_impl_ = NULL;
212 WebFrame* web_frame() const {
213 DCHECK(web_frame_ != NULL);
216 WebView* web_view() const {
217 DCHECK(web_view_ != NULL);
220 RenderViewImpl* render_view_impl() const {
221 DCHECK(render_view_impl_ != NULL);
222 return render_view_impl_;
224 RenderWidgetCompositor* compositor() const {
225 DCHECK(compositor_ != NULL);
230 WebFrame* web_frame_;
232 RenderViewImpl* render_view_impl_;
233 RenderWidgetCompositor* compositor_;
235 DISALLOW_COPY_AND_ASSIGN(GpuBenchmarkingContext);
240 class GpuBenchmarkingWrapper : public v8::Extension {
242 GpuBenchmarkingWrapper() :
243 v8::Extension(kGpuBenchmarkingExtensionName,
244 "if (typeof(chrome) == 'undefined') {"
247 "if (typeof(chrome.gpuBenchmarking) == 'undefined') {"
248 " chrome.gpuBenchmarking = {};"
250 "chrome.gpuBenchmarking.setNeedsDisplayOnAllLayers = function() {"
251 " native function SetNeedsDisplayOnAllLayers();"
252 " return SetNeedsDisplayOnAllLayers();"
254 "chrome.gpuBenchmarking.setRasterizeOnlyVisibleContent = function() {"
255 " native function SetRasterizeOnlyVisibleContent();"
256 " return SetRasterizeOnlyVisibleContent();"
258 "chrome.gpuBenchmarking.renderingStats = function() {"
259 " native function GetRenderingStats();"
260 " return GetRenderingStats();"
262 "chrome.gpuBenchmarking.gpuRenderingStats = function() {"
263 " native function GetGpuRenderingStats();"
264 " return GetGpuRenderingStats();"
266 "chrome.gpuBenchmarking.printToSkPicture = function(dirname) {"
267 " native function PrintToSkPicture();"
268 " return PrintToSkPicture(dirname);"
270 "chrome.gpuBenchmarking.smoothScrollBy = "
271 " function(pixels_to_scroll, opt_callback, opt_mouse_event_x,"
272 " opt_mouse_event_y) {"
273 " pixels_to_scroll = pixels_to_scroll || 0;"
274 " callback = opt_callback || function() { };"
275 " native function BeginSmoothScroll();"
276 " if (typeof opt_mouse_event_x !== 'undefined' &&"
277 " typeof opt_mouse_event_y !== 'undefined') {"
278 " return BeginSmoothScroll(pixels_to_scroll >= 0, callback,"
279 " Math.abs(pixels_to_scroll),"
280 " opt_mouse_event_x, opt_mouse_event_y);"
282 " return BeginSmoothScroll(pixels_to_scroll >= 0, callback,"
283 " Math.abs(pixels_to_scroll));"
286 "chrome.gpuBenchmarking.smoothScrollBySendsTouch = function() {"
287 " native function SmoothScrollSendsTouch();"
288 " return SmoothScrollSendsTouch();"
290 "chrome.gpuBenchmarking.pinchBy = "
291 " function(zoom_in, pixels_to_move, anchor_x, anchor_y,"
293 " callback = opt_callback || function() { };"
294 " native function BeginPinch();"
295 " return BeginPinch(zoom_in, pixels_to_move,"
296 " anchor_x, anchor_y, callback);"
298 "chrome.gpuBenchmarking.beginWindowSnapshotPNG = function(callback) {"
299 " native function BeginWindowSnapshotPNG();"
300 " BeginWindowSnapshotPNG(callback);"
302 "chrome.gpuBenchmarking.clearImageCache = function() {"
303 " native function ClearImageCache();"
304 " ClearImageCache();"
306 "chrome.gpuBenchmarking.runMicroBenchmark ="
307 " function(name, callback, opt_arguments) {"
308 " arguments = opt_arguments || {};"
309 " native function RunMicroBenchmark();"
310 " return RunMicroBenchmark(name, callback, arguments);"
312 "chrome.gpuBenchmarking.hasGpuProcess = function() {"
313 " native function HasGpuProcess();"
314 " return HasGpuProcess();"
317 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
318 v8::Handle<v8::String> name) OVERRIDE {
319 if (name->Equals(v8::String::New("SetNeedsDisplayOnAllLayers")))
320 return v8::FunctionTemplate::New(SetNeedsDisplayOnAllLayers);
321 if (name->Equals(v8::String::New("SetRasterizeOnlyVisibleContent")))
322 return v8::FunctionTemplate::New(SetRasterizeOnlyVisibleContent);
323 if (name->Equals(v8::String::New("GetRenderingStats")))
324 return v8::FunctionTemplate::New(GetRenderingStats);
325 if (name->Equals(v8::String::New("GetGpuRenderingStats")))
326 return v8::FunctionTemplate::New(GetGpuRenderingStats);
327 if (name->Equals(v8::String::New("PrintToSkPicture")))
328 return v8::FunctionTemplate::New(PrintToSkPicture);
329 if (name->Equals(v8::String::New("BeginSmoothScroll")))
330 return v8::FunctionTemplate::New(BeginSmoothScroll);
331 if (name->Equals(v8::String::New("SmoothScrollSendsTouch")))
332 return v8::FunctionTemplate::New(SmoothScrollSendsTouch);
333 if (name->Equals(v8::String::New("BeginPinch")))
334 return v8::FunctionTemplate::New(BeginPinch);
335 if (name->Equals(v8::String::New("BeginWindowSnapshotPNG")))
336 return v8::FunctionTemplate::New(BeginWindowSnapshotPNG);
337 if (name->Equals(v8::String::New("ClearImageCache")))
338 return v8::FunctionTemplate::New(ClearImageCache);
339 if (name->Equals(v8::String::New("RunMicroBenchmark")))
340 return v8::FunctionTemplate::New(RunMicroBenchmark);
341 if (name->Equals(v8::String::New("HasGpuProcess")))
342 return v8::FunctionTemplate::New(HasGpuProcess);
344 return v8::Handle<v8::FunctionTemplate>();
347 static void SetNeedsDisplayOnAllLayers(
348 const v8::FunctionCallbackInfo<v8::Value>& args) {
349 GpuBenchmarkingContext context;
350 if (!context.Init(true))
353 context.compositor()->SetNeedsDisplayOnAllLayers();
356 static void SetRasterizeOnlyVisibleContent(
357 const v8::FunctionCallbackInfo<v8::Value>& args) {
358 GpuBenchmarkingContext context;
359 if (!context.Init(true))
362 context.compositor()->SetRasterizeOnlyVisibleContent();
365 static void GetRenderingStats(
366 const v8::FunctionCallbackInfo<v8::Value>& args) {
368 GpuBenchmarkingContext context;
369 if (!context.Init(false))
372 WebRenderingStatsImpl stats;
373 context.render_view_impl()->GetRenderingStats(stats);
375 content::GpuRenderingStats gpu_stats;
376 context.render_view_impl()->GetGpuRenderingStats(&gpu_stats);
377 BrowserRenderingStats browser_stats;
378 context.render_view_impl()->GetBrowserRenderingStats(&browser_stats);
379 v8::Handle<v8::Object> stats_object = v8::Object::New();
381 RenderingStatsEnumerator enumerator(stats_object);
382 stats.rendering_stats.EnumerateFields(&enumerator);
383 gpu_stats.EnumerateFields(&enumerator);
384 browser_stats.EnumerateFields(&enumerator);
386 args.GetReturnValue().Set(stats_object);
389 static void GetGpuRenderingStats(
390 const v8::FunctionCallbackInfo<v8::Value>& args) {
392 GpuBenchmarkingContext context;
393 if (!context.Init(false))
396 content::GpuRenderingStats gpu_stats;
397 context.render_view_impl()->GetGpuRenderingStats(&gpu_stats);
399 v8::Handle<v8::Object> stats_object = v8::Object::New();
400 RenderingStatsEnumerator enumerator(stats_object);
401 gpu_stats.EnumerateFields(&enumerator);
403 args.GetReturnValue().Set(stats_object);
406 static void PrintToSkPicture(
407 const v8::FunctionCallbackInfo<v8::Value>& args) {
408 if (args.Length() != 1)
411 v8::String::AsciiValue dirname(args[0]);
412 if (dirname.length() == 0)
415 GpuBenchmarkingContext context;
416 if (!context.Init(true))
419 const cc::Layer* root_layer = context.compositor()->GetRootLayer();
423 base::FilePath dirpath(
424 base::FilePath::StringType(*dirname, *dirname + dirname.length()));
425 if (!file_util::CreateDirectory(dirpath) ||
426 !base::PathIsWritable(dirpath)) {
427 std::string msg("Path is not writable: ");
428 msg.append(dirpath.MaybeAsASCII());
429 v8::ThrowException(v8::Exception::Error(
430 v8::String::New(msg.c_str(), msg.length())));
434 SkPictureSerializer serializer(dirpath);
435 serializer.Serialize(root_layer);
438 static void OnSmoothScrollCompleted(
439 CallbackAndContext* callback_and_context) {
440 v8::HandleScope scope(callback_and_context->isolate());
441 v8::Handle<v8::Context> context = callback_and_context->GetContext();
442 v8::Context::Scope context_scope(context);
443 WebFrame* frame = WebFrame::frameForContext(context);
445 frame->callFunctionEvenIfScriptDisabled(
446 callback_and_context->GetCallback(), v8::Object::New(), 0, NULL);
450 static void SmoothScrollSendsTouch(
451 const v8::FunctionCallbackInfo<v8::Value>& args) {
452 // TODO(epenner): Should other platforms emulate touch events?
453 #if defined(OS_ANDROID) || defined(OS_CHROMEOS)
454 args.GetReturnValue().Set(true);
456 args.GetReturnValue().Set(false);
460 static void BeginSmoothScroll(
461 const v8::FunctionCallbackInfo<v8::Value>& args) {
462 GpuBenchmarkingContext context;
463 if (!context.Init(false))
466 // Account for the 2 optional arguments, mouse_event_x and mouse_event_y.
467 int arglen = args.Length();
469 !args[0]->IsBoolean() ||
470 !args[1]->IsFunction() ||
471 !args[2]->IsNumber()) {
472 args.GetReturnValue().Set(false);
476 bool scroll_down = args[0]->BooleanValue();
477 v8::Local<v8::Function> callback_local =
478 v8::Local<v8::Function>::Cast(args[1]);
480 scoped_refptr<CallbackAndContext> callback_and_context =
481 new CallbackAndContext(args.GetIsolate(),
483 context.web_frame()->mainWorldScriptContext());
485 int pixels_to_scroll = args[2]->IntegerValue();
487 int mouse_event_x = 0;
488 int mouse_event_y = 0;
491 WebKit::WebRect rect = context.render_view_impl()->windowRect();
492 mouse_event_x = rect.x + rect.width / 2;
493 mouse_event_y = rect.y + rect.height / 2;
496 !args[3]->IsNumber() ||
497 !args[4]->IsNumber()) {
498 args.GetReturnValue().Set(false);
502 mouse_event_x = args[3]->IntegerValue() *
503 context.web_view()->pageScaleFactor();
504 mouse_event_y = args[4]->IntegerValue() *
505 context.web_view()->pageScaleFactor();
508 // TODO(nduca): If the render_view_impl is destroyed while the gesture is in
509 // progress, we will leak the callback and context. This needs to be fixed,
511 context.render_view_impl()->BeginSmoothScroll(
513 base::Bind(&OnSmoothScrollCompleted,
514 callback_and_context),
519 args.GetReturnValue().Set(true);
522 static void BeginPinch(
523 const v8::FunctionCallbackInfo<v8::Value>& args) {
524 GpuBenchmarkingContext context;
525 if (!context.Init(false))
528 int arglen = args.Length();
530 !args[0]->IsBoolean() ||
531 !args[1]->IsNumber() ||
532 !args[2]->IsNumber() ||
533 !args[3]->IsNumber() ||
534 !args[4]->IsFunction()) {
535 args.GetReturnValue().Set(false);
539 bool zoom_in = args[0]->BooleanValue();
540 int pixels_to_move = args[1]->IntegerValue();
541 int anchor_x = args[2]->IntegerValue();
542 int anchor_y = args[3]->IntegerValue();
544 v8::Local<v8::Function> callback_local =
545 v8::Local<v8::Function>::Cast(args[4]);
547 scoped_refptr<CallbackAndContext> callback_and_context =
548 new CallbackAndContext(args.GetIsolate(),
550 context.web_frame()->mainWorldScriptContext());
553 // TODO(nduca): If the render_view_impl is destroyed while the gesture is in
554 // progress, we will leak the callback and context. This needs to be fixed,
556 context.render_view_impl()->BeginPinch(
561 base::Bind(&OnSmoothScrollCompleted,
562 callback_and_context));
564 args.GetReturnValue().Set(true);
567 static void OnSnapshotCompleted(CallbackAndContext* callback_and_context,
568 const gfx::Size& size,
569 const std::vector<unsigned char>& png) {
570 v8::HandleScope scope(callback_and_context->isolate());
571 v8::Handle<v8::Context> context = callback_and_context->GetContext();
572 v8::Context::Scope context_scope(context);
573 WebFrame* frame = WebFrame::frameForContext(context);
576 v8::Handle<v8::Value> result;
578 if(!size.IsEmpty()) {
579 v8::Handle<v8::Object> result_object;
580 result_object = v8::Object::New();
582 result_object->Set(v8::String::New("width"),
583 v8::Number::New(size.width()));
584 result_object->Set(v8::String::New("height"),
585 v8::Number::New(size.height()));
587 std::string base64_png;
588 base::Base64Encode(base::StringPiece(
589 reinterpret_cast<const char*>(&*png.begin()), png.size()),
592 result_object->Set(v8::String::New("data"),
593 v8::String::New(base64_png.c_str(), base64_png.size()));
595 result = result_object;
600 v8::Handle<v8::Value> argv[] = { result };
602 frame->callFunctionEvenIfScriptDisabled(
603 callback_and_context->GetCallback(), v8::Object::New(), 1, argv);
607 static void BeginWindowSnapshotPNG(
608 const v8::FunctionCallbackInfo<v8::Value>& args) {
609 GpuBenchmarkingContext context;
610 if (!context.Init(false))
613 if (!args[0]->IsFunction())
616 v8::Local<v8::Function> callback_local =
617 v8::Local<v8::Function>::Cast(args[0]);
619 scoped_refptr<CallbackAndContext> callback_and_context =
620 new CallbackAndContext(args.GetIsolate(),
622 context.web_frame()->mainWorldScriptContext());
624 context.render_view_impl()->GetWindowSnapshot(
625 base::Bind(&OnSnapshotCompleted, callback_and_context));
628 static void ClearImageCache(
629 const v8::FunctionCallbackInfo<v8::Value>& args) {
630 WebImageCache::clear();
633 static void OnMicroBenchmarkCompleted(
634 CallbackAndContext* callback_and_context,
635 scoped_ptr<base::Value> result) {
636 v8::HandleScope scope(callback_and_context->isolate());
637 v8::Handle<v8::Context> context = callback_and_context->GetContext();
638 v8::Context::Scope context_scope(context);
639 WebFrame* frame = WebFrame::frameForContext(context);
641 scoped_ptr<V8ValueConverter> converter =
642 make_scoped_ptr(V8ValueConverter::create());
643 v8::Handle<v8::Value> value = converter->ToV8Value(result.get(), context);
644 v8::Handle<v8::Value> argv[] = { value };
646 frame->callFunctionEvenIfScriptDisabled(
647 callback_and_context->GetCallback(), v8::Object::New(), 1, argv);
651 static void RunMicroBenchmark(
652 const v8::FunctionCallbackInfo<v8::Value>& args) {
653 GpuBenchmarkingContext context;
654 if (!context.Init(true)) {
655 args.GetReturnValue().Set(false);
659 if (args.Length() != 3 ||
660 !args[0]->IsString() ||
661 !args[1]->IsFunction() ||
662 !args[2]->IsObject()) {
663 args.GetReturnValue().Set(false);
667 v8::Local<v8::Function> callback_local =
668 v8::Local<v8::Function>::Cast(args[1]);
670 scoped_refptr<CallbackAndContext> callback_and_context =
671 new CallbackAndContext(args.GetIsolate(),
673 context.web_frame()->mainWorldScriptContext());
675 scoped_ptr<V8ValueConverter> converter =
676 make_scoped_ptr(V8ValueConverter::create());
677 v8::Handle<v8::Context> v8_context = callback_and_context->GetContext();
678 scoped_ptr<base::Value> value =
679 make_scoped_ptr(converter->FromV8Value(args[2], v8_context));
681 v8::String::Utf8Value benchmark(args[0]);
683 args.GetReturnValue().Set(context.compositor()->ScheduleMicroBenchmark(
684 std::string(*benchmark),
686 base::Bind(&OnMicroBenchmarkCompleted, callback_and_context)));
689 static void HasGpuProcess(const v8::FunctionCallbackInfo<v8::Value>& args) {
690 GpuChannelHost* gpu_channel = RenderThreadImpl::current()->GetGpuChannel();
691 args.GetReturnValue().Set(!!gpu_channel);
695 v8::Extension* GpuBenchmarkingExtension::Get() {
696 return new GpuBenchmarkingWrapper();
699 } // namespace content