// 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 <string> #include "base/file_path.h" #include "base/file_util.h" #include "base/memory/scoped_vector.h" #include "base/string_number_conversions.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/render_view_impl.h" #include "content/renderer/rendering_benchmark.h" #include "third_party/skia/include/core/SkGraphics.h" #include "third_party/skia/include/core/SkPicture.h" #include "third_party/skia/include/core/SkStream.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebViewBenchmarkSupport.h" #include "v8/include/v8.h" #include "webkit/compositor_bindings/web_rendering_stats_impl.h" using WebKit::WebCanvas; using WebKit::WebFrame; using WebKit::WebPrivatePtr; using WebKit::WebRenderingStatsImpl; using WebKit::WebSize; using WebKit::WebView; using WebKit::WebViewBenchmarkSupport; const char kGpuBenchmarkingExtensionName[] = "v8/GpuBenchmarking"; namespace { // Always called on the main render thread. // Does not need to be thread-safe. void InitSkGraphics() { static bool init = false; if (!init) { SkGraphics::Init(); init = true; } } class SkPictureRecorder : public WebViewBenchmarkSupport::PaintClient { public: explicit SkPictureRecorder(const FilePath& dirpath) : dirpath_(dirpath), layer_id_(0) { // Let skia register known effect subclasses. This basically enables // reflection on those subclasses required for picture serialization. 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); } private: FilePath dirpath_; int layer_id_; SkPicture picture_; }; class RenderingStatsEnumerator : public cc::RenderingStats::Enumerator { public: RenderingStatsEnumerator(v8::Handle<v8::Object> stats_object) : stats_object(stats_object) { } virtual void AddInt64(const char* name, int64 value) { stats_object->Set(v8::String::New(name), v8::Number::New(value)); } virtual void AddDouble(const char* name, double value) { stats_object->Set(v8::String::New(name), v8::Number::New(value)); } virtual void AddInt(const char* name, int value) { stats_object->Set(v8::String::New(name), v8::Integer::New(value)); } virtual void AddTimeDeltaInSecondsF(const char* name, const base::TimeDelta& value) { stats_object->Set(v8::String::New(name), v8::Number::New(value.InSecondsF())); } private: v8::Handle<v8::Object> stats_object; }; } // namespace namespace content { class GpuBenchmarkingWrapper : public v8::Extension { public: GpuBenchmarkingWrapper() : v8::Extension(kGpuBenchmarkingExtensionName, "if (typeof(chrome) == 'undefined') {" " chrome = {};" "};" "if (typeof(chrome.gpuBenchmarking) == 'undefined') {" " chrome.gpuBenchmarking = {};" "};" "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);" "};") {} virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( v8::Handle<v8::String> name) { 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); return v8::Handle<v8::FunctionTemplate>(); } static v8::Handle<v8::Value> 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); v8::Handle<v8::Object> stats_object = v8::Object::New(); RenderingStatsEnumerator enumerator(stats_object); stats.rendering_stats.EnumerateFields(&enumerator); gpu_stats.EnumerateFields(&enumerator); return stats_object; } static v8::Handle<v8::Value> 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(); FilePath dirpath(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(v8::Persistent<v8::Function> callback, v8::Persistent<v8::Context> context) { v8::HandleScope scope; v8::Context::Scope context_scope(context); WebFrame* frame = WebFrame::frameForContext(context); if (frame) { frame->callFunctionEvenIfScriptDisabled(callback, v8::Object::New(), 0, NULL); } callback.Dispose(); context.Dispose(); } static v8::Handle<v8::Value> 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<v8::Function> callback_local = v8::Local<v8::Function>(v8::Function::Cast(*args[1])); v8::Persistent<v8::Function> callback = v8::Persistent<v8::Function>::New(callback_local); v8::Persistent<v8::Context> context = v8::Persistent<v8::Context>::New(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(); mouse_event_y = args[4]->IntegerValue(); } // 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, context), pixels_to_scroll, mouse_event_x, mouse_event_y); return v8::True(); } static v8::Handle<v8::Value> 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()->WriteAscii(filter, 0, 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<RenderingBenchmark> benchmarks = AllRenderingBenchmarks(); v8::Handle<v8::Array> results = v8::Array::New(0); ScopedVector<RenderingBenchmark>::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<v8::Object> 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; } }; v8::Extension* GpuBenchmarkingExtension::Get() { return new GpuBenchmarkingWrapper(); } } // namespace content