// 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/string_number_conversions.h" #include "content/common/browser_rendering_stats.h" #include "content/common/gpu/gpu_rendering_stats.h" #include "content/public/renderer/render_thread.h" #include "content/renderer/all_rendering_benchmarks.h" #include "content/renderer/gpu/render_widget_compositor.h" #include "content/renderer/render_view_impl.h" #include "content/renderer/rendering_benchmark.h" #include "content/renderer/skia_benchmarking_extension.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebImageCache.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebViewBenchmarkSupport.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 WebKit::WebCanvas; using WebKit::WebFrame; using WebKit::WebImageCache; using WebKit::WebPrivatePtr; using WebKit::WebRenderingStatsImpl; using WebKit::WebSize; using WebKit::WebView; using WebKit::WebViewBenchmarkSupport; 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, true, &vector)) { return SkData::NewWithCopy(&vector.front() , vector.size()); } return NULL; } namespace { class SkPictureRecorder : public WebViewBenchmarkSupport::PaintClient { public: explicit SkPictureRecorder(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(); } virtual WebCanvas* willPaint(const WebSize& size) { return picture_.beginRecording(size.width, size.height); } virtual void didPaint(WebCanvas* canvas) { DCHECK(canvas == picture_.getRecordingCanvas()); picture_.endRecording(); // 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_; SkPicture picture_; }; class RenderingStatsEnumerator : public cc::RenderingStats::Enumerator { public: RenderingStatsEnumerator(v8::Handle stats_object) : stats_object(stats_object) { } virtual void AddInt64(const char* name, int64 value) OVERRIDE { stats_object->Set(v8::String::New(name), v8::Number::New(value)); } virtual void AddDouble(const char* name, double value) OVERRIDE { stats_object->Set(v8::String::New(name), v8::Number::New(value)); } virtual void AddInt(const char* name, int value) OVERRIDE { stats_object->Set(v8::String::New(name), v8::Integer::New(value)); } virtual void AddTimeDeltaInSecondsF(const char* name, const base::TimeDelta& value) OVERRIDE { stats_object->Set(v8::String::New(name), v8::Number::New(value.InSecondsF())); } private: 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_.Dispose(); context_.Dispose(); } v8::Isolate* isolate_; v8::Persistent callback_; v8::Persistent context_; DISALLOW_COPY_AND_ASSIGN(CallbackAndContext); }; } // 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.printToSkPicture = function(dirname) {" " native function PrintToSkPicture();" " return PrintToSkPicture(dirname);" "};" "chrome.gpuBenchmarking.smoothScrollBy = " " function(pixels_to_scroll, opt_callback, opt_mouse_event_x," " opt_mouse_event_y) {" " pixels_to_scroll = pixels_to_scroll || 0;" " callback = opt_callback || function() { };" " native function BeginSmoothScroll();" " if (typeof opt_mouse_event_x !== 'undefined' &&" " typeof opt_mouse_event_y !== 'undefined') {" " return BeginSmoothScroll(pixels_to_scroll >= 0, callback," " Math.abs(pixels_to_scroll)," " opt_mouse_event_x, opt_mouse_event_y);" " } else {" " return BeginSmoothScroll(pixels_to_scroll >= 0, callback," " Math.abs(pixels_to_scroll));" " }" "};" "chrome.gpuBenchmarking.runRenderingBenchmarks = function(filter) {" " native function RunRenderingBenchmarks();" " return RunRenderingBenchmarks(filter);" "};" "chrome.gpuBenchmarking.beginWindowSnapshotPNG = function(callback) {" " native function BeginWindowSnapshotPNG();" " BeginWindowSnapshotPNG(callback);" "};" "chrome.gpuBenchmarking.clearImageCache = function() {" " native function ClearImageCache();" " ClearImageCache();" "};") {} virtual v8::Handle GetNativeFunction( v8::Handle name) OVERRIDE { if (name->Equals(v8::String::New("SetNeedsDisplayOnAllLayers"))) return v8::FunctionTemplate::New(SetNeedsDisplayOnAllLayers); if (name->Equals(v8::String::New("SetRasterizeOnlyVisibleContent"))) return v8::FunctionTemplate::New(SetRasterizeOnlyVisibleContent); if (name->Equals(v8::String::New("GetRenderingStats"))) return v8::FunctionTemplate::New(GetRenderingStats); if (name->Equals(v8::String::New("PrintToSkPicture"))) return v8::FunctionTemplate::New(PrintToSkPicture); if (name->Equals(v8::String::New("BeginSmoothScroll"))) return v8::FunctionTemplate::New(BeginSmoothScroll); if (name->Equals(v8::String::New("RunRenderingBenchmarks"))) return v8::FunctionTemplate::New(RunRenderingBenchmarks); if (name->Equals(v8::String::New("BeginWindowSnapshotPNG"))) return v8::FunctionTemplate::New(BeginWindowSnapshotPNG); if (name->Equals(v8::String::New("ClearImageCache"))) return v8::FunctionTemplate::New(ClearImageCache); return v8::Handle(); } static v8::Handle SetNeedsDisplayOnAllLayers( const v8::Arguments& args) { WebFrame* web_frame = WebFrame::frameForCurrentContext(); if (!web_frame) return v8::Undefined(); WebView* web_view = web_frame->view(); if (!web_view) return v8::Undefined(); RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view); if (!render_view_impl) return v8::Undefined(); RenderWidgetCompositor* compositor = render_view_impl->compositor(); if (!compositor) return v8::Undefined(); compositor->SetNeedsDisplayOnAllLayers(); return v8::Undefined(); } static v8::Handle SetRasterizeOnlyVisibleContent( const v8::Arguments& args) { WebFrame* web_frame = WebFrame::frameForCurrentContext(); if (!web_frame) return v8::Undefined(); WebView* web_view = web_frame->view(); if (!web_view) return v8::Undefined(); RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view); if (!render_view_impl) return v8::Undefined(); RenderWidgetCompositor* compositor = render_view_impl->compositor(); if (!compositor) return v8::Undefined(); compositor->SetRasterizeOnlyVisibleContent(); return v8::Undefined(); } static v8::Handle GetRenderingStats(const v8::Arguments& args) { WebFrame* web_frame = WebFrame::frameForCurrentContext(); if (!web_frame) return v8::Undefined(); WebView* web_view = web_frame->view(); if (!web_view) return v8::Undefined(); RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view); if (!render_view_impl) return v8::Undefined(); WebRenderingStatsImpl stats; render_view_impl->GetRenderingStats(stats); content::GpuRenderingStats gpu_stats; render_view_impl->GetGpuRenderingStats(&gpu_stats); BrowserRenderingStats browser_stats; render_view_impl->GetBrowserRenderingStats(&browser_stats); v8::Handle stats_object = v8::Object::New(); RenderingStatsEnumerator enumerator(stats_object); stats.rendering_stats.EnumerateFields(&enumerator); gpu_stats.EnumerateFields(&enumerator); browser_stats.EnumerateFields(&enumerator); return stats_object; } static v8::Handle PrintToSkPicture(const v8::Arguments& args) { if (args.Length() != 1) return v8::Undefined(); v8::String::AsciiValue dirname(args[0]); if (dirname.length() == 0) return v8::Undefined(); WebFrame* web_frame = WebFrame::frameForCurrentContext(); if (!web_frame) return v8::Undefined(); WebView* web_view = web_frame->view(); if (!web_view) return v8::Undefined(); WebViewBenchmarkSupport* benchmark_support = web_view->benchmarkSupport(); if (!benchmark_support) return v8::Undefined(); base::FilePath dirpath( base::FilePath::StringType(*dirname, *dirname + dirname.length())); if (!file_util::CreateDirectory(dirpath) || !file_util::PathIsWritable(dirpath)) { std::string msg("Path is not writable: "); msg.append(dirpath.MaybeAsASCII()); return v8::ThrowException(v8::Exception::Error( v8::String::New(msg.c_str(), msg.length()))); } SkPictureRecorder recorder(dirpath); benchmark_support->paint(&recorder, WebViewBenchmarkSupport::PaintModeEverything); return v8::Undefined(); } static void OnSmoothScrollCompleted( 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 v8::Handle BeginSmoothScroll(const v8::Arguments& args) { WebFrame* web_frame = WebFrame::frameForCurrentContext(); if (!web_frame) return v8::Undefined(); WebView* web_view = web_frame->view(); if (!web_view) return v8::Undefined(); RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view); if (!render_view_impl) return v8::Undefined(); // Account for the 2 optional arguments, mouse_event_x and mouse_event_y. int arglen = args.Length(); if (arglen < 3 || !args[0]->IsBoolean() || !args[1]->IsFunction() || !args[2]->IsNumber()) return v8::False(); bool scroll_down = args[0]->BooleanValue(); v8::Local callback_local = v8::Local::Cast(args[1]); scoped_refptr callback_and_context = new CallbackAndContext(args.GetIsolate(), callback_local, web_frame->mainWorldScriptContext()); int pixels_to_scroll = args[2]->IntegerValue(); int mouse_event_x = 0; int mouse_event_y = 0; if (arglen == 3) { WebKit::WebRect rect = render_view_impl->windowRect(); mouse_event_x = rect.x + rect.width / 2; mouse_event_y = rect.y + rect.height / 2; } else { if (arglen != 5 || !args[3]->IsNumber() || !args[4]->IsNumber()) return v8::False(); mouse_event_x = args[3]->IntegerValue() * web_view->pageScaleFactor(); mouse_event_y = args[4]->IntegerValue() * web_view->pageScaleFactor(); } // 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. render_view_impl->BeginSmoothScroll( scroll_down, base::Bind(&OnSmoothScrollCompleted, callback_and_context), pixels_to_scroll, mouse_event_x, mouse_event_y); return v8::True(); } static v8::Handle RunRenderingBenchmarks( const v8::Arguments& args) { // For our name filter, the argument can be undefined or null to run // all benchmarks, or a string for filtering by name. if (!args.Length() || (!args[0]->IsString() && !(args[0]->IsNull() || args[0]->IsUndefined()))) { return v8::Undefined(); } std::string name_filter; if (args[0]->IsNull() || args[0]->IsUndefined()) { name_filter = ""; } else { char filter[256]; args[0]->ToString()->WriteUtf8(filter, sizeof(filter)-1); name_filter = std::string(filter); } WebFrame* web_frame = WebFrame::frameForCurrentContext(); if (!web_frame) return v8::Undefined(); WebView* web_view = web_frame->view(); if (!web_view) return v8::Undefined(); WebViewBenchmarkSupport* support = web_view->benchmarkSupport(); if (!support) return v8::Undefined(); ScopedVector benchmarks = AllRenderingBenchmarks(); v8::Handle results = v8::Array::New(0); ScopedVector::const_iterator it; for (it = benchmarks.begin(); it != benchmarks.end(); it++) { RenderingBenchmark* benchmark = *it; const std::string& name = benchmark->name(); if (name_filter != "" && std::string::npos == name.find(name_filter)) { continue; } benchmark->SetUp(support); double result = benchmark->Run(support); benchmark->TearDown(support); v8::Handle result_object = v8::Object::New(); result_object->Set(v8::String::New("benchmark", 9), v8::String::New(name.c_str(), -1)); result_object->Set(v8::String::New("result", 6), v8::Number::New(result)); results->Set(results->Length(), result_object); } return results; } static void OnSnapshotCompleted(CallbackAndContext* callback_and_context, const gfx::Size& size, const std::vector& png) { 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) { v8::Handle result; if(!size.IsEmpty()) { v8::Handle result_object; result_object = v8::Object::New(); result_object->Set(v8::String::New("width"), v8::Number::New(size.width())); result_object->Set(v8::String::New("height"), v8::Number::New(size.height())); std::string base64_png; base::Base64Encode(base::StringPiece( reinterpret_cast(&*png.begin()), png.size()), &base64_png); result_object->Set(v8::String::New("data"), v8::String::New(base64_png.c_str(), base64_png.size())); result = result_object; } else { result = v8::Null(); } v8::Handle argv[] = { result }; frame->callFunctionEvenIfScriptDisabled( callback_and_context->GetCallback(), v8::Object::New(), 1, argv); } } static v8::Handle BeginWindowSnapshotPNG( const v8::Arguments& args) { WebFrame* web_frame = WebFrame::frameForCurrentContext(); if (!web_frame) return v8::Undefined(); WebView* web_view = web_frame->view(); if (!web_view) return v8::Undefined(); RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view); if (!render_view_impl) return v8::Undefined(); if (!args[0]->IsFunction()) return v8::Undefined(); v8::Local callback_local = v8::Local::Cast(args[0]); scoped_refptr callback_and_context = new CallbackAndContext(args.GetIsolate(), callback_local, web_frame->mainWorldScriptContext()); render_view_impl->GetWindowSnapshot( base::Bind(&OnSnapshotCompleted, callback_and_context)); return v8::Undefined(); } static v8::Handle ClearImageCache( const v8::Arguments& args) { WebImageCache::clear(); return v8::Undefined(); } }; v8::Extension* GpuBenchmarkingExtension::Get() { return new GpuBenchmarkingWrapper(); } } // namespace content