Commit bd1200bb authored by jbates@chromium.org's avatar jbates@chromium.org

fix mac frame-rate regression for non-threaded GPU compositing

This change looks weird - sorry about that. On Mac, setNeedsDisplay-driven drawRect calls appear to be implemented with a software timer that is slightly slower than vsync. So in order to render aligned with Vsync we have to either use CVDisplayLink or call CGLFlushDrawable immediately when new frames arrive at the browser RWHVMac. CVDisplayLink adds a frame of latency, so we don't want to use that for rendering. So, we call CGLFlushDrawable immediately.

Calling CGLFlushDrawable is fine except when the window becomes non-visible, during which undocumented behavior kicks in: CGLFlushDrawable returns immediately, no longer throttled to vsync. So, we have the unfortunate task of manually throttling CGLFlushDrawable so that chrome doesn't spin while the window is hidden.

The implementation of manual throttling is to use CVDisplayLink to compute vsync frame counts, and compare that to the flush (swap) counts. If the swap count gets more than frames ahead of vsync, we sleep for 1 frame interval to simulate blocking on vsync.

NOTE: this code path is only for the non-threaded accelerated compositing path. It will not be triggered for the threaded compositing path because we won't render frames at faster than vsync unless there's a bug in the compositor thread scheduling logic.

BUG=139416

Review URL: https://chromiumcodereview.appspot.com/10828104

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@150056 0039d316-1c4b-4281-b951-d872f2087c98
parent 20f9f161
......@@ -6,10 +6,14 @@
#define CONTENT_BROWSER_RENDERER_HOST_ACCELERATED_COMPOSITING_VIEW_MAC_H
#import <Cocoa/Cocoa.h>
#import <QuartzCore/CVDisplayLink.h>
#include <QuartzCore/QuartzCore.h>
#include "base/mac/scoped_cftyperef.h"
#include "base/memory/scoped_nsobject.h"
#include "base/synchronization/lock.h"
#include "base/time.h"
#include "base/timer.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/gfx/size.h"
......@@ -69,7 +73,19 @@ class CompositingIOSurfaceMac {
bool is_vsync_disabled() const { return is_vsync_disabled_; }
// Get vsync scheduling parameters.
void GetVSyncParameters(base::TimeTicks* timebase,
uint32* interval_numerator,
uint32* interval_denominator);
private:
friend CVReturn DisplayLinkCallback(CVDisplayLinkRef,
const CVTimeStamp*,
const CVTimeStamp*,
CVOptionFlags,
CVOptionFlags*,
void*);
// Vertex structure for use in glDraw calls.
struct SurfaceVertex {
SurfaceVertex() : x_(0.0f), y_(0.0f), tx_(0.0f), ty_(0.0f) { }
......@@ -130,7 +146,8 @@ class CompositingIOSurfaceMac {
GLuint shader_program_blit_rgb,
GLint blit_rgb_sampler_location,
GLuint shader_program_white,
bool is_vsync_disabled);
bool is_vsync_disabled,
CVDisplayLinkRef display_link);
// Returns true if IOSurface is ready to render. False otherwise.
bool MapIOSurfaceToTexture(uint64 io_surface_handle);
......@@ -139,6 +156,19 @@ class CompositingIOSurfaceMac {
void DrawQuad(const SurfaceQuad& quad);
// Called on display-link thread.
void DisplayLinkTick(CVDisplayLinkRef display_link,
const CVTimeStamp* output_time);
void CalculateVsyncParametersLockHeld(const CVTimeStamp* time);
// Prevent from spinning on CGLFlushDrawable when it fails to throttle to
// VSync frequency.
void RateLimitDraws();
void StartOrContinueDisplayLink();
void StopDisplayLink();
// Cached pointer to IOSurfaceSupport Singleton.
IOSurfaceSupport* io_surface_support_;
......@@ -169,6 +199,24 @@ class CompositingIOSurfaceMac {
SurfaceQuad quad_;
bool is_vsync_disabled_;
// CVDisplayLink for querying Vsync timing info and throttling swaps.
CVDisplayLinkRef display_link_;
// Timer for stopping display link after a timeout with no swaps.
base::DelayTimer<CompositingIOSurfaceMac> display_link_stop_timer_;
// Lock for sharing data between UI thread and display-link thread.
base::Lock lock_;
// Counts for throttling swaps.
int64 vsync_count_;
int64 swap_count_;
// Vsync timing data.
base::TimeTicks vsync_timebase_;
uint32 vsync_interval_numerator_;
uint32 vsync_interval_denominator_;
};
} // namespace content
......
......@@ -9,6 +9,7 @@
#include "base/command_line.h"
#include "base/debug/trace_event.h"
#include "base/threading/platform_thread.h"
#include "content/browser/renderer_host/render_widget_host_view_mac.h"
#include "content/public/browser/browser_thread.h"
#include "gpu/command_buffer/service/gpu_switches.h"
......@@ -32,26 +33,26 @@
namespace content {
namespace {
static const char* g_vertex_shader_blit_rgb = SHADER_STRING_GLSL(
const char* g_vertex_shader_blit_rgb = SHADER_STRING_GLSL(
varying vec2 texture_coordinate;
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
texture_coordinate = vec2(gl_MultiTexCoord0);
});
static const char* g_fragment_shader_blit_rgb = SHADER_STRING_GLSL(
const char* g_fragment_shader_blit_rgb = SHADER_STRING_GLSL(
varying vec2 texture_coordinate;
uniform sampler2DRect texture;
void main() {
gl_FragColor = vec4(texture2DRect(texture, texture_coordinate).rgb, 1.0);
});
static const char* g_vertex_shader_white = SHADER_STRING_GLSL(
const char* g_vertex_shader_white = SHADER_STRING_GLSL(
void main() {
gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
});
static const char* g_fragment_shader_white = SHADER_STRING_GLSL(
const char* g_fragment_shader_white = SHADER_STRING_GLSL(
void main() {
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
});
......@@ -106,6 +107,18 @@ GLuint CreateProgramGLSL(const char* vertex_shader_str,
} // namespace
CVReturn DisplayLinkCallback(CVDisplayLinkRef display_link,
const CVTimeStamp* now,
const CVTimeStamp* output_time,
CVOptionFlags flags_in,
CVOptionFlags* flags_out,
void* context) {
CompositingIOSurfaceMac* surface =
static_cast<CompositingIOSurfaceMac*>(context);
surface->DisplayLinkTick(display_link, output_time);
return kCVReturnSuccess;
}
CompositingIOSurfaceMac* CompositingIOSurfaceMac::Create() {
TRACE_EVENT0("browser", "CompositingIOSurfaceMac::Create");
IOSurfaceSupport* io_surface_support = IOSurfaceSupport::Initialize();
......@@ -170,12 +183,33 @@ CompositingIOSurfaceMac* CompositingIOSurfaceMac::Create() {
return NULL;
}
CVDisplayLinkRef display_link;
CVReturn ret = CVDisplayLinkCreateWithActiveCGDisplays(&display_link);
if (ret != kCVReturnSuccess) {
LOG(ERROR) << "CVDisplayLinkCreateWithActiveCGDisplays failed: " << ret;
return NULL;
}
// Set the display link for the current renderer
CGLPixelFormatObj cglPixelFormat =
(CGLPixelFormatObj)[glPixelFormat CGLPixelFormatObj];
ret = CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(display_link,
cglContext,
cglPixelFormat);
if (ret != kCVReturnSuccess) {
CVDisplayLinkRelease(display_link);
LOG(ERROR) << "CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext failed: "
<< ret;
return NULL;
}
return new CompositingIOSurfaceMac(io_surface_support, glContext.release(),
cglContext,
shader_program_blit_rgb,
blit_rgb_sampler_location,
shader_program_white,
is_vsync_disabled);
is_vsync_disabled,
display_link);
}
CompositingIOSurfaceMac::CompositingIOSurfaceMac(
......@@ -185,7 +219,8 @@ CompositingIOSurfaceMac::CompositingIOSurfaceMac(
GLuint shader_program_blit_rgb,
GLint blit_rgb_sampler_location,
GLuint shader_program_white,
bool is_vsync_disabled)
bool is_vsync_disabled,
CVDisplayLinkRef display_link)
: io_surface_support_(io_surface_support),
glContext_(glContext),
cglContext_(cglContext),
......@@ -194,10 +229,45 @@ CompositingIOSurfaceMac::CompositingIOSurfaceMac(
shader_program_blit_rgb_(shader_program_blit_rgb),
blit_rgb_sampler_location_(blit_rgb_sampler_location),
shader_program_white_(shader_program_white),
is_vsync_disabled_(is_vsync_disabled) {
is_vsync_disabled_(is_vsync_disabled),
display_link_(display_link),
display_link_stop_timer_(FROM_HERE, base::TimeDelta::FromSeconds(1),
this, &CompositingIOSurfaceMac::StopDisplayLink),
vsync_count_(0),
swap_count_(0),
vsync_interval_numerator_(0),
vsync_interval_denominator_(0) {
CVReturn ret = CVDisplayLinkSetOutputCallback(display_link_,
&DisplayLinkCallback, this);
DCHECK(ret == kCVReturnSuccess)
<< "CVDisplayLinkSetOutputCallback failed: " << ret;
StartOrContinueDisplayLink();
CVTimeStamp cv_time;
ret = CVDisplayLinkGetCurrentTime(display_link_, &cv_time);
DCHECK(ret == kCVReturnSuccess)
<< "CVDisplayLinkGetCurrentTime failed: " << ret;
{
base::AutoLock lock(lock_);
CalculateVsyncParametersLockHeld(&cv_time);
}
// Stop display link for now, it will be started when needed during Draw.
StopDisplayLink();
}
void CompositingIOSurfaceMac::GetVSyncParameters(base::TimeTicks* timebase,
uint32* interval_numerator,
uint32* interval_denominator) {
*timebase = vsync_timebase_;
*interval_numerator = vsync_interval_numerator_;
*interval_denominator = vsync_interval_denominator_;
}
CompositingIOSurfaceMac::~CompositingIOSurfaceMac() {
CVDisplayLinkRelease(display_link_);
UnrefIOSurface();
}
......@@ -301,7 +371,15 @@ void CompositingIOSurfaceMac::DrawIOSurface(NSView* view, float scale_factor) {
CGLFlushDrawable(cglContext_);
// For latency_tests.cc:
UNSHIPPED_TRACE_EVENT_INSTANT0("test_gpu", "CompositorSwapBuffersComplete");
CGLSetCurrentContext(0);
StartOrContinueDisplayLink();
if (!is_vsync_disabled_)
RateLimitDraws();
}
bool CompositingIOSurfaceMac::CopyTo(
......@@ -473,4 +551,64 @@ void CompositingIOSurfaceMac::ClearDrawable() {
UnrefIOSurface();
}
void CompositingIOSurfaceMac::DisplayLinkTick(CVDisplayLinkRef display_link,
const CVTimeStamp* output_time) {
base::AutoLock lock(lock_);
// Increment vsync_count but don't let it get ahead of swap_count.
vsync_count_ = std::min(vsync_count_ + 1, swap_count_);
CalculateVsyncParametersLockHeld(output_time);
}
void CompositingIOSurfaceMac::CalculateVsyncParametersLockHeld(
const CVTimeStamp* time) {
vsync_interval_numerator_ = static_cast<uint32>(time->videoRefreshPeriod);
vsync_interval_denominator_ = time->videoTimeScale;
// Verify that videoRefreshPeriod is 32 bits.
DCHECK((time->videoRefreshPeriod & ~0xffffFFFFull) == 0ull);
vsync_timebase_ =
base::TimeTicks::FromInternalValue(time->hostTime / 1000);
}
void CompositingIOSurfaceMac::RateLimitDraws() {
int64 vsync_count;
int64 swap_count;
{
base::AutoLock lock(lock_);
vsync_count = vsync_count_;
swap_count = ++swap_count_;
}
// It's OK for swap_count to get 2 ahead of vsync_count, but any more
// indicates that it has become unthrottled. This happens when, for example,
// the window is obscured by another opaque window.
if (swap_count > vsync_count + 2) {
TRACE_EVENT0("gpu", "CompositingIOSurfaceMac::RateLimitDraws");
// Sleep for one vsync interval. This will prevent spinning while the window
// is not visible, but will also allow quick recovery when the window
// becomes visible again.
int64 sleep_us = 16666; // default to 60hz if display link API fails.
if (vsync_interval_denominator_ > 0) {
sleep_us = (static_cast<int64>(vsync_interval_numerator_) * 1000000) /
vsync_interval_denominator_;
}
base::PlatformThread::Sleep(base::TimeDelta::FromMicroseconds(sleep_us));
}
}
void CompositingIOSurfaceMac::StartOrContinueDisplayLink() {
if (!CVDisplayLinkIsRunning(display_link_)) {
vsync_count_ = swap_count_ = 0;
CVDisplayLinkStart(display_link_);
}
display_link_stop_timer_.Reset();
}
void CompositingIOSurfaceMac::StopDisplayLink() {
if (CVDisplayLinkIsRunning(display_link_))
CVDisplayLinkStop(display_link_);
}
} // namespace content
......@@ -155,9 +155,6 @@ namespace {
// Maximum number of characters we allow in a tooltip.
const size_t kMaxTooltipLength = 1024;
// Invalidation NSRect to trigger a drawRect on BuffersSwapped.
const NSRect kGpuSwapBuffersDirtyRect = { {0, 0}, {1, 1} };
// TODO(suzhe): Upstream this function.
WebKit::WebColor WebColorFromNSColor(NSColor *color) {
CGFloat r, g, b, a;
......@@ -991,22 +988,10 @@ bool RenderWidgetHostViewMac::CompositorSwapBuffers(uint64 surface_handle) {
// No need to draw the surface if we are inside a drawRect. It will be done
// later.
if (!about_to_validate_and_paint_) {
// Trigger a drawRect, but don't invalidate the whole window because it
// is expensive to clear it with transparency to expose the GL underneath.
[cocoa_view_ setNeedsDisplayInRect:kGpuSwapBuffersDirtyRect];
// While resizing, OSX fails to call drawRect on the NSView unless the
// window size has changed. That means we won't see animations update if the
// user has the mouse button held down, but is not currently changing the
// size of the window. To work around that, display here while resizing.
// Also, OSX will never call drawRect faster than vsync rate, so if
// disable-gpu-vsync is set, we need to display now.
if (compositing_iosurface_->is_vsync_disabled() ||
[cocoa_view_ inLiveResize]) {
[cocoa_view_ displayIfNeeded];
}
compositing_iosurface_->DrawIOSurface(cocoa_view_,
ScaleFactor(cocoa_view_));
}
return false;
return true;
}
void RenderWidgetHostViewMac::AckPendingSwapBuffers() {
......@@ -2161,12 +2146,7 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
if (renderWidgetHostView_->last_frame_was_accelerated_ &&
renderWidgetHostView_->compositing_iosurface_.get()) {
bool is_swap_without_dirty =
(dirtyRect.origin.x == kGpuSwapBuffersDirtyRect.origin.x &&
dirtyRect.origin.y == kGpuSwapBuffersDirtyRect.origin.y &&
dirtyRect.size.width == kGpuSwapBuffersDirtyRect.size.width &&
dirtyRect.size.height == kGpuSwapBuffersDirtyRect.size.height);
if (!is_swap_without_dirty) {
{
TRACE_EVENT2("gpu", "NSRectFill clear", "w", damagedRect.width(),
"h", damagedRect.height());
// Draw transparency to expose the GL underlay. NSRectFill is extremely
......@@ -2183,9 +2163,6 @@ void RenderWidgetHostViewMac::SetTextInputActive(bool active) {
// that on.
renderWidgetHostView_->compositing_iosurface_->DrawIOSurface(
self, ScaleFactor(self));
// For latency_tests.cc:
UNSHIPPED_TRACE_EVENT_INSTANT0("test_gpu", "CompositorSwapBuffersComplete");
renderWidgetHostView_->AckPendingSwapBuffers();
return;
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment