VBoxFBQuartz2D.cpp revision e64031e20c39650a7bc902a3e1aba613b9415dee
/* $Id$ */
/** @file
* Qt GUI (aka VirtualBox) - Quartz2D framebuffer implementation.
*/
/*
* Copyright (C) 2006-2007 Oracle Corporation
*
* This file is part of VirtualBox Open Source Edition (OSE), as
* available from http://www.virtualbox.org. This file is free software;
* you can redistribute it and/or modify it under the terms of the GNU
* General Public License (GPL) as published by the Free Software
* Foundation, in version 2 as it comes in the "COPYING" file of the
* VirtualBox OSE distribution. VirtualBox OSE is distributed in the
* hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
*/
#if defined (VBOX_GUI_USE_QUARTZ2D)
#ifdef VBOX_WITH_PRECOMPILED_HEADERS
# include "precomp.h"
#else /* !VBOX_WITH_PRECOMPILED_HEADERS */
#include <iprt/asm.h>
/* VBox includes */
#include "VBoxFrameBuffer.h"
#include "VBoxConsoleView.h"
#include "VBoxProblemReporter.h"
#include "VBoxGlobal.h"
#include "VBoxUtils.h"
/* Needed for checking against seamless mode */
#include "VBoxConsoleWnd.h"
#include "VBoxIChatTheaterWrapper.h"
#include <QDesktopWidget>
#include "iprt/system.h"
#endif /* !VBOX_WITH_PRECOMPILED_HEADERS */
//#define COMP_WITH_SHADOW
//#define OVERLAY_CLIPRECTS
/** @class VBoxQuartz2DFrameBuffer
*
* The VBoxQuartz2dFrameBuffer class is a class that implements the IFrameBuffer
* interface and uses Apples Quartz2D to store and render VM display data.
*/
#ifndef QT_MAC_USE_COCOA
static OSStatus darwinSNWindowHandler (EventHandlerCallRef aInHandlerCallRef, EventRef aInEvent, void *aInUserData)
{
if ( aInUserData
&& ::GetEventClass (aInEvent) == kEventClassWindow
&& ::GetEventKind (aInEvent) == kEventWindowBoundsChanged)
static_cast<VBoxQuartz2DFrameBuffer *> (aInUserData)->testAndSetSNCarbonFix();
return ::CallNextEventHandler (aInHandlerCallRef, aInEvent);
}
#endif /* QT_MAC_USE_COCOA */
VBoxQuartz2DFrameBuffer::VBoxQuartz2DFrameBuffer (VBoxConsoleView *aView)
: VBoxFrameBuffer(aView)
, mDataAddress(NULL)
, mBitmapData(NULL)
, mPixelFormat(FramebufferPixelFormat_FOURCC_RGB)
, mImage(NULL)
, mRegion (NULL)
, mRegionUnused (NULL)
#ifndef QT_MAC_USE_COCOA
, mSnowLeoCarbonFix (false)
, mDarwinSNWindowHandlerRef (NULL)
#endif /* QT_MAC_USE_COCOA */
{
Log (("Quartz2D: Creating\n"));
#ifndef QT_MAC_USE_COCOA
/* There seems to be a big bug on Snow Leopard regarding hardware
* accelerated image handling in Carbon. If our VM image is used on a
* second monitor it seems not really to be valid. (maybe the OpenGL
* texture is not properly shared between the two contexts). As a
* workaround we make a deep copy on every paint event. This workaround
* should only be in place if we are firstly on Snow Leopard and secondly
* on any screen beside the primary one. To track the current screen, we
* install a event handler for the window move event. Whenever the user
* drag the window around we recheck the current screen of the window. */
char szInfo[64];
int rc = RTSystemQueryOSInfo (RTSYSOSINFO_RELEASE, szInfo, sizeof(szInfo));
if (RT_SUCCESS (rc) &&
szInfo[0] == '1') /* higher than 1x.x.x */
{
EventTypeSpec eventTypes[] =
{
{ kEventClassWindow, kEventWindowBoundsChanged }
};
::InstallWindowEventHandler (::darwinToNativeWindow (mView->viewport()), darwinSNWindowHandler, RT_ELEMENTS (eventTypes), &eventTypes[0],
this, &mDarwinSNWindowHandlerRef);
/* Initialize it now */
testAndSetSNCarbonFix();
}
#endif /* QT_MAC_USE_COCOA */
VBoxResizeEvent event(FramebufferPixelFormat_Opaque,
NULL, 0, 0, 640, 480);
resizeEvent (&event);
}
VBoxQuartz2DFrameBuffer::~VBoxQuartz2DFrameBuffer()
{
Log (("Quartz2D: Deleting\n"));
clean();
#ifndef QT_MAC_USE_COCOA
if (mDarwinSNWindowHandlerRef)
{
::RemoveEventHandler (mDarwinSNWindowHandlerRef);
mDarwinSNWindowHandlerRef = NULL;
}
#endif /* QT_MAC_USE_COCOA */
}
/** @note This method is called on EMT from under this object's lock */
STDMETHODIMP VBoxQuartz2DFrameBuffer::NotifyUpdate (ULONG aX, ULONG aY,
ULONG aW, ULONG aH)
{
/* Log (("Quartz2D: NotifyUpdate %d,%d %dx%d\n", aX, aY, aW, aH));*/
QApplication::postEvent (mView,
new VBoxRepaintEvent (aX, aY, aW, aH));
return S_OK;
}
STDMETHODIMP VBoxQuartz2DFrameBuffer::SetVisibleRegion (BYTE *aRectangles, ULONG aCount)
{
PRTRECT rects = (PRTRECT)aRectangles;
if (!rects)
return E_POINTER;
/** @todo r=bird: Is this thread safe? If I remember the code flow correctly, the
* GUI thread could be happily jogging along paintEvent now on another cpu core.
* This function is called on the EMT (emulation thread). Which means, blocking
* execution waiting for a lock is out of the question. A quick solution using
* ASMAtomic(Cmp)XchgPtr and a struct { cAllocated; cRects; aRects[1]; }
* *mRegion, *mUnusedRegion; should suffice (and permit you to reuse allocations). */
RegionRects *rgnRcts = (RegionRects *) ASMAtomicXchgPtr ((void * volatile *) &mRegionUnused, NULL);
if (rgnRcts && rgnRcts->allocated < aCount)
{
RTMemFree (rgnRcts);
rgnRcts = NULL;
}
if (!rgnRcts)
{
ULONG allocated = RT_ALIGN_32 (aCount + 1, 32);
allocated = RT_MAX (128, allocated);
rgnRcts = (RegionRects *) RTMemAlloc (RT_OFFSETOF (RegionRects, rcts[allocated]));
if (!rgnRcts)
return E_OUTOFMEMORY;
rgnRcts->allocated = allocated;
}
rgnRcts->used = 0;
QRegion reg;
// printf ("Region rects follow...\n");
QRect vmScreenRect (0, 0, width(), height());
for (ULONG ind = 0; ind < aCount; ++ ind)
{
QRect rect;
rect.setLeft (rects->xLeft);
rect.setTop (rects->yTop);
/* QRect are inclusive */
rect.setRight (rects->xRight - 1);
rect.setBottom (rects->yBottom - 1);
/* The rect should intersect with the vm screen. */
rect = vmScreenRect.intersect (rect);
++ rects;
/* Make sure only valid rects are distributed */
/* todo: Test if the other framebuffer implementation have the same
* problem with invalid rects (In Linux/Windows) */
if (rect.isValid() &&
rect.width() > 0 && rect.height() > 0)
reg += rect;
else
continue;
CGRect *cgRct = &rgnRcts->rcts[rgnRcts->used];
cgRct->origin.x = rect.x();
cgRct->origin.y = height() - rect.y() - rect.height();
cgRct->size.width = rect.width();
cgRct->size.height = rect.height();
// printf ("Region rect[%d - %d]: %d %d %d %d\n", rgnRcts->used, aCount, rect.x(), rect.y(), rect.height(), rect.width());
rgnRcts->used++;
}
// printf ("..................................\n");
void *pvOld = ASMAtomicXchgPtr ((void * volatile *) &mRegion, rgnRcts);
if ( pvOld
&& !ASMAtomicCmpXchgPtr ((void * volatile *) &mRegionUnused, pvOld, NULL))
RTMemFree (pvOld);
QApplication::postEvent (mView, new VBoxSetRegionEvent (reg));
return S_OK;
}
void VBoxQuartz2DFrameBuffer::paintEvent (QPaintEvent *aEvent)
{
/* For debugging /Developer/Applications/Performance Tools/Quartz
* Debug.app is a nice tool to see which parts of the screen are
* updated.*/
Assert (mImage);
VBoxConsoleWnd *main = qobject_cast <VBoxConsoleWnd *> (vboxGlobal().mainWindow());
Assert (VALID_PTR (main));
QWidget* viewport = mView->viewport();
Assert (VALID_PTR (viewport));
/* Get the dimensions of the viewport */
CGRect viewRect = ::darwinToCGRect (viewport->geometry());
/* Get the context of this window from Qt */
CGContextRef ctx = ::darwinToCGContextRef (viewport);
Assert (VALID_PTR (ctx));
/* Flip the context */
CGContextTranslateCTM (ctx, 0, viewRect.size.height);
CGContextScaleCTM (ctx, 1.0, -1.0);
/* We handle the seamless mode as a special case. */
if ( main
&& main->isTrueSeamless())
{
/* Here we paint the windows without any wallpaper.
* So the background would be set transparently. */
/* Create a subimage of the current view.
* Currently this subimage is the whole screen. */
CGImageRef subImage;
if (!mView->pauseShot().isNull())
{
CGImageRef pauseImg = ::darwinToCGImageRef (&mView->pauseShot());
subImage = CGImageCreateWithImageInRect (pauseImg, CGRectMake (mView->contentsX(), mView->contentsY(), mView->visibleWidth(), mView->visibleHeight()));
CGImageRelease (pauseImg);
}
else
{
#ifndef QT_MAC_USE_COCOA
/* For the reason of this see the constructor. */
if (mSnowLeoCarbonFix)
{
CGImageRef copy = CGImageCreateCopy (mImage);
subImage = CGImageCreateWithImageInRect (copy, CGRectMake (mView->contentsX(), mView->contentsY(), mView->visibleWidth(), mView->visibleHeight()));
CGImageRelease (copy);
}else
#endif /* QT_MAC_USE_COCOA */
subImage = CGImageCreateWithImageInRect (mImage, CGRectMake (mView->contentsX(), mView->contentsY(), mView->visibleWidth(), mView->visibleHeight()));
}
Assert (VALID_PTR (subImage));
/* Clear the background (Make the rect fully transparent) */
CGContextClearRect (ctx, viewRect);
#ifdef OVERLAY_CLIPRECTS
CGContextSetRGBFillColor (ctx, 0.0, 0.0, 5.0, 0.7);
CGContextFillRect (ctx, viewRect);
#endif
#ifdef COMP_WITH_SHADOW
/* Enable shadows */
CGContextSetShadow (ctx, CGSizeMake (10, -10), 10);
CGContextBeginTransparencyLayer (ctx, NULL);
#endif
/* Grab the current visible region. */
RegionRects *rgnRcts = (RegionRects *) ASMAtomicXchgPtr ((void * volatile *) &mRegion, NULL);
if (rgnRcts)
{
if (rgnRcts->used > 0)
{
/* Add the clipping rects all at once. They are defined in
* SetVisibleRegion. */
CGContextBeginPath (ctx);
CGContextAddRects (ctx, rgnRcts->rcts, rgnRcts->used);
/* Now convert the path to a clipping path. */
CGContextClip (ctx);
}
/* Put back the visible region, free if we cannot (2+ SetVisibleRegion calls). */
if ( !ASMAtomicCmpXchgPtr ((void * volatile *) &mRegion, rgnRcts, NULL)
&& !ASMAtomicCmpXchgPtr ((void * volatile *) &mRegionUnused, rgnRcts, NULL))
RTMemFree (rgnRcts);
}
/* In any case clip the drawing to the view window */
CGContextClipToRect (ctx, viewRect);
/* At this point draw the real vm image */
CGContextDrawImage (ctx, ::darwinFlipCGRect (viewRect, viewRect.size.height), subImage);
#ifdef COMP_WITH_SHADOW
CGContextEndTransparencyLayer (ctx);
#endif
CGImageRelease (subImage);
#ifdef OVERLAY_CLIPRECTS
if (rgnRcts && rgnRcts->used > 0)
{
CGContextBeginPath (ctx);
CGContextAddRects (ctx, rgnRcts->rcts, rgnRcts->used);
CGContextSetRGBStrokeColor (ctx, 1.0, 0.0, 0.0, 0.7);
CGContextDrawPath (ctx, kCGPathStroke);
}
CGContextSetRGBStrokeColor (ctx, 0.0, 1.0, 0.0, 0.7);
CGContextStrokeRect (ctx, viewRect);
#endif
}
else
{
/* Here we paint if we didn't care about any masks */
/* Create a subimage of the current view in the size
* of the bounding box of the current paint event */
QRect ir = aEvent->rect();
QRect is = QRect (ir.x() + mView->contentsX(), ir.y() + mView->contentsY(), ir.width(), ir.height());
CGImageRef subImage;
if (!mView->pauseShot().isNull())
{
CGImageRef pauseImg = ::darwinToCGImageRef (&mView->pauseShot());
subImage = CGImageCreateWithImageInRect (pauseImg, ::darwinToCGRect (is));
CGImageRelease (pauseImg);
}
else
{
#ifndef QT_MAC_USE_COCOA
/* For the reason of this see the constructor. */
if (mSnowLeoCarbonFix)
{
CGImageRef copy = CGImageCreateCopy (mImage);
subImage = CGImageCreateWithImageInRect (copy, ::darwinToCGRect (is));
CGImageRelease (copy);
}else
#endif /* QT_MAC_USE_COCOA */
subImage = CGImageCreateWithImageInRect (mImage, ::darwinToCGRect (is));
}
Assert (VALID_PTR (subImage));
/* Ok, for more performance we set a clipping path of the
* regions given by this paint event. */
QVector<QRect> a = aEvent->region().rects();
if (!a.isEmpty())
{
CGContextBeginPath (ctx);
/* Add all region rects to the current context as path components */
for (int i = 0; i < a.size(); ++i)
CGContextAddRect (ctx, ::darwinFlipCGRect (::darwinToCGRect (a[i]), viewRect.size.height));
/* Now convert the path to a clipping path. */
CGContextClip (ctx);
}
/* In any case clip the drawing to the view window */
CGContextClipToRect (ctx, viewRect);
/* At this point draw the real vm image */
CGContextDrawImage (ctx, ::darwinFlipCGRect (::darwinToCGRect (ir), viewRect.size.height), subImage);
CGImageRelease (subImage);
}
}
void VBoxQuartz2DFrameBuffer::resizeEvent (VBoxResizeEvent *aEvent)
{
#if 0
printf ("fmt=%lu, vram=%X, bpp=%lu, bpl=%lu, width=%lu, height=%lu\n",
aEvent->pixelFormat(), (unsigned int)aEvent->VRAM(),
aEvent->bitsPerPixel(), aEvent->bytesPerLine(),
aEvent->width(), aEvent->height());
#endif
/* Clean out old stuff */
clean();
mWdt = aEvent->width();
mHgt = aEvent->height();
bool remind = false;
/* We need a color space in any case */
CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
/* Check if we support the pixel format/colordepth and can use the guest VRAM directly.
* Mac OS X supports 16 bit also but not in the 565 mode. So we could use
* 32 bit only. */
if ( aEvent->pixelFormat() == FramebufferPixelFormat_FOURCC_RGB
&& aEvent->bitsPerPixel() == 32)
{
// printf ("VRAM\n");
/* Create the image copy of the framebuffer */
CGDataProviderRef dp = CGDataProviderCreateWithData (NULL, aEvent->VRAM(), aEvent->bitsPerPixel() / 8 * mWdt * mHgt, NULL);
mImage = CGImageCreate (mWdt, mHgt, 8, aEvent->bitsPerPixel(), aEvent->bytesPerLine(), cs,
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, dp, 0, false,
kCGRenderingIntentDefault);
mDataAddress = aEvent->VRAM();
CGDataProviderRelease (dp);
}
else
{
remind = true;
// printf ("No VRAM\n");
/* Create the memory we need for our image copy
* Read somewhere that an alignment of 16 is
* best for optimal performance. So why not. */
// int bitmapBytesPerRow = RT_ALIGN (mWdt * 4, 16);
int bitmapBytesPerRow = mWdt * 4;
int bitmapByteCount = (bitmapBytesPerRow * mHgt);
mBitmapData = RTMemAlloc (bitmapByteCount);
CGDataProviderRef dp = CGDataProviderCreateWithData (NULL, mBitmapData, bitmapByteCount, NULL);
mImage = CGImageCreate (mWdt, mHgt, 8, 32, bitmapBytesPerRow, cs,
kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, dp, 0, false,
kCGRenderingIntentDefault);
mDataAddress = static_cast <uchar*> (mBitmapData);
CGDataProviderRelease (dp);
}
CGColorSpaceRelease (cs);
#ifdef VBOX_WITH_ICHAT_THEATER
setImageRef (mImage);
#endif
// if (remind)
// {
// class RemindEvent : public VBoxAsyncEvent
// {
// ulong mRealBPP;
// public:
// RemindEvent (ulong aRealBPP)
// : mRealBPP (aRealBPP) {}
// void handle()
// {
// vboxProblem().remindAboutWrongColorDepth (mRealBPP, 32);
// }
// };
// (new RemindEvent (aEvent->bitsPerPixel()))->post();
// }
}
void VBoxQuartz2DFrameBuffer::clean()
{
if (mImage)
{
CGImageRelease (mImage);
mImage = NULL;
}
if (mBitmapData)
{
RTMemFree (mBitmapData);
mBitmapData = NULL;
}
if (mRegion)
{
RTMemFree ((void *) mRegion);
mRegion = NULL;
}
if (mRegionUnused)
{
RTMemFree ((void *) mRegionUnused);
mRegionUnused = NULL;
}
}
#ifndef QT_MAC_USE_COCOA
void VBoxQuartz2DFrameBuffer::testAndSetSNCarbonFix()
{
QWidget* viewport = mView->viewport();
Assert (VALID_PTR (viewport));
QDesktopWidget dw;
mSnowLeoCarbonFix = dw.primaryScreen() != dw.screenNumber (viewport);
}
#endif /* QT_MAC_USE_COCOA */
#endif /* defined (VBOX_GUI_USE_QUARTZ2D) */