Merge revision 743 changes:

- Optimize the cefclient off-screen rendering example (issue #697).

git-svn-id: https://chromiumembedded.googlecode.com/svn/branches/963@744 5089003a-bbd8-11dd-ad1f-f1f9622dbc98
This commit is contained in:
Marshall Greenblatt 2012-08-16 19:22:25 +00:00
parent 4292533524
commit 90472b1155
4 changed files with 159 additions and 349 deletions

View File

@ -14,82 +14,21 @@
#error Platform is not supported.
#endif
namespace {
#ifndef GL_BGR
#define GL_BGR 0x80E0
#endif
#ifndef GL_BGRA
#define GL_BGRA 0x80E1
#endif
#ifndef GL_UNSIGNED_INT_8_8_8_8_REV
#define GL_UNSIGNED_INT_8_8_8_8_REV 0x8367
#endif
// Convert from BGRA to RGBA format and from upper-left to lower-left origin.
void ConvertToRGBA(const unsigned char* src, unsigned char* dst,
int width, int height) {
int sp = 0, dp = (height-1) * width * 4;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++, dp += 4, sp += 4) {
dst[dp] = src[sp+2]; // R
dst[dp+1] = src[sp+1]; // G
dst[dp+2] = src[sp]; // B
dst[dp+3] = src[sp+3]; // A
}
dp -= width * 8;
}
}
void ConvertToRGBARect(const CefRect& clipRect,
const unsigned char* src, unsigned char* dst,
int width, int height) {
int sp = 0, dp = (height-1) * width * 4;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++, dp += 4, sp += 4) {
if ((clipRect.x <= j) && (clipRect.x + clipRect.width > j) &&
(clipRect.y <= i) && (clipRect.y + clipRect.height > i)) {
dst[dp] = src[sp+2]; // R
dst[dp+1] = src[sp+1]; // G
dst[dp+2] = src[sp]; // B
dst[dp+3] = src[sp+3]; // A
}
}
dp -= width * 8;
}
}
// Convert from BGRA to RGB format and from upper-left to lower-left origin.
void ConvertToRGB(const unsigned char* src, unsigned char* dst,
int width, int height) {
int sp = 0, dp = (height-1) * width * 3;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++, dp += 3, sp += 4) {
dst[dp] = src[sp+2]; // R
dst[dp+1] = src[sp+1]; // G
dst[dp+2] = src[sp]; // B
}
dp -= width * 6;
}
}
void ConvertToRGBRect(const CefRect& clipRect,
const unsigned char* src, unsigned char* dst,
int width, int height) {
int sp = 0, dp = (height-1) * width * 3;
for (int i = 0; i < height; i++) {
for (int j = 0; j < width; j++, dp += 3, sp += 4) {
if ((clipRect.x <= j) && (clipRect.x + clipRect.width > j) &&
(clipRect.y <= i) && (clipRect.y + clipRect.height > i)) {
dst[dp] = src[sp+2]; // R
dst[dp+1] = src[sp+1]; // G
dst[dp+2] = src[sp]; // B
}
}
dp -= width * 6;
}
}
} // namespace
ClientOSRenderer::ClientOSRenderer(bool transparent)
: transparent_(transparent),
initialized_(false),
texture_id_(0),
view_buffer_(NULL),
view_buffer_size_(0),
popup_buffer_(NULL),
popup_buffer_size_(0),
view_width_(0),
view_height_(0),
spin_x_(0),
@ -104,104 +43,44 @@ void ClientOSRenderer::Initialize() {
if (initialized_)
return;
glHint(GL_POLYGON_SMOOTH_HINT, GL_NICEST);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
// Necessary for non-power-of-2 textures to render correctly.
glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
if (transparent_) {
// Alpha blending style. Texture values have premultiplied alpha.
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
}
// Create the texture.
glGenTextures(1, &texture_id_);
ASSERT(texture_id_ != 0);
glBindTexture(GL_TEXTURE_2D, texture_id_);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
initialized_ = true;
}
void ClientOSRenderer::Cleanup() {
if (view_buffer_) {
delete [] view_buffer_;
view_buffer_ = NULL;
view_buffer_size_ = 0;
}
if (popup_buffer_) {
delete [] popup_buffer_;
popup_buffer_ = NULL;
popup_buffer_size_ = 0;
}
if (texture_id_ != 0)
glDeleteTextures(1, &texture_id_);
}
void ClientOSRenderer::SetSize(int width, int height) {
if (!initialized_)
Initialize();
// Match GL units to screen coordinates.
glViewport(0, 0, width, height);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 0, width, height, 0.1, 100.0);
if (transparent_) {
// Enable alpha blending.
glEnable(GL_BLEND);
}
// Enable 2D textures.
glEnable(GL_TEXTURE_2D);
GLuint new_texture_id = 0;
// Create a new texture.
glGenTextures(1, &new_texture_id);
ASSERT(new_texture_id != 0);
glBindTexture(GL_TEXTURE_2D, new_texture_id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
// Start with all white contents.
{
int size = width * height * (transparent_?4:3);
unsigned char* buffer = new unsigned char[size];
memset(buffer, 255, size);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0,
transparent_?GL_RGBA:GL_RGB, GL_UNSIGNED_BYTE, buffer);
delete [] buffer;
}
if (texture_id_ != 0) {
// Draw the existing view buffer to the new texture.
DrawViewBuffer(width, height);
// Delete the old texture.
glDeleteTextures(1, &texture_id_);
}
texture_id_ = new_texture_id;
// Disable 2D textures.
glDisable(GL_TEXTURE_2D);
if (transparent_) {
// Disable alpha blending.
glDisable(GL_BLEND);
}
}
void ClientOSRenderer::Render() {
if (view_width_ == 0 || view_height_ == 0)
return;
ASSERT(initialized_);
struct {
float tu, tv;
float x, y, z;
} static vertices[] = {
{0.0f, 0.0f, -1.0f, -1.0f, 0.0f},
{1.0f, 0.0f, 1.0f, -1.0f, 0.0f},
{1.0f, 1.0f, 1.0f, 1.0f, 0.0f},
{0.0f, 1.0f, -1.0f, 1.0f, 0.0f}
{0.0f, 1.0f, -1.0f, -1.0f, 0.0f},
{1.0f, 1.0f, 1.0f, -1.0f, 0.0f},
{1.0f, 0.0f, 1.0f, 1.0f, 0.0f},
{0.0f, 0.0f, -1.0f, 1.0f, 0.0f}
};
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
@ -209,6 +88,12 @@ void ClientOSRenderer::Render() {
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
// Match GL units to screen coordinates.
glViewport(0, 0, view_width_, view_height_);
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
glOrtho(0, 0, view_width_, view_height_, 0.1, 100.0);
// Draw the background gradient.
glPushAttrib(GL_ALL_ATTRIB_BITS);
glBegin(GL_QUADS);
@ -222,10 +107,15 @@ void ClientOSRenderer::Render() {
glPopAttrib();
// Rotate the view based on the mouse spin.
glRotatef(-spin_x_, 1.0f, 0.0f, 0.0f);
glRotatef(-spin_y_, 0.0f, 1.0f, 0.0f);
if (spin_x_ != 0)
glRotatef(-spin_x_, 1.0f, 0.0f, 0.0f);
if (spin_y_ != 0)
glRotatef(-spin_y_, 0.0f, 1.0f, 0.0f);
if (transparent_) {
// Alpha blending style. Texture values have premultiplied alpha.
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
// Enable alpha blending.
glEnable(GL_BLEND);
}
@ -253,34 +143,26 @@ void ClientOSRenderer::Render() {
void ClientOSRenderer::OnPopupShow(CefRefPtr<CefBrowser> browser,
bool show) {
if (!show) {
// Clear the popup buffer.
// Invalidate the previous popup rectangle so that it will be repainted.
browser->Invalidate(popup_rect_);
// Clear the popup rectangle.
popup_rect_.Set(0, 0, 0, 0);
if (popup_buffer_) {
delete [] popup_buffer_;
popup_buffer_ = NULL;
popup_buffer_size_ = 0;
}
}
}
void ClientOSRenderer::OnPopupSize(CefRefPtr<CefBrowser> browser,
const CefRect& rect) {
if (rect.width > 0) {
// Update the popup rectange. It should always be inside the view.
ASSERT(rect.x + rect.width < view_width_ &&
rect.y + rect.height < view_height_);
if (rect.width > 0 && rect.height > 0)
popup_rect_ = rect;
}
}
void ClientOSRenderer::OnPaint(CefRefPtr<CefBrowser> browser,
CefRenderHandler::PaintElementType type,
const CefRenderHandler::RectList& dirtyRects,
const void* buffer) {
ASSERT(initialized_);
// Retrieve the current size of the browser view.
browser->GetSize(type, view_width_, view_height_);
if (!initialized_)
Initialize();
if (transparent_) {
// Enable alpha blending.
@ -294,46 +176,59 @@ void ClientOSRenderer::OnPaint(CefRefPtr<CefBrowser> browser,
glBindTexture(GL_TEXTURE_2D, texture_id_);
if (type == PET_VIEW) {
SetBufferSize(view_width_, view_height_, true);
int old_width = view_width_;
int old_height = view_height_;
// Paint the view.
if (transparent_) {
CefRenderHandler::RectList::const_iterator i = dirtyRects.begin();
for (; i != dirtyRects.end(); ++i) {
ConvertToRGBARect(*i, (unsigned char*)buffer, view_buffer_,
view_width_, view_height_);
}
// Retrieve the current size of the browser view.
browser->GetSize(type, view_width_, view_height_);
glPixelStorei(GL_UNPACK_ROW_LENGTH, view_width_);
if (old_width != view_width_ || old_height != view_height_) {
// Update/resize the whole texture.
glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
glPixelStorei(GL_UNPACK_SKIP_ROWS, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, view_width_, view_height_, 0,
GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
} else {
// Update just the dirty rectangles.
CefRenderHandler::RectList::const_iterator i = dirtyRects.begin();
for (; i != dirtyRects.end(); ++i) {
ConvertToRGBRect(*i, (unsigned char*)buffer, view_buffer_,
view_width_, view_height_);
const CefRect& rect = *i;
glPixelStorei(GL_UNPACK_SKIP_PIXELS, rect.x);
glPixelStorei(GL_UNPACK_SKIP_ROWS, rect.y);
glTexSubImage2D(GL_TEXTURE_2D, 0, rect.x, rect.y, rect.width,
rect.height, GL_BGRA, GL_UNSIGNED_INT_8_8_8_8_REV,
buffer);
}
}
} else if (type == PET_POPUP && popup_rect_.width > 0 &&
popup_rect_.height > 0) {
int skip_pixels = 0, x = popup_rect_.x;
int skip_rows = 0, y = popup_rect_.y;
int width = popup_rect_.width;
int height = popup_rect_.height;
// Update the whole texture. This is done for simplicity instead of
// updating just the dirty region.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, view_width_, view_height_,
transparent_?GL_RGBA:GL_RGB, GL_UNSIGNED_BYTE, view_buffer_);
}
if (popup_rect_.width > 0) {
if (type == PET_POPUP) {
// Paint the popup.
if (transparent_)
SetRGBA(buffer, popup_rect_.width, popup_rect_.height, false);
else
SetRGB(buffer, popup_rect_.width, popup_rect_.height, false);
// Adjust the popup to fit inside the view.
if (x < 0) {
skip_pixels = -x;
x = 0;
}
if (popup_buffer_) {
// Update the popup region.
glTexSubImage2D(GL_TEXTURE_2D, 0, popup_rect_.x,
view_height_ - popup_rect_.y - popup_rect_.height,
popup_rect_.width, popup_rect_.height,
transparent_?GL_RGBA:GL_RGB,
GL_UNSIGNED_BYTE, popup_buffer_);
if (y < 0) {
skip_rows = -y;
y = 0;
}
if (x + width > view_width_)
width -= x + width - view_width_;
if (y + height > view_height_)
height -= y + height - view_height_;
// Update the popup rectangle.
glPixelStorei(GL_UNPACK_ROW_LENGTH, popup_rect_.width);
glPixelStorei(GL_UNPACK_SKIP_PIXELS, skip_pixels);
glPixelStorei(GL_UNPACK_SKIP_ROWS, skip_rows);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_BGRA,
GL_UNSIGNED_INT_8_8_8_8_REV, buffer);
}
// Disable 2D textures.
@ -354,79 +249,3 @@ void ClientOSRenderer::IncrementSpin(float spinDX, float spinDY) {
spin_x_ -= spinDX;
spin_y_ -= spinDY;
}
bool ClientOSRenderer::GetPixelValue(int x, int y, unsigned char& r,
unsigned char& g, unsigned char& b,
unsigned char& a) {
if (!view_buffer_)
return false;
ASSERT(x >= 0 && x < view_width_);
ASSERT(y >= 0 && y < view_height_);
int pixel_bytes = transparent_ ? 4 : 3;
int y_flipped = view_height_ - y;
int index = (view_width_ * y_flipped * pixel_bytes) + (x * pixel_bytes);
r = view_buffer_[index];
g = view_buffer_[index+1];
b = view_buffer_[index+2];
if (transparent_)
a = view_buffer_[index+3];
return true;
}
void ClientOSRenderer::SetBufferSize(int width, int height, bool view) {
int dst_size = width * height * (transparent_?4:3);
// Allocate a new buffer if necesary.
if (view) {
if (dst_size > view_buffer_size_) {
if (view_buffer_)
delete [] view_buffer_;
view_buffer_ = new unsigned char[dst_size];
view_buffer_size_ = dst_size;
}
} else {
if (dst_size > popup_buffer_size_) {
if (popup_buffer_)
delete [] popup_buffer_;
popup_buffer_ = new unsigned char[dst_size];
popup_buffer_size_ = dst_size;
}
}
}
// Set the contents of the RGBA buffer.
void ClientOSRenderer::SetRGBA(const void* src, int width, int height,
bool view) {
SetBufferSize(width, height, view);
ConvertToRGBA((unsigned char*)src, view?view_buffer_:popup_buffer_, width,
height);
}
// Set the contents of the RGB buffer.
void ClientOSRenderer::SetRGB(const void* src, int width, int height,
bool view) {
SetBufferSize(width, height, view);
ConvertToRGB((unsigned char*)src, view?view_buffer_:popup_buffer_, width,
height);
}
void ClientOSRenderer::DrawViewBuffer(int max_width, int max_height) {
if (max_width < view_width_ || max_height < view_height_) {
// The requested max size is smaller than the current view buffer.
int width = std::min(max_width, view_width_);
int height = std::min(max_height, view_height_);
glPixelStorei(GL_UNPACK_ROW_LENGTH, view_width_);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height,
transparent_?GL_RGBA:GL_RGB, GL_UNSIGNED_BYTE,
view_buffer_);
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
} else {
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, view_width_, view_height_,
transparent_?GL_RGBA:GL_RGB, GL_UNSIGNED_BYTE, view_buffer_);
}
}

View File

@ -19,9 +19,6 @@ class ClientOSRenderer {
// Clean up the OpenGL environment.
void Cleanup();
// Set the size of the viewport.
void SetSize(int width, int height);
// Render to the screen.
void Render();
@ -39,29 +36,18 @@ class ClientOSRenderer {
void SetSpin(float spinX, float spinY);
void IncrementSpin(float spinDX, float spinDY);
// Retrieve the pixel value from the view buffer. |x| and |y| are relative to
// the upper-left corner of the view.
bool GetPixelValue(int x, int y, unsigned char& r, unsigned char& g,
unsigned char& b, unsigned char& a);
bool IsTransparent() { return transparent_; }
int GetViewWidth() { return view_width_; }
int GetViewHeight() { return view_height_; }
private:
void SetBufferSize(int width, int height, bool view);
void SetRGBA(const void* src, int width, int height, bool view);
void SetRGB(const void* src, int width, int height, bool view);
void DrawViewBuffer(int max_width, int max_height);
bool transparent_;
bool initialized_;
unsigned int texture_id_;
unsigned char* view_buffer_;
int view_buffer_size_;
unsigned char* popup_buffer_;
int popup_buffer_size_;
CefRect popup_rect_;
int view_width_;
int view_height_;
CefRect popup_rect_;
float spin_x_;
float spin_y_;
};

View File

@ -58,7 +58,8 @@ class ClientOSRHandler : public CefClient,
public CefRenderHandler {
public:
explicit ClientOSRHandler(ClientPlugin* plugin)
: plugin_(plugin) {
: plugin_(plugin),
want_pixel_value_(false) {
}
~ClientOSRHandler() {
}
@ -68,6 +69,21 @@ class ClientOSRHandler : public CefClient,
plugin_ = NULL;
}
// Retrieve the pixel value at the requested coordinate the next time that
// the view is painted.
void WantPixelValue(int x, int y) {
if (!want_pixel_value_) {
want_pixel_value_ = true;
// Trigger a call to OnPaint.
if (g_offscreenBrowser.get())
g_offscreenBrowser->Invalidate(CefRect(0,0,1,1));
}
coord_x_ = x;
coord_y_ = y;
}
// CefClient methods
virtual CefRefPtr<CefLifeSpanHandler> GetLifeSpanHandler() OVERRIDE {
return this;
@ -274,6 +290,42 @@ class ClientOSRHandler : public CefClient,
wglMakeCurrent(plugin_->hDC, plugin_->hRC);
plugin_->renderer.OnPaint(browser, type, dirtyRects, buffer);
plugin_->renderer.Render();
SwapBuffers(plugin_->hDC);
if (type == PET_VIEW && want_pixel_value_) {
// Retrieve the pixel value at the requested coordinate.
int width = plugin_->renderer.GetViewWidth();
int height = plugin_->renderer.GetViewHeight();
if(coord_x_ >= 0 && coord_x_ < width &&
coord_y_ >= 0 && coord_y_ < height) {
bool transparent = plugin_->renderer.IsTransparent();
int index = (width * coord_y_ * 4) + (coord_x_ * 4);
const unsigned char *pBuf = static_cast<const unsigned char*>(buffer);
unsigned char r, g, b, a;
b = pBuf[index];
g = pBuf[index+1];
r = pBuf[index+2];
if (transparent)
a = pBuf[index+3];
std::stringstream ss;
ss << coord_x_ << "," << coord_y_ << " = R:" << static_cast<int>(r)
<< " G:" << static_cast<int>(g) << " B:" << static_cast<int>(b);
if (transparent)
ss << " A:" << static_cast<int>(a);
std::string js =
"document.getElementById('pixel').innerText = '" + ss.str() + "';";
AppGetBrowser()->GetMainFrame()->ExecuteJavaScript(js, "", 0);
}
want_pixel_value_ = false;
}
}
virtual void OnCursorChange(CefRefPtr<CefBrowser> browser,
@ -302,6 +354,11 @@ class ClientOSRHandler : public CefClient,
ClientPlugin* plugin_;
// Used for retrieving the pixel value.
bool want_pixel_value_;
int coord_x_;
int coord_y_;
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(ClientOSRPlugin);
};
@ -348,23 +405,10 @@ void DisableGL(ClientPlugin* plugin) {
// Size the GL view.
void SizeGL(ClientPlugin* plugin, int width, int height) {
wglMakeCurrent(plugin->hDC, plugin->hRC);
plugin->renderer.SetSize(width, height);
if (g_offscreenBrowser.get())
g_offscreenBrowser->SetSize(PET_VIEW, width, height);
}
// Render the view contents.
void RenderGL(ClientPlugin* plugin) {
wglMakeCurrent(plugin->hDC, plugin->hRC);
plugin->renderer.Render();
SwapBuffers(plugin->hDC);
}
NPError NPP_NewImpl(NPMIMEType plugin_type, NPP instance, uint16 mode,
int16 argc, char* argn[], char* argv[],
NPSavedData* saved) {
@ -471,24 +515,12 @@ LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT message, WPARAM wParam,
reinterpret_cast<ClientPlugin*>(GetWindowLongPtr(hWnd, GWLP_USERDATA));
switch (message) {
case WM_CREATE:
// Start the timer that's used for redrawing.
SetTimer(hWnd, 1, 20, NULL);
return 0;
case WM_DESTROY:
// Stop the timer that's used for redrawing.
KillTimer(hWnd, 1);
// Explicitly close the offscreen browser and release the reference.
g_offscreenBrowser->CloseBrowser();
g_offscreenBrowser = NULL;
return 0;
case WM_TIMER:
RenderGL(plugin);
break;
case WM_LBUTTONDOWN:
case WM_RBUTTONDOWN:
SetCapture(hWnd);
@ -502,18 +534,9 @@ LRESULT CALLBACK PluginWndProc(HWND hWnd, UINT message, WPARAM wParam,
// Retrieve the pixel value.
int x = LOWORD(lParam);
int y = HIWORD(lParam);
unsigned char r, g, b, a;
if (plugin->renderer.GetPixelValue(x, y, r, g, b, a)) {
std::stringstream ss;
ss << x << "," << y << " = R:" << static_cast<int>(r) << " G:" <<
static_cast<int>(g) << " B:" << static_cast<int>(b);
if (plugin->renderer.IsTransparent())
ss << " A:" << static_cast<int>(a);
std::string js =
"document.getElementById('pixel').innerText = '" + ss.str() + "';";
AppGetBrowser()->GetMainFrame()->ExecuteJavaScript(js, "", 0);
}
CefRefPtr<ClientOSRHandler> handler =
static_cast<ClientOSRHandler*>(g_offscreenBrowser->GetClient().get());
handler->WantPixelValue(x, y);
} else {
if (g_offscreenBrowser.get()) {
g_offscreenBrowser->SendMouseClickEvent(LOWORD(lParam), HIWORD(lParam),

View File

@ -254,18 +254,13 @@ class ClientOSRHandler : public CefClient,
if (!view_)
return;
[[view_ openGLContext] makeCurrentContext];
NSOpenGLContext* context = [view_ openGLContext];
[context makeCurrentContext];
view_->renderer_->OnPaint(browser, type, dirtyRects, buffer);
view_->renderer_->Render();
// Notify the view to redraw the invalidated regions.
{
RectList::const_iterator i = dirtyRects.begin();
for (; i != dirtyRects.end(); ++i) {
NSRect rect = {{i->x, i->y}, {i->width, i->height}};
[view_ setNeedsDisplayInRect:rect];
}
}
[context flushBuffer];
}
virtual void OnCursorChange(CefRefPtr<CefBrowser> browser,
@ -329,24 +324,11 @@ class ClientOSRHandler : public CefClient,
[super dealloc];
}
- (void)drawRect: (NSRect)bounds {
NSOpenGLContext* context = [self openGLContext];
[context makeCurrentContext];
renderer_->Render();
[context flushBuffer];
}
- (void)setFrame:(NSRect)frameRect {
[super setFrame:frameRect];
int width = frameRect.size.width;
int height = frameRect.size.height;
[[self openGLContext] makeCurrentContext];
renderer_->SetSize(width, height);
if (browser_)
browser_->SetSize(PET_VIEW, width, height);