blob: a274d0d8361f7ef38f6440f0f8fde8c6d916781e [file] [log] [blame]
// 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/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 "content/common/browser_rendering_stats.h"
#include "content/common/gpu/gpu_rendering_stats.h"
#include "content/public/renderer/render_thread.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/public/web/WebFrame.h"
#include "third_party/WebKit/public/web/WebImageCache.h"
#include "third_party/WebKit/public/web/WebView.h"
#include "third_party/WebKit/public/web/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<unsigned char> vector;
if (gfx::PNGCodec::EncodeBGRASkBitmap(bm, false, &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<v8::Object> 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<v8::Object> stats_object;
};
} // namespace
namespace content {
namespace {
class CallbackAndContext : public base::RefCounted<CallbackAndContext> {
public:
CallbackAndContext(v8::Isolate* isolate,
v8::Handle<v8::Function> callback,
v8::Handle<v8::Context> context)
: isolate_(isolate) {
callback_.Reset(isolate_, callback);
context_.Reset(isolate_, context);
}
v8::Isolate* isolate() {
return isolate_;
}
v8::Handle<v8::Function> GetCallback() {
return v8::Local<v8::Function>::New(isolate_, callback_);
}
v8::Handle<v8::Context> GetContext() {
return v8::Local<v8::Context>::New(isolate_, context_);
}
private:
friend class base::RefCounted<CallbackAndContext>;
virtual ~CallbackAndContext() {
callback_.Dispose();
context_.Dispose();
}
v8::Isolate* isolate_;
v8::Persistent<v8::Function> callback_;
v8::Persistent<v8::Context> 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.smoothScrollBySendsTouch = function() {"
" native function SmoothScrollSendsTouch();"
" return SmoothScrollSendsTouch();"
"};"
"chrome.gpuBenchmarking.beginWindowSnapshotPNG = function(callback) {"
" native function BeginWindowSnapshotPNG();"
" BeginWindowSnapshotPNG(callback);"
"};"
"chrome.gpuBenchmarking.clearImageCache = function() {"
" native function ClearImageCache();"
" ClearImageCache();"
"};") {}
virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
v8::Handle<v8::String> 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("SmoothScrollSendsTouch")))
return v8::FunctionTemplate::New(SmoothScrollSendsTouch);
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<v8::FunctionTemplate>();
}
static void SetNeedsDisplayOnAllLayers(
const v8::FunctionCallbackInfo<v8::Value>& args) {
WebFrame* web_frame = WebFrame::frameForCurrentContext();
if (!web_frame)
return;
WebView* web_view = web_frame->view();
if (!web_view)
return;
RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view);
if (!render_view_impl)
return;
RenderWidgetCompositor* compositor = render_view_impl->compositor();
if (!compositor)
return;
compositor->SetNeedsDisplayOnAllLayers();
}
static void SetRasterizeOnlyVisibleContent(
const v8::FunctionCallbackInfo<v8::Value>& args) {
WebFrame* web_frame = WebFrame::frameForCurrentContext();
if (!web_frame)
return;
WebView* web_view = web_frame->view();
if (!web_view)
return;
RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view);
if (!render_view_impl)
return;
RenderWidgetCompositor* compositor = render_view_impl->compositor();
if (!compositor)
return;
compositor->SetRasterizeOnlyVisibleContent();
}
static void GetRenderingStats(
const v8::FunctionCallbackInfo<v8::Value>& args) {
WebFrame* web_frame = WebFrame::frameForCurrentContext();
if (!web_frame)
return;
WebView* web_view = web_frame->view();
if (!web_view)
return;
RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view);
if (!render_view_impl)
return;
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<v8::Object> stats_object = v8::Object::New();
RenderingStatsEnumerator enumerator(stats_object);
stats.rendering_stats.EnumerateFields(&enumerator);
gpu_stats.EnumerateFields(&enumerator);
browser_stats.EnumerateFields(&enumerator);
args.GetReturnValue().Set(stats_object);
}
static void PrintToSkPicture(
const v8::FunctionCallbackInfo<v8::Value>& args) {
if (args.Length() != 1)
return;
v8::String::AsciiValue dirname(args[0]);
if (dirname.length() == 0)
return;
WebFrame* web_frame = WebFrame::frameForCurrentContext();
if (!web_frame)
return;
WebView* web_view = web_frame->view();
if (!web_view)
return;
WebViewBenchmarkSupport* benchmark_support = web_view->benchmarkSupport();
if (!benchmark_support)
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::ThrowException(v8::Exception::Error(
v8::String::New(msg.c_str(), msg.length())));
return;
}
SkPictureRecorder recorder(dirpath);
benchmark_support->paint(&recorder,
WebViewBenchmarkSupport::PaintModeEverything);
}
static void OnSmoothScrollCompleted(
CallbackAndContext* callback_and_context) {
v8::HandleScope scope(callback_and_context->isolate());
v8::Handle<v8::Context> 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<v8::Value>& 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<v8::Value>& args) {
WebFrame* web_frame = WebFrame::frameForCurrentContext();
if (!web_frame)
return;
WebView* web_view = web_frame->view();
if (!web_view)
return;
RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view);
if (!render_view_impl)
return;
// 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()) {
args.GetReturnValue().Set(false);
return;
}
bool scroll_down = args[0]->BooleanValue();
v8::Local<v8::Function> callback_local =
v8::Local<v8::Function>::Cast(args[1]);
scoped_refptr<CallbackAndContext> 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()) {
args.GetReturnValue().Set(false);
return;
}
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);
args.GetReturnValue().Set(true);
}
static void OnSnapshotCompleted(CallbackAndContext* callback_and_context,
const gfx::Size& size,
const std::vector<unsigned char>& png) {
v8::HandleScope scope(callback_and_context->isolate());
v8::Handle<v8::Context> context = callback_and_context->GetContext();
v8::Context::Scope context_scope(context);
WebFrame* frame = WebFrame::frameForContext(context);
if (frame) {
v8::Handle<v8::Value> result;
if(!size.IsEmpty()) {
v8::Handle<v8::Object> 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<const char*>(&*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<v8::Value> argv[] = { result };
frame->callFunctionEvenIfScriptDisabled(
callback_and_context->GetCallback(), v8::Object::New(), 1, argv);
}
}
static void BeginWindowSnapshotPNG(
const v8::FunctionCallbackInfo<v8::Value>& args) {
WebFrame* web_frame = WebFrame::frameForCurrentContext();
if (!web_frame)
return;
WebView* web_view = web_frame->view();
if (!web_view)
return;
RenderViewImpl* render_view_impl = RenderViewImpl::FromWebView(web_view);
if (!render_view_impl)
return;
if (!args[0]->IsFunction())
return;
v8::Local<v8::Function> callback_local =
v8::Local<v8::Function>::Cast(args[0]);
scoped_refptr<CallbackAndContext> callback_and_context =
new CallbackAndContext(args.GetIsolate(),
callback_local,
web_frame->mainWorldScriptContext());
render_view_impl->GetWindowSnapshot(
base::Bind(&OnSnapshotCompleted, callback_and_context));
}
static void ClearImageCache(
const v8::FunctionCallbackInfo<v8::Value>& args) {
WebImageCache::clear();
}
};
v8::Extension* GpuBenchmarkingExtension::Get() {
return new GpuBenchmarkingWrapper();
}
} // namespace content