// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "content/renderer/gpu/gpu_benchmarking_extension.h" #include #include "base/base64.h" #include "base/file_util.h" #include "base/files/file_path.h" #include "base/memory/scoped_vector.h" #include "base/strings/string_number_conversions.h" #include "cc/layers/layer.h" #include "content/common/browser_rendering_stats.h" #include "content/common/gpu/gpu_rendering_stats.h" #include "content/common/input/synthetic_gesture_params.h" #include "content/common/input/synthetic_pinch_gesture_params.h" #include "content/common/input/synthetic_smooth_scroll_gesture_params.h" #include "content/public/renderer/render_thread.h" #include "content/public/renderer/v8_value_converter.h" #include "content/renderer/gpu/render_widget_compositor.h" #include "content/renderer/render_thread_impl.h" #include "content/renderer/render_view_impl.h" #include "content/renderer/skia_benchmarking_extension.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/WebKit/public/web/WebImageCache.h" #include "third_party/WebKit/public/web/WebView.h" #include "third_party/skia/include/core/SkData.h" #include "third_party/skia/include/core/SkGraphics.h" #include "third_party/skia/include/core/SkPicture.h" #include "third_party/skia/include/core/SkPixelRef.h" #include "third_party/skia/include/core/SkStream.h" #include "ui/gfx/codec/png_codec.h" #include "v8/include/v8.h" #include "webkit/renderer/compositor_bindings/web_rendering_stats_impl.h" using blink::WebCanvas; using blink::WebFrame; using blink::WebImageCache; using blink::WebPrivatePtr; using blink::WebRenderingStatsImpl; using blink::WebSize; using blink::WebView; const char kGpuBenchmarkingExtensionName[] = "v8/GpuBenchmarking"; static SkData* EncodeBitmapToData(size_t* offset, const SkBitmap& bm) { SkPixelRef* pr = bm.pixelRef(); if (pr != NULL) { SkData* data = pr->refEncodedData(); if (data != NULL) { *offset = bm.pixelRefOffset(); return data; } } std::vector vector; if (gfx::PNGCodec::EncodeBGRASkBitmap(bm, false, &vector)) { return SkData::NewWithCopy(&vector.front() , vector.size()); } return NULL; } namespace { class SkPictureSerializer { public: explicit SkPictureSerializer(const base::FilePath& dirpath) : dirpath_(dirpath), layer_id_(0) { // Let skia register known effect subclasses. This basically enables // reflection on those subclasses required for picture serialization. content::SkiaBenchmarkingExtension::InitSkGraphics(); } // Recursively serializes the layer tree. // Each layer in the tree is serialized into a separate skp file // in the given directory. void Serialize(const cc::Layer* layer) { const cc::LayerList& children = layer->children(); for (size_t i = 0; i < children.size(); ++i) { Serialize(children[i].get()); } skia::RefPtr picture = layer->GetPicture(); if (!picture) return; // Serialize picture to file. // TODO(alokp): Note that for this to work Chrome needs to be launched with // --no-sandbox command-line flag. Get rid of this limitation. // CRBUG: 139640. std::string filename = "layer_" + base::IntToString(layer_id_++) + ".skp"; std::string filepath = dirpath_.AppendASCII(filename).MaybeAsASCII(); DCHECK(!filepath.empty()); SkFILEWStream file(filepath.c_str()); DCHECK(file.isValid()); picture->serialize(&file, &EncodeBitmapToData); } private: base::FilePath dirpath_; int layer_id_; }; class RenderingStatsEnumerator : public cc::RenderingStats::Enumerator { public: RenderingStatsEnumerator(v8::Isolate* isolate, v8::Handle stats_object) : isolate(isolate), stats_object(stats_object) {} virtual void AddInt64(const char* name, int64 value) OVERRIDE { stats_object->Set(v8::String::NewFromUtf8(isolate, name), v8::Number::New(isolate, value)); } virtual void AddDouble(const char* name, double value) OVERRIDE { stats_object->Set(v8::String::NewFromUtf8(isolate, name), v8::Number::New(isolate, value)); } virtual void AddInt(const char* name, int value) OVERRIDE { stats_object->Set(v8::String::NewFromUtf8(isolate, name), v8::Integer::New(value)); } virtual void AddTimeDeltaInSecondsF(const char* name, const base::TimeDelta& value) OVERRIDE { stats_object->Set(v8::String::NewFromUtf8(isolate, name), v8::Number::New(isolate, value.InSecondsF())); } private: v8::Isolate* isolate; v8::Handle stats_object; }; } // namespace namespace content { namespace { class CallbackAndContext : public base::RefCounted { public: CallbackAndContext(v8::Isolate* isolate, v8::Handle callback, v8::Handle context) : isolate_(isolate) { callback_.Reset(isolate_, callback); context_.Reset(isolate_, context); } v8::Isolate* isolate() { return isolate_; } v8::Handle GetCallback() { return v8::Local::New(isolate_, callback_); } v8::Handle GetContext() { return v8::Local::New(isolate_, context_); } private: friend class base::RefCounted; virtual ~CallbackAndContext() { callback_.Reset(); context_.Reset(); } v8::Isolate* isolate_; v8::Persistent callback_; v8::Persistent context_; DISALLOW_COPY_AND_ASSIGN(CallbackAndContext); }; class GpuBenchmarkingContext { public: GpuBenchmarkingContext() : web_frame_(NULL), web_view_(NULL), render_view_impl_(NULL), compositor_(NULL) {} bool Init(bool init_compositor) { web_frame_ = WebFrame::frameForCurrentContext(); if (!web_frame_) return false; web_view_ = web_frame_->view(); if (!web_view_) { web_frame_ = NULL; return false; } render_view_impl_ = RenderViewImpl::FromWebView(web_view_); if (!render_view_impl_) { web_frame_ = NULL; web_view_ = NULL; return false; } if (!init_compositor) return true; compositor_ = render_view_impl_->compositor(); if (!compositor_) { web_frame_ = NULL; web_view_ = NULL; render_view_impl_ = NULL; return false; } return true; } WebFrame* web_frame() const { DCHECK(web_frame_ != NULL); return web_frame_; } WebView* web_view() const { DCHECK(web_view_ != NULL); return web_view_; } RenderViewImpl* render_view_impl() const { DCHECK(render_view_impl_ != NULL); return render_view_impl_; } RenderWidgetCompositor* compositor() const { DCHECK(compositor_ != NULL); return compositor_; } private: WebFrame* web_frame_; WebView* web_view_; RenderViewImpl* render_view_impl_; RenderWidgetCompositor* compositor_; DISALLOW_COPY_AND_ASSIGN(GpuBenchmarkingContext); }; } // namespace class GpuBenchmarkingWrapper : public v8::Extension { public: GpuBenchmarkingWrapper() : v8::Extension(kGpuBenchmarkingExtensionName, "if (typeof(chrome) == 'undefined') {" " chrome = {};" "};" "if (typeof(chrome.gpuBenchmarking) == 'undefined') {" " chrome.gpuBenchmarking = {};" "};" "chrome.gpuBenchmarking.setNeedsDisplayOnAllLayers = function() {" " native function SetNeedsDisplayOnAllLayers();" " return SetNeedsDisplayOnAllLayers();" "};" "chrome.gpuBenchmarking.setRasterizeOnlyVisibleContent = function() {" " native function SetRasterizeOnlyVisibleContent();" " return SetRasterizeOnlyVisibleContent();" "};" "chrome.gpuBenchmarking.renderingStats = function() {" " native function GetRenderingStats();" " return GetRenderingStats();" "};" "chrome.gpuBenchmarking.gpuRenderingStats = function() {" " native function GetGpuRenderingStats();" " return GetGpuRenderingStats();" "};" "chrome.gpuBenchmarking.printToSkPicture = function(dirname) {" " native function PrintToSkPicture();" " return PrintToSkPicture(dirname);" "};" "chrome.gpuBenchmarking.DEFAULT_INPUT = 0;" "chrome.gpuBenchmarking.TOUCH_INPUT = 1;" "chrome.gpuBenchmarking.MOUSE_INPUT = 2;" "chrome.gpuBenchmarking.smoothScrollBy = " " function(pixels_to_scroll, opt_callback, opt_start_x," " opt_start_y, opt_gesture_source_type," " opt_speed_in_pixels_s) {" " pixels_to_scroll = pixels_to_scroll || 0;" " callback = opt_callback || function() { };" " gesture_source_type = opt_gesture_source_type ||" " chrome.gpuBenchmarking.DEFAULT_INPUT;" " speed_in_pixels_s = opt_speed_in_pixels_s || 800;" " native function BeginSmoothScroll();" " if (typeof opt_mouse_event_x !== 'undefined' &&" " typeof opt_mouse_event_y !== 'undefined') {" " return BeginSmoothScroll(pixels_to_scroll, callback," " gesture_source_type, speed_in_pixels_s," " opt_mouse_event_x, opt_mouse_event_y);" " } else {" " return BeginSmoothScroll(pixels_to_scroll, callback," " gesture_source_type," " speed_in_pixels_s);" " }" "};" "chrome.gpuBenchmarking.smoothScrollBySendsTouch = function() {" " native function SmoothScrollSendsTouch();" " return SmoothScrollSendsTouch();" "};" "chrome.gpuBenchmarking.pinchBy = " " function(zoom_in, pixels_to_cover, anchor_x, anchor_y," " opt_callback, opt_relative_pointer_speed_in_pixels_s) {" " callback = opt_callback || function() { };" " relative_pointer_speed_in_pixels_s =" " opt_relative_pointer_speed_in_pixels_s || 800;" " native function BeginPinch();" " return BeginPinch(zoom_in, pixels_to_cover," " anchor_x, anchor_y, callback," " relative_pointer_speed_in_pixels_s);" "};" "chrome.gpuBenchmarking.beginWindowSnapshotPNG = function(callback) {" " native function BeginWindowSnapshotPNG();" " BeginWindowSnapshotPNG(callback);" "};" "chrome.gpuBenchmarking.clearImageCache = function() {" " native function ClearImageCache();" " ClearImageCache();" "};" "chrome.gpuBenchmarking.runMicroBenchmark =" " function(name, callback, opt_arguments) {" " arguments = opt_arguments || {};" " native function RunMicroBenchmark();" " return RunMicroBenchmark(name, callback, arguments);" "};" "chrome.gpuBenchmarking.hasGpuProcess = function() {" " native function HasGpuProcess();" " return HasGpuProcess();" "};") {} virtual v8::Handle GetNativeFunctionTemplate( v8::Isolate* isolate, v8::Handle name) OVERRIDE { if (name->Equals( v8::String::NewFromUtf8(isolate, "SetNeedsDisplayOnAllLayers"))) return v8::FunctionTemplate::New(isolate, SetNeedsDisplayOnAllLayers); if (name->Equals( v8::String::NewFromUtf8(isolate, "SetRasterizeOnlyVisibleContent"))) return v8::FunctionTemplate::New(isolate, SetRasterizeOnlyVisibleContent); if (name->Equals(v8::String::NewFromUtf8(isolate, "GetRenderingStats"))) return v8::FunctionTemplate::New(isolate, GetRenderingStats); if (name->Equals(v8::String::NewFromUtf8(isolate, "GetGpuRenderingStats"))) return v8::FunctionTemplate::New(isolate, GetGpuRenderingStats); if (name->Equals(v8::String::NewFromUtf8(isolate, "PrintToSkPicture"))) return v8::FunctionTemplate::New(isolate, PrintToSkPicture); if (name->Equals(v8::String::NewFromUtf8(isolate, "BeginSmoothScroll"))) return v8::FunctionTemplate::New(isolate, BeginSmoothScroll); if (name->Equals( v8::String::NewFromUtf8(isolate, "SmoothScrollSendsTouch"))) return v8::FunctionTemplate::New(isolate, SmoothScrollSendsTouch); if (name->Equals(v8::String::NewFromUtf8(isolate, "BeginPinch"))) return v8::FunctionTemplate::New(isolate, BeginPinch); if (name->Equals( v8::String::NewFromUtf8(isolate, "BeginWindowSnapshotPNG"))) return v8::FunctionTemplate::New(isolate, BeginWindowSnapshotPNG); if (name->Equals(v8::String::NewFromUtf8(isolate, "ClearImageCache"))) return v8::FunctionTemplate::New(isolate, ClearImageCache); if (name->Equals(v8::String::NewFromUtf8(isolate, "RunMicroBenchmark"))) return v8::FunctionTemplate::New(isolate, RunMicroBenchmark); if (name->Equals(v8::String::NewFromUtf8(isolate, "HasGpuProcess"))) return v8::FunctionTemplate::New(isolate, HasGpuProcess); return v8::Handle(); } static void SetNeedsDisplayOnAllLayers( const v8::FunctionCallbackInfo& args) { GpuBenchmarkingContext context; if (!context.Init(true)) return; context.compositor()->SetNeedsDisplayOnAllLayers(); } static void SetRasterizeOnlyVisibleContent( const v8::FunctionCallbackInfo& args) { GpuBenchmarkingContext context; if (!context.Init(true)) return; context.compositor()->SetRasterizeOnlyVisibleContent(); } static void GetRenderingStats( const v8::FunctionCallbackInfo& args) { GpuBenchmarkingContext context; if (!context.Init(false)) return; WebRenderingStatsImpl stats; context.render_view_impl()->GetRenderingStats(stats); content::GpuRenderingStats gpu_stats; context.render_view_impl()->GetGpuRenderingStats(&gpu_stats); BrowserRenderingStats browser_stats; context.render_view_impl()->GetBrowserRenderingStats(&browser_stats); v8::Handle stats_object = v8::Object::New(); RenderingStatsEnumerator enumerator(args.GetIsolate(), stats_object); stats.rendering_stats.EnumerateFields(&enumerator); gpu_stats.EnumerateFields(&enumerator); browser_stats.EnumerateFields(&enumerator); args.GetReturnValue().Set(stats_object); } static void GetGpuRenderingStats( const v8::FunctionCallbackInfo& args) { GpuBenchmarkingContext context; if (!context.Init(false)) return; content::GpuRenderingStats gpu_stats; context.render_view_impl()->GetGpuRenderingStats(&gpu_stats); v8::Isolate* isolate = args.GetIsolate(); v8::Handle stats_object = v8::Object::New(isolate); RenderingStatsEnumerator enumerator(isolate, stats_object); gpu_stats.EnumerateFields(&enumerator); args.GetReturnValue().Set(stats_object); } static void PrintToSkPicture( const v8::FunctionCallbackInfo& args) { if (args.Length() != 1) return; v8::String::Utf8Value dirname(args[0]); if (dirname.length() == 0) return; GpuBenchmarkingContext context; if (!context.Init(true)) return; const cc::Layer* root_layer = context.compositor()->GetRootLayer(); if (!root_layer) return; base::FilePath dirpath( base::FilePath::StringType(*dirname, *dirname + dirname.length())); if (!file_util::CreateDirectory(dirpath) || !base::PathIsWritable(dirpath)) { std::string msg("Path is not writable: "); msg.append(dirpath.MaybeAsASCII()); v8::Isolate* isolate = args.GetIsolate(); isolate->ThrowException(v8::Exception::Error(v8::String::NewFromUtf8( isolate, msg.c_str(), v8::String::kNormalString, msg.length()))); return; } SkPictureSerializer serializer(dirpath); serializer.Serialize(root_layer); } static void OnSyntheticGestureCompleted( CallbackAndContext* callback_and_context) { v8::HandleScope scope(callback_and_context->isolate()); v8::Handle context = callback_and_context->GetContext(); v8::Context::Scope context_scope(context); WebFrame* frame = WebFrame::frameForContext(context); if (frame) { frame->callFunctionEvenIfScriptDisabled( callback_and_context->GetCallback(), v8::Object::New(), 0, NULL); } } static void SmoothScrollSendsTouch( const v8::FunctionCallbackInfo& args) { // TODO(epenner): Should other platforms emulate touch events? #if defined(OS_ANDROID) || defined(OS_CHROMEOS) args.GetReturnValue().Set(true); #else args.GetReturnValue().Set(false); #endif } static void BeginSmoothScroll( const v8::FunctionCallbackInfo& args) { GpuBenchmarkingContext context; if (!context.Init(false)) return; // Account for the 2 optional arguments, mouse_event_x and mouse_event_y. int arglen = args.Length(); if (arglen < 4 || !args[0]->IsNumber() || !args[1]->IsFunction() || !args[2]->IsNumber() || !args[3]->IsNumber()) { args.GetReturnValue().Set(false); return; } v8::Local callback_local = v8::Local::Cast(args[1]); scoped_refptr callback_and_context = new CallbackAndContext(args.GetIsolate(), callback_local, context.web_frame()->mainWorldScriptContext()); scoped_ptr gesture_params( new SyntheticSmoothScrollGestureParams); // Convert coordinates from CSS pixels to density independent pixels (DIPs). float page_scale_factor = context.web_view()->pageScaleFactor(); gesture_params->distance = args[0]->IntegerValue() * page_scale_factor; int gesture_source_type = args[2]->IntegerValue(); if (gesture_source_type < 0 || gesture_source_type > SyntheticGestureParams::GESTURE_SOURCE_TYPE_MAX) { args.GetReturnValue().Set(false); return; } gesture_params->gesture_source_type = static_cast( gesture_source_type); gesture_params->speed_in_pixels_s = args[3]->IntegerValue(); if (arglen == 4) { blink::WebRect rect = context.render_view_impl()->windowRect(); gesture_params->anchor.SetPoint(rect.x + rect.width / 2, rect.y + rect.height / 2); } else { if (arglen != 6 || !args[4]->IsNumber() || !args[5]->IsNumber()) { args.GetReturnValue().Set(false); return; } gesture_params->anchor.SetPoint( args[4]->IntegerValue() * page_scale_factor, args[5]->IntegerValue() * page_scale_factor); } // TODO(nduca): If the render_view_impl is destroyed while the gesture is in // progress, we will leak the callback and context. This needs to be fixed, // somehow. context.render_view_impl()->QueueSyntheticGesture( gesture_params.PassAs(), base::Bind(&OnSyntheticGestureCompleted, callback_and_context)); args.GetReturnValue().Set(true); } static void BeginPinch( const v8::FunctionCallbackInfo& args) { GpuBenchmarkingContext context; if (!context.Init(false)) return; int arglen = args.Length(); if (arglen < 6 || !args[0]->IsBoolean() || !args[1]->IsNumber() || !args[2]->IsNumber() || !args[3]->IsNumber() || !args[4]->IsFunction() || !args[5]->IsNumber()) { args.GetReturnValue().Set(false); return; } scoped_ptr gesture_params( new SyntheticPinchGestureParams); // Convert coordinates from CSS pixels to density independent pixels (DIPs). float page_scale_factor = context.web_view()->pageScaleFactor(); gesture_params->zoom_in = args[0]->BooleanValue(); gesture_params->total_num_pixels_covered = args[1]->IntegerValue() * page_scale_factor; gesture_params->anchor.SetPoint( args[2]->IntegerValue() * page_scale_factor, args[3]->IntegerValue() * page_scale_factor); gesture_params->relative_pointer_speed_in_pixels_s = args[5]->IntegerValue(); v8::Local callback_local = v8::Local::Cast(args[4]); scoped_refptr callback_and_context = new CallbackAndContext(args.GetIsolate(), callback_local, context.web_frame()->mainWorldScriptContext()); // TODO(nduca): If the render_view_impl is destroyed while the gesture is in // progress, we will leak the callback and context. This needs to be fixed, // somehow. context.render_view_impl()->QueueSyntheticGesture( gesture_params.PassAs(), base::Bind(&OnSyntheticGestureCompleted, callback_and_context)); args.GetReturnValue().Set(true); } static void OnSnapshotCompleted(CallbackAndContext* callback_and_context, const gfx::Size& size, const std::vector& png) { v8::Isolate* isolate = callback_and_context->isolate(); v8::HandleScope scope(isolate); v8::Handle context = callback_and_context->GetContext(); v8::Context::Scope context_scope(context); WebFrame* frame = WebFrame::frameForContext(context); if (frame) { v8::Handle result; if(!size.IsEmpty()) { v8::Handle result_object; result_object = v8::Object::New(isolate); result_object->Set(v8::String::NewFromUtf8(isolate, "width"), v8::Number::New(isolate, size.width())); result_object->Set(v8::String::NewFromUtf8(isolate, "height"), v8::Number::New(isolate, size.height())); std::string base64_png; base::Base64Encode(base::StringPiece( reinterpret_cast(&*png.begin()), png.size()), &base64_png); result_object->Set(v8::String::NewFromUtf8(isolate, "data"), v8::String::NewFromUtf8(isolate, base64_png.c_str(), v8::String::kNormalString, base64_png.size())); result = result_object; } else { result = v8::Null(isolate); } v8::Handle argv[] = { result }; frame->callFunctionEvenIfScriptDisabled( callback_and_context->GetCallback(), v8::Object::New(), 1, argv); } } static void BeginWindowSnapshotPNG( const v8::FunctionCallbackInfo& args) { GpuBenchmarkingContext context; if (!context.Init(false)) return; if (!args[0]->IsFunction()) return; v8::Local callback_local = v8::Local::Cast(args[0]); scoped_refptr callback_and_context = new CallbackAndContext(args.GetIsolate(), callback_local, context.web_frame()->mainWorldScriptContext()); context.render_view_impl()->GetWindowSnapshot( base::Bind(&OnSnapshotCompleted, callback_and_context)); } static void ClearImageCache( const v8::FunctionCallbackInfo& args) { WebImageCache::clear(); } static void OnMicroBenchmarkCompleted( CallbackAndContext* callback_and_context, scoped_ptr result) { v8::HandleScope scope(callback_and_context->isolate()); v8::Handle context = callback_and_context->GetContext(); v8::Context::Scope context_scope(context); WebFrame* frame = WebFrame::frameForContext(context); if (frame) { scoped_ptr converter = make_scoped_ptr(V8ValueConverter::create()); v8::Handle value = converter->ToV8Value(result.get(), context); v8::Handle argv[] = { value }; frame->callFunctionEvenIfScriptDisabled( callback_and_context->GetCallback(), v8::Object::New(), 1, argv); } } static void RunMicroBenchmark( const v8::FunctionCallbackInfo& args) { GpuBenchmarkingContext context; if (!context.Init(true)) { args.GetReturnValue().Set(false); return; } if (args.Length() != 3 || !args[0]->IsString() || !args[1]->IsFunction() || !args[2]->IsObject()) { args.GetReturnValue().Set(false); return; } v8::Local callback_local = v8::Local::Cast(args[1]); scoped_refptr callback_and_context = new CallbackAndContext(args.GetIsolate(), callback_local, context.web_frame()->mainWorldScriptContext()); scoped_ptr converter = make_scoped_ptr(V8ValueConverter::create()); v8::Handle v8_context = callback_and_context->GetContext(); scoped_ptr value = make_scoped_ptr(converter->FromV8Value(args[2], v8_context)); v8::String::Utf8Value benchmark(args[0]); DCHECK(*benchmark); args.GetReturnValue().Set(context.compositor()->ScheduleMicroBenchmark( std::string(*benchmark), value.Pass(), base::Bind(&OnMicroBenchmarkCompleted, callback_and_context))); } static void HasGpuProcess(const v8::FunctionCallbackInfo& args) { GpuChannelHost* gpu_channel = RenderThreadImpl::current()->GetGpuChannel(); args.GetReturnValue().Set(!!gpu_channel); } }; v8::Extension* GpuBenchmarkingExtension::Get() { return new GpuBenchmarkingWrapper(); } } // namespace content