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. 4 5 #include "content/renderer/gpu/gpu_benchmarking_extension.h" 6 7 #include <string> 8 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 "content/common/browser_rendering_stats.h" 15 #include "content/common/gpu/gpu_rendering_stats.h" 16 #include "content/public/renderer/render_thread.h" 17 #include "content/renderer/gpu/render_widget_compositor.h" 18 #include "content/renderer/render_view_impl.h" 19 #include "content/renderer/rendering_benchmark.h" 20 #include "content/renderer/skia_benchmarking_extension.h" 21 #include "third_party/WebKit/public/web/WebFrame.h" 22 #include "third_party/WebKit/public/web/WebImageCache.h" 23 #include "third_party/WebKit/public/web/WebView.h" 24 #include "third_party/WebKit/public/web/WebViewBenchmarkSupport.h" 25 #include "third_party/skia/include/core/SkData.h" 26 #include "third_party/skia/include/core/SkGraphics.h" 27 #include "third_party/skia/include/core/SkPicture.h" 28 #include "third_party/skia/include/core/SkPixelRef.h" 29 #include "third_party/skia/include/core/SkStream.h" 30 #include "ui/gfx/codec/png_codec.h" 31 #include "v8/include/v8.h" 32 #include "webkit/renderer/compositor_bindings/web_rendering_stats_impl.h" 33 34 using WebKit::WebCanvas; 35 using WebKit::WebFrame; 36 using WebKit::WebImageCache; 37 using WebKit::WebPrivatePtr; 38 using WebKit::WebRenderingStatsImpl; 39 using WebKit::WebSize; 40 using WebKit::WebView; 41 using WebKit::WebViewBenchmarkSupport; 42 43 const char kGpuBenchmarkingExtensionName[] = "v8/GpuBenchmarking"; 44 45 static SkData* EncodeBitmapToData(size_t* offset, const SkBitmap& bm) { 46 SkPixelRef* pr = bm.pixelRef(); 47 if (pr != NULL) { 48 SkData* data = pr->refEncodedData(); 49 if (data != NULL) { 50 *offset = bm.pixelRefOffset(); 51 return data; 52 } 53 } 54 std::vector<unsigned char> vector; 55 if (gfx::PNGCodec::EncodeBGRASkBitmap(bm, false, &vector)) { 56 return SkData::NewWithCopy(&vector.front() , vector.size()); 57 } 58 return NULL; 59 } 60 61 namespace { 62 63 class SkPictureRecorder : public WebViewBenchmarkSupport::PaintClient { 64 public: 65 explicit SkPictureRecorder(const base::FilePath& dirpath) 66 : dirpath_(dirpath), 67 layer_id_(0) { 68 // Let skia register known effect subclasses. This basically enables 69 // reflection on those subclasses required for picture serialization. 70 content::SkiaBenchmarkingExtension::InitSkGraphics(); 71 } 72 73 virtual WebCanvas* willPaint(const WebSize& size) { 74 return picture_.beginRecording(size.width, size.height); 75 } 76 77 virtual void didPaint(WebCanvas* canvas) { 78 DCHECK(canvas == picture_.getRecordingCanvas()); 79 picture_.endRecording(); 80 // Serialize picture to file. 81 // TODO(alokp): Note that for this to work Chrome needs to be launched with 82 // --no-sandbox command-line flag. Get rid of this limitation. 83 // CRBUG: 139640. 84 std::string filename = "layer_" + base::IntToString(layer_id_++) + ".skp"; 85 std::string filepath = dirpath_.AppendASCII(filename).MaybeAsASCII(); 86 DCHECK(!filepath.empty()); 87 SkFILEWStream file(filepath.c_str()); 88 DCHECK(file.isValid()); 89 picture_.serialize(&file, &EncodeBitmapToData); 90 } 91 92 private: 93 base::FilePath dirpath_; 94 int layer_id_; 95 SkPicture picture_; 96 }; 97 98 class RenderingStatsEnumerator : public cc::RenderingStats::Enumerator { 99 public: 100 RenderingStatsEnumerator(v8::Handle<v8::Object> stats_object) 101 : stats_object(stats_object) { } 102 103 virtual void AddInt64(const char* name, int64 value) OVERRIDE { 104 stats_object->Set(v8::String::New(name), v8::Number::New(value)); 105 } 106 107 virtual void AddDouble(const char* name, double value) OVERRIDE { 108 stats_object->Set(v8::String::New(name), v8::Number::New(value)); 109 } 110 111 virtual void AddInt(const char* name, int value) OVERRIDE { 112 stats_object->Set(v8::String::New(name), v8::Integer::New(value)); 113 } 114 115 virtual void AddTimeDeltaInSecondsF(const char* name, 116 const base::TimeDelta& value) OVERRIDE { 117 stats_object->Set(v8::String::New(name), 118 v8::Number::New(value.InSecondsF())); 119 } 120 121 private: 122 v8::Handle<v8::Object> stats_object; 123 }; 124 125 } // namespace 126 127 namespace content { 128 129 namespace { 130 131 class CallbackAndContext : public base::RefCounted<CallbackAndContext> { 132 public: 133 CallbackAndContext(v8::Isolate* isolate, 134 v8::Handle<v8::Function> callback, 135 v8::Handle<v8::Context> context) 136 : isolate_(isolate) { 137 callback_.Reset(isolate_, callback); 138 context_.Reset(isolate_, context); 139 } 140 141 v8::Isolate* isolate() { 142 return isolate_; 143 } 144 145 v8::Handle<v8::Function> GetCallback() { 146 return v8::Local<v8::Function>::New(isolate_, callback_); 147 } 148 149 v8::Handle<v8::Context> GetContext() { 150 return v8::Local<v8::Context>::New(isolate_, context_); 151 } 152 153 private: 154 friend class base::RefCounted<CallbackAndContext>; 155 156 virtual ~CallbackAndContext() { 157 callback_.Dispose(); 158 context_.Dispose(); 159 } 160 161 v8::Isolate* isolate_; 162 v8::Persistent<v8::Function> callback_; 163 v8::Persistent<v8::Context> context_; 164 DISALLOW_COPY_AND_ASSIGN(CallbackAndContext); 165 }; 166 167 } // namespace 168 169 class GpuBenchmarkingWrapper : public v8::Extension { 170 public: 171 GpuBenchmarkingWrapper() : 172 v8::Extension(kGpuBenchmarkingExtensionName, 173 "if (typeof(chrome) == 'undefined') {" 174 " chrome = {};" 175 "};" 176 "if (typeof(chrome.gpuBenchmarking) == 'undefined') {" 177 " chrome.gpuBenchmarking = {};" 178 "};" 179 "chrome.gpuBenchmarking.setNeedsDisplayOnAllLayers = function() {" 180 " native function SetNeedsDisplayOnAllLayers();" 181 " return SetNeedsDisplayOnAllLayers();" 182 "};" 183 "chrome.gpuBenchmarking.setRasterizeOnlyVisibleContent = function() {" 184 " native function SetRasterizeOnlyVisibleContent();" 185 " return SetRasterizeOnlyVisibleContent();" 186 "};" 187 "chrome.gpuBenchmarking.renderingStats = function() {" 188 " native function GetRenderingStats();" 189 " return GetRenderingStats();" 190 "};" 191 "chrome.gpuBenchmarking.printToSkPicture = function(dirname) {" 192 " native function PrintToSkPicture();" 193 " return PrintToSkPicture(dirname);" 194 "};" 195 "chrome.gpuBenchmarking.smoothScrollBy = " 196 " function(pixels_to_scroll, opt_callback, opt_mouse_event_x," 197 " opt_mouse_event_y) {" 198 " pixels_to_scroll = pixels_to_scroll || 0;" 199 " callback = opt_callback || function() { };" 200 " native function BeginSmoothScroll();" 201 " if (typeof opt_mouse_event_x !== 'undefined' &&" 202 " typeof opt_mouse_event_y !== 'undefined') {" 203 " return BeginSmoothScroll(pixels_to_scroll >= 0, callback," 204 " Math.abs(pixels_to_scroll)," 205 " opt_mouse_event_x, opt_mouse_event_y);" 206 " } else {" 207 " return BeginSmoothScroll(pixels_to_scroll >= 0, callback," 208 " Math.abs(pixels_to_scroll));" 209 " }" 210 "};" 211 "chrome.gpuBenchmarking.smoothScrollBySendsTouch = function() {" 212 " native function SmoothScrollSendsTouch();" 213 " return SmoothScrollSendsTouch();" 214 "};" 215 "chrome.gpuBenchmarking.beginWindowSnapshotPNG = function(callback) {" 216 " native function BeginWindowSnapshotPNG();" 217 " BeginWindowSnapshotPNG(callback);" 218 "};" 219 "chrome.gpuBenchmarking.clearImageCache = function() {" 220 " native function ClearImageCache();" 221 " ClearImageCache();" 222 "};") {} 223 224 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( 225 v8::Handle<v8::String> name) OVERRIDE { 226 if (name->Equals(v8::String::New("SetNeedsDisplayOnAllLayers"))) 227 return v8::FunctionTemplate::New(SetNeedsDisplayOnAllLayers); 228 if (name->Equals(v8::String::New("SetRasterizeOnlyVisibleContent"))) 229 return v8::FunctionTemplate::New(SetRasterizeOnlyVisibleContent); 230 if (name->Equals(v8::String::New("GetRenderingStats"))) 231 return v8::FunctionTemplate::New(GetRenderingStats); 232 if (name->Equals(v8::String::New("PrintToSkPicture"))) 233 return v8::FunctionTemplate::New(PrintToSkPicture); 234 if (name->Equals(v8::String::New("BeginSmoothScroll"))) 235 return v8::FunctionTemplate::New(BeginSmoothScroll); 236 if (name->Equals(v8::String::New("SmoothScrollSendsTouch"))) 237 return v8::FunctionTemplate::New(SmoothScrollSendsTouch); 238 if (name->Equals(v8::String::New("BeginWindowSnapshotPNG"))) 239 return v8::FunctionTemplate::New(BeginWindowSnapshotPNG); 240 if (name->Equals(v8::String::New("ClearImageCache"))) 241 return v8::FunctionTemplate::New(ClearImageCache); 242 243 return v8::Handle<v8::FunctionTemplate>(); 244 } 245 246 static void SetNeedsDisplayOnAllLayers( 247 const v8::FunctionCallbackInfo<v8::Value>& args) { 248 WebFrame* web_frame = WebFrame::frameForCurrentContext(); 249 if (!web_frame) 250 return; 251 252 WebView* web_view = web_frame->view(); 253 if (!web_view) 254 return; 255 256 RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view); 257 if (!render_view_impl) 258 return; 259 260 RenderWidgetCompositor* compositor = render_view_impl->compositor(); 261 if (!compositor) 262 return; 263 264 compositor->SetNeedsDisplayOnAllLayers(); 265 } 266 267 static void SetRasterizeOnlyVisibleContent( 268 const v8::FunctionCallbackInfo<v8::Value>& args) { 269 WebFrame* web_frame = WebFrame::frameForCurrentContext(); 270 if (!web_frame) 271 return; 272 273 WebView* web_view = web_frame->view(); 274 if (!web_view) 275 return; 276 277 RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view); 278 if (!render_view_impl) 279 return; 280 281 RenderWidgetCompositor* compositor = render_view_impl->compositor(); 282 if (!compositor) 283 return; 284 285 compositor->SetRasterizeOnlyVisibleContent(); 286 } 287 288 static void GetRenderingStats( 289 const v8::FunctionCallbackInfo<v8::Value>& args) { 290 291 WebFrame* web_frame = WebFrame::frameForCurrentContext(); 292 if (!web_frame) 293 return; 294 295 WebView* web_view = web_frame->view(); 296 if (!web_view) 297 return; 298 299 RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view); 300 if (!render_view_impl) 301 return; 302 303 WebRenderingStatsImpl stats; 304 render_view_impl->GetRenderingStats(stats); 305 306 content::GpuRenderingStats gpu_stats; 307 render_view_impl->GetGpuRenderingStats(&gpu_stats); 308 BrowserRenderingStats browser_stats; 309 render_view_impl->GetBrowserRenderingStats(&browser_stats); 310 v8::Handle<v8::Object> stats_object = v8::Object::New(); 311 312 RenderingStatsEnumerator enumerator(stats_object); 313 stats.rendering_stats.EnumerateFields(&enumerator); 314 gpu_stats.EnumerateFields(&enumerator); 315 browser_stats.EnumerateFields(&enumerator); 316 317 args.GetReturnValue().Set(stats_object); 318 } 319 320 static void PrintToSkPicture( 321 const v8::FunctionCallbackInfo<v8::Value>& args) { 322 if (args.Length() != 1) 323 return; 324 325 v8::String::AsciiValue dirname(args[0]); 326 if (dirname.length() == 0) 327 return; 328 329 WebFrame* web_frame = WebFrame::frameForCurrentContext(); 330 if (!web_frame) 331 return; 332 333 WebView* web_view = web_frame->view(); 334 if (!web_view) 335 return; 336 337 WebViewBenchmarkSupport* benchmark_support = web_view->benchmarkSupport(); 338 if (!benchmark_support) 339 return; 340 341 base::FilePath dirpath( 342 base::FilePath::StringType(*dirname, *dirname + dirname.length())); 343 if (!file_util::CreateDirectory(dirpath) || 344 !base::PathIsWritable(dirpath)) { 345 std::string msg("Path is not writable: "); 346 msg.append(dirpath.MaybeAsASCII()); 347 v8::ThrowException(v8::Exception::Error( 348 v8::String::New(msg.c_str(), msg.length()))); 349 return; 350 } 351 352 SkPictureRecorder recorder(dirpath); 353 benchmark_support->paint(&recorder, 354 WebViewBenchmarkSupport::PaintModeEverything); 355 } 356 357 static void OnSmoothScrollCompleted( 358 CallbackAndContext* callback_and_context) { 359 v8::HandleScope scope(callback_and_context->isolate()); 360 v8::Handle<v8::Context> context = callback_and_context->GetContext(); 361 v8::Context::Scope context_scope(context); 362 WebFrame* frame = WebFrame::frameForContext(context); 363 if (frame) { 364 frame->callFunctionEvenIfScriptDisabled( 365 callback_and_context->GetCallback(), v8::Object::New(), 0, NULL); 366 } 367 } 368 369 static void SmoothScrollSendsTouch( 370 const v8::FunctionCallbackInfo<v8::Value>& args) { 371 // TODO(epenner): Should other platforms emulate touch events? 372 #if defined(OS_ANDROID) || defined(OS_CHROMEOS) 373 args.GetReturnValue().Set(true); 374 #else 375 args.GetReturnValue().Set(false); 376 #endif 377 } 378 379 static void BeginSmoothScroll( 380 const v8::FunctionCallbackInfo<v8::Value>& args) { 381 WebFrame* web_frame = WebFrame::frameForCurrentContext(); 382 if (!web_frame) 383 return; 384 385 WebView* web_view = web_frame->view(); 386 if (!web_view) 387 return; 388 389 RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view); 390 if (!render_view_impl) 391 return; 392 393 // Account for the 2 optional arguments, mouse_event_x and mouse_event_y. 394 int arglen = args.Length(); 395 if (arglen < 3 || 396 !args[0]->IsBoolean() || 397 !args[1]->IsFunction() || 398 !args[2]->IsNumber()) { 399 args.GetReturnValue().Set(false); 400 return; 401 } 402 403 bool scroll_down = args[0]->BooleanValue(); 404 v8::Local<v8::Function> callback_local = 405 v8::Local<v8::Function>::Cast(args[1]); 406 407 scoped_refptr<CallbackAndContext> callback_and_context = 408 new CallbackAndContext(args.GetIsolate(), 409 callback_local, 410 web_frame->mainWorldScriptContext()); 411 412 int pixels_to_scroll = args[2]->IntegerValue(); 413 414 int mouse_event_x = 0; 415 int mouse_event_y = 0; 416 417 if (arglen == 3) { 418 WebKit::WebRect rect = render_view_impl->windowRect(); 419 mouse_event_x = rect.x + rect.width / 2; 420 mouse_event_y = rect.y + rect.height / 2; 421 } else { 422 if (arglen != 5 || 423 !args[3]->IsNumber() || 424 !args[4]->IsNumber()) { 425 args.GetReturnValue().Set(false); 426 return; 427 } 428 429 mouse_event_x = args[3]->IntegerValue() * web_view->pageScaleFactor(); 430 mouse_event_y = args[4]->IntegerValue() * web_view->pageScaleFactor(); 431 } 432 433 // TODO(nduca): If the render_view_impl is destroyed while the gesture is in 434 // progress, we will leak the callback and context. This needs to be fixed, 435 // somehow. 436 render_view_impl->BeginSmoothScroll( 437 scroll_down, 438 base::Bind(&OnSmoothScrollCompleted, 439 callback_and_context), 440 pixels_to_scroll, 441 mouse_event_x, 442 mouse_event_y); 443 444 args.GetReturnValue().Set(true); 445 } 446 447 static void OnSnapshotCompleted(CallbackAndContext* callback_and_context, 448 const gfx::Size& size, 449 const std::vector<unsigned char>& png) { 450 v8::HandleScope scope(callback_and_context->isolate()); 451 v8::Handle<v8::Context> context = callback_and_context->GetContext(); 452 v8::Context::Scope context_scope(context); 453 WebFrame* frame = WebFrame::frameForContext(context); 454 if (frame) { 455 456 v8::Handle<v8::Value> result; 457 458 if(!size.IsEmpty()) { 459 v8::Handle<v8::Object> result_object; 460 result_object = v8::Object::New(); 461 462 result_object->Set(v8::String::New("width"), 463 v8::Number::New(size.width())); 464 result_object->Set(v8::String::New("height"), 465 v8::Number::New(size.height())); 466 467 std::string base64_png; 468 base::Base64Encode(base::StringPiece( 469 reinterpret_cast<const char*>(&*png.begin()), png.size()), 470 &base64_png); 471 472 result_object->Set(v8::String::New("data"), 473 v8::String::New(base64_png.c_str(), base64_png.size())); 474 475 result = result_object; 476 } else { 477 result = v8::Null(); 478 } 479 480 v8::Handle<v8::Value> argv[] = { result }; 481 482 frame->callFunctionEvenIfScriptDisabled( 483 callback_and_context->GetCallback(), v8::Object::New(), 1, argv); 484 } 485 } 486 487 static void BeginWindowSnapshotPNG( 488 const v8::FunctionCallbackInfo<v8::Value>& args) { 489 WebFrame* web_frame = WebFrame::frameForCurrentContext(); 490 if (!web_frame) 491 return; 492 493 WebView* web_view = web_frame->view(); 494 if (!web_view) 495 return; 496 497 RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view); 498 if (!render_view_impl) 499 return; 500 501 if (!args[0]->IsFunction()) 502 return; 503 504 v8::Local<v8::Function> callback_local = 505 v8::Local<v8::Function>::Cast(args[0]); 506 507 scoped_refptr<CallbackAndContext> callback_and_context = 508 new CallbackAndContext(args.GetIsolate(), 509 callback_local, 510 web_frame->mainWorldScriptContext()); 511 512 render_view_impl->GetWindowSnapshot( 513 base::Bind(&OnSnapshotCompleted, callback_and_context)); 514 } 515 516 static void ClearImageCache( 517 const v8::FunctionCallbackInfo<v8::Value>& args) { 518 WebImageCache::clear(); 519 } 520 }; 521 522 v8::Extension* GpuBenchmarkingExtension::Get() { 523 return new GpuBenchmarkingWrapper(); 524 } 525 526 } // namespace content 527