VirtualBox

source: vbox/trunk/src/VBox/Frontends/VirtualBox/src/runtime/UIFrameBufferQuartz2D.cpp@ 37063

Last change on this file since 37063 was 37063, checked in by vboxsync, 13 years ago

FE/Qt4-OSX: fix second monitor image corruptions on 64bit

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 18.3 KB
Line 
1/* $Id: UIFrameBufferQuartz2D.cpp 37063 2011-05-13 10:21:28Z vboxsync $ */
2/** @file
3 *
4 * VBox frontends: Qt GUI ("VirtualBox"):
5 * UIFrameBufferQuartz2D class implementation
6 */
7
8/*
9 * Copyright (C) 2006-2010 Oracle Corporation
10 *
11 * This file is part of VirtualBox Open Source Edition (OSE), as
12 * available from http://www.virtualbox.org. This file is free software;
13 * you can redistribute it and/or modify it under the terms of the GNU
14 * General Public License (GPL) as published by the Free Software
15 * Foundation, in version 2 as it comes in the "COPYING" file of the
16 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
17 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
18 */
19
20#if defined (VBOX_GUI_USE_QUARTZ2D)
21
22#ifdef VBOX_WITH_PRECOMPILED_HEADERS
23# include "precomp.h"
24#else /* !VBOX_WITH_PRECOMPILED_HEADERS */
25/* Global includes */
26#include <QApplication>
27
28#include <iprt/asm.h>
29
30/* Local includes */
31#include "UIFrameBufferQuartz2D.h"
32#include "UIMachineView.h"
33#include "UIMachineLogic.h"
34#include "VBoxUtils.h"
35
36#endif /* !VBOX_WITH_PRECOMPILED_HEADERS */
37
38//#define COMP_WITH_SHADOW
39//#define OVERLAY_CLIPRECTS
40
41/** @class UIFrameBufferQuartz2D
42 *
43 * The UIFrameBufferQuartz2D class is a class that implements the IFrameBuffer
44 * interface and uses Apples Quartz2D to store and render VM display data.
45 */
46
47UIFrameBufferQuartz2D::UIFrameBufferQuartz2D(UIMachineView *pMachineView)
48 : UIFrameBuffer(pMachineView)
49 , m_pMachineLogic(pMachineView->machineLogic())
50 , m_pDataAddress(NULL)
51 , m_pBitmapData(NULL)
52 , m_uPixelFormat(FramebufferPixelFormat_FOURCC_RGB)
53 , m_image(NULL)
54 , mRegion(NULL)
55 , mRegionUnused(NULL)
56{
57 Log (("Quartz2D: Creating\n"));
58
59 UIResizeEvent event(FramebufferPixelFormat_Opaque,
60 NULL, 0, 0, 640, 480);
61 resizeEvent (&event);
62}
63
64UIFrameBufferQuartz2D::~UIFrameBufferQuartz2D()
65{
66 Log (("Quartz2D: Deleting\n"));
67 clean();
68}
69
70/** @note This method is called on EMT from under this object's lock */
71STDMETHODIMP UIFrameBufferQuartz2D::NotifyUpdate(ULONG aX, ULONG aY,
72 ULONG aW, ULONG aH)
73{
74/* Log (("Quartz2D: NotifyUpdate %d,%d %dx%d\n", aX, aY, aW, aH));*/
75
76 QApplication::postEvent(m_pMachineView,
77 new UIRepaintEvent (aX, aY, aW, aH));
78 return S_OK;
79}
80
81STDMETHODIMP UIFrameBufferQuartz2D::SetVisibleRegion(BYTE *aRectangles, ULONG aCount)
82{
83 PRTRECT rects = (PRTRECT)aRectangles;
84
85 if (!rects)
86 return E_POINTER;
87
88 /** @todo r=bird: Is this thread safe? If I remember the code flow correctly, the
89 * GUI thread could be happily jogging along paintEvent now on another cpu core.
90 * This function is called on the EMT (emulation thread). Which means, blocking
91 * execution waiting for a lock is out of the question. A quick solution using
92 * ASMAtomic(Cmp)XchgPtr and a struct { cAllocated; cRects; aRects[1]; }
93 * *mRegion, *mUnusedRegion; should suffice (and permit you to reuse allocations). */
94 RegionRects *rgnRcts = ASMAtomicXchgPtrT(&mRegionUnused, NULL, RegionRects *);
95 if (rgnRcts && rgnRcts->allocated < aCount)
96 {
97 RTMemFree (rgnRcts);
98 rgnRcts = NULL;
99 }
100 if (!rgnRcts)
101 {
102 ULONG allocated = RT_ALIGN_32(aCount + 1, 32);
103 allocated = RT_MAX (128, allocated);
104 rgnRcts = (RegionRects *)RTMemAlloc(RT_OFFSETOF(RegionRects, rcts[allocated]));
105 if (!rgnRcts)
106 return E_OUTOFMEMORY;
107 rgnRcts->allocated = allocated;
108 }
109 rgnRcts->used = 0;
110
111 QRegion reg;
112// printf ("Region rects follow...\n");
113 QRect vmScreenRect (0, 0, width(), height());
114 for (ULONG ind = 0; ind < aCount; ++ ind)
115 {
116 QRect rect;
117 rect.setLeft(rects->xLeft);
118 rect.setTop(rects->yTop);
119 /* QRect are inclusive */
120 rect.setRight(rects->xRight - 1);
121 rect.setBottom(rects->yBottom - 1);
122
123 /* The rect should intersect with the vm screen. */
124 rect = vmScreenRect.intersect(rect);
125 ++ rects;
126 /* Make sure only valid rects are distributed */
127 /* todo: Test if the other framebuffer implementation have the same
128 * problem with invalid rects (In Linux/Windows) */
129 if (rect.isValid() &&
130 rect.width() > 0 && rect.height() > 0)
131 reg += rect;
132 else
133 continue;
134
135 CGRect *cgRct = &rgnRcts->rcts[rgnRcts->used];
136 cgRct->origin.x = rect.x();
137 cgRct->origin.y = height() - rect.y() - rect.height();
138 cgRct->size.width = rect.width();
139 cgRct->size.height = rect.height();
140// printf ("Region rect[%d - %d]: %d %d %d %d\n", rgnRcts->used, aCount, rect.x(), rect.y(), rect.height(), rect.width());
141 rgnRcts->used++;
142 }
143// printf ("..................................\n");
144
145 RegionRects *pOld = ASMAtomicXchgPtrT(&mRegion, rgnRcts, RegionRects *);
146 if ( pOld
147 && !ASMAtomicCmpXchgPtr(&mRegionUnused, pOld, NULL))
148 RTMemFree(pOld);
149
150 QApplication::postEvent(m_pMachineView, new UISetRegionEvent (reg));
151
152 return S_OK;
153}
154
155void UIFrameBufferQuartz2D::paintEvent(QPaintEvent *aEvent)
156{
157 /* For debugging /Developer/Applications/Performance Tools/Quartz
158 * Debug.app is a nice tool to see which parts of the screen are
159 * updated.*/
160 Assert(m_image);
161
162 QWidget* viewport = m_pMachineView->viewport();
163 Assert(VALID_PTR(viewport));
164 /* Get the dimensions of the viewport */
165 CGRect viewRect = ::darwinToCGRect(viewport->geometry());
166 /* Get the context of this window from Qt */
167 CGContextRef ctx = ::darwinToCGContextRef(viewport);
168 Assert(VALID_PTR(ctx));
169
170 /* Flip the context */
171 CGContextTranslateCTM(ctx, 0, viewRect.size.height);
172 CGContextScaleCTM(ctx, 1.0, -1.0);
173
174 /* We handle the seamless mode as a special case. */
175 if (m_pMachineLogic->visualStateType() == UIVisualStateType_Seamless)
176 {
177 /* Here we paint the windows without any wallpaper.
178 * So the background would be set transparently. */
179
180 /* Create a subimage of the current view.
181 * Currently this subimage is the whole screen. */
182 CGImageRef subImage;
183 if (!m_pMachineView->pauseShot().isNull())
184 {
185 CGImageRef pauseImg = ::darwinToCGImageRef(&m_pMachineView->pauseShot());
186 subImage = CGImageCreateWithImageInRect(pauseImg, CGRectMake(m_pMachineView->contentsX(), m_pMachineView->contentsY(), m_pMachineView->visibleWidth(), m_pMachineView->visibleHeight()));
187 CGImageRelease(pauseImg);
188 }
189 else
190 {
191#ifdef RT_ARCH_AMD64
192 /* Not sure who to blame, but it seems on 64bit there goes
193 * something terrible wrong (on a second monitor) when directly
194 * using CGImageCreateWithImageInRect without making a copy. We saw
195 * something like this already with the scale mode. */
196 CGImageRef tmpImage = CGImageCreateWithImageInRect(m_image, CGRectMake(m_pMachineView->contentsX(), m_pMachineView->contentsY(), m_pMachineView->visibleWidth(), m_pMachineView->visibleHeight()));
197 subImage = CGImageCreateCopy(tmpImage);
198 CGImageRelease(tmpImage);
199#else
200 subImage = CGImageCreateWithImageInRect(m_image, CGRectMake(m_pMachineView->contentsX(), m_pMachineView->contentsY(), m_pMachineView->visibleWidth(), m_pMachineView->visibleHeight()));
201#endif
202 }
203 Assert(VALID_PTR(subImage));
204 /* Clear the background (Make the rect fully transparent) */
205 CGContextClearRect(ctx, viewRect);
206#ifdef OVERLAY_CLIPRECTS
207 CGContextSetRGBFillColor(ctx, 0.0, 0.0, 5.0, 0.7);
208 CGContextFillRect(ctx, viewRect);
209#endif
210#ifdef COMP_WITH_SHADOW
211 /* Enable shadows */
212 CGContextSetShadow(ctx, CGSizeMake (10, -10), 10);
213 CGContextBeginTransparencyLayer(ctx, NULL);
214#endif
215 /* Grab the current visible region. */
216 RegionRects *rgnRcts = ASMAtomicXchgPtrT(&mRegion, NULL, RegionRects *);
217 if (rgnRcts)
218 {
219 if (rgnRcts->used > 0)
220 {
221 /* Add the clipping rects all at once. They are defined in
222 * SetVisibleRegion. */
223 CGContextBeginPath(ctx);
224 CGContextAddRects(ctx, rgnRcts->rcts, rgnRcts->used);
225 /* Now convert the path to a clipping path. */
226 CGContextClip(ctx);
227 }
228 /* Put back the visible region, free if we cannot (2+ SetVisibleRegion calls). */
229 if ( !ASMAtomicCmpXchgPtr(&mRegion, rgnRcts, NULL)
230 && !ASMAtomicCmpXchgPtr(&mRegionUnused, rgnRcts, NULL))
231 RTMemFree(rgnRcts);
232 }
233 /* In any case clip the drawing to the view window */
234 CGContextClipToRect(ctx, viewRect);
235 /* At this point draw the real vm image */
236 CGContextDrawImage(ctx, ::darwinFlipCGRect (viewRect, viewRect.size.height), subImage);
237#ifdef COMP_WITH_SHADOW
238 CGContextEndTransparencyLayer(ctx);
239#endif
240 CGImageRelease(subImage);
241#ifdef OVERLAY_CLIPRECTS
242 if (rgnRcts && rgnRcts->used > 0)
243 {
244 CGContextBeginPath(ctx);
245 CGContextAddRects(ctx, rgnRcts->rcts, rgnRcts->used);
246 CGContextSetRGBStrokeColor(ctx, 1.0, 0.0, 0.0, 0.7);
247 CGContextDrawPath(ctx, kCGPathStroke);
248 }
249 CGContextSetRGBStrokeColor(ctx, 0.0, 1.0, 0.0, 0.7);
250 CGContextStrokeRect(ctx, viewRect);
251#endif
252 }
253 else if ( m_pMachineLogic->visualStateType() == UIVisualStateType_Scale
254 && m_scaledSize.isValid())
255 {
256 /* Here we paint if we didn't care about any masks */
257
258 /* Create a subimage of the current view in the size
259 * of the bounding box of the current paint event */
260 CGRect ir = ::darwinToCGRect(aEvent->rect());
261 CGRect is = CGRectMake(CGRectGetMinX(ir), CGRectGetMinY(ir), CGRectGetWidth(ir), CGRectGetHeight(ir));
262
263 double iw = 1.0;
264 double ih = 1.0;
265 CGImageRef subImage = 0;
266 if (!m_pMachineView->pauseShot().isNull())
267 {
268 /* The pause image is already scaled. Maybe we should change that,
269 * to have a more uniform behavior and the different
270 * implementations use there own scaling algorithm. */
271 CGImageRef pauseImg = ::darwinToCGImageRef(&m_pMachineView->pauseShot());
272 subImage = CGImageCreateWithImageInRect(pauseImg, is);
273 CGImageRelease(pauseImg);
274 }
275 else
276 {
277 /* Scale all values needed for the image drawing. */
278 iw = (double)CGImageGetWidth(m_image) / m_scaledSize.width();
279 ih = (double)CGImageGetHeight(m_image) / m_scaledSize.height();
280 /* We make sure the image is always bigger than requested to
281 * compensate rounding errors. */
282 /* Round down */
283 is.origin.x = (int)(is.origin.x * iw);
284 is.origin.y = (int)(is.origin.y * ih);
285 /* Round up */
286 is.size.width = (int)(is.size.width * iw) + 2;
287 is.size.height = (int)(is.size.height * ih) + 2;
288 /* Make sure the size is within the image boundaries */
289 CGRect is1 = CGRectIntersection(is, CGRectMake(0, 0, CGImageGetWidth(m_image), CGImageGetHeight(m_image)));
290 /* Cause we probably changed the rectangle to update in the origin
291 * coordinate system, we have to recalculate the update rectangle
292 * for the screen coordinates as well. Please note that this has to
293 * be in double precision. */
294 ir.origin.x = is1.origin.x / iw;
295 ir.origin.y = is1.origin.y / ih;
296 ir.size.width = is1.size.width / iw;
297 ir.size.height = is1.size.height / ih;
298 /* Create the sub image. Note: The copy step is necessary otherwise
299 * strange things happens especially on Snow Leopard. No idea why. */
300 CGImageRef tmpImage = CGImageCreateWithImageInRect(m_image, is1);
301 subImage = CGImageCreateCopy(tmpImage);
302 CGImageRelease(tmpImage);
303 }
304 if (subImage)
305 {
306 /* For more performance we set a clipping path of the regions given
307 * by this paint event. */
308 QVector<QRect> a = aEvent->region().rects();
309 if (!a.isEmpty())
310 {
311 CGContextBeginPath(ctx);
312 /* Add all region rects to the current context as path components */
313 for (int i = 0; i < a.size(); ++i)
314 CGContextAddRect(ctx, ::darwinFlipCGRect(::darwinToCGRect(a.at(i)), CGRectGetHeight(viewRect)));
315 /* Now convert the path to a clipping path. */
316 CGContextClip(ctx);
317 }
318 /* In any case clip the drawing to the view window */
319 CGContextClipToRect(ctx, viewRect);
320 /* Turn the high interpolation quality on. */
321 CGContextSetInterpolationQuality(ctx, kCGInterpolationHigh);
322 /* Draw the image. */
323 CGContextDrawImage(ctx, ::darwinFlipCGRect(ir, CGRectGetHeight(viewRect)), subImage);
324 CGImageRelease(subImage);
325 }
326 }
327 else
328 {
329 /* Here we paint if we didn't care about any masks */
330
331 /* Create a subimage of the current view in the size
332 * of the bounding box of the current paint event */
333 QRect ir = aEvent->rect();
334 QRect is = QRect(ir.x() + m_pMachineView->contentsX(), ir.y() + m_pMachineView->contentsY(), ir.width(), ir.height());
335 CGImageRef subImage = 0;
336 if (!m_pMachineView->pauseShot().isNull())
337 {
338 CGImageRef pauseImg = ::darwinToCGImageRef(&m_pMachineView->pauseShot());
339 subImage = CGImageCreateWithImageInRect(pauseImg, ::darwinToCGRect(is));
340 CGImageRelease(pauseImg);
341 }
342 else
343 {
344#ifdef RT_ARCH_AMD64
345 /* Not sure who to blame, but it seems on 64bit there goes
346 * something terrible wrong (on a second monitor) when directly
347 * using CGImageCreateWithImageInRect without making a copy. We saw
348 * something like this already with the scale mode. */
349 CGImageRef tmpImage = CGImageCreateWithImageInRect(m_image, ::darwinToCGRect(is));
350 subImage = CGImageCreateCopy(tmpImage);
351 CGImageRelease(tmpImage);
352#else
353 subImage = CGImageCreateWithImageInRect(m_image, ::darwinToCGRect(is));
354#endif
355 }
356 if (subImage)
357 {
358 /* Ok, for more performance we set a clipping path of the
359 * regions given by this paint event. */
360 QVector<QRect> a = aEvent->region().rects();
361 if (!a.isEmpty())
362 {
363 CGContextBeginPath (ctx);
364 /* Add all region rects to the current context as path components */
365 for (int i = 0; i < a.size(); ++i)
366 CGContextAddRect(ctx, ::darwinFlipCGRect(::darwinToCGRect(a[i]), viewRect.size.height));
367 /* Now convert the path to a clipping path. */
368 CGContextClip(ctx);
369 }
370
371 /* In any case clip the drawing to the view window */
372 CGContextClipToRect(ctx, viewRect);
373
374 /* At this point draw the real vm image */
375 CGContextDrawImage(ctx, ::darwinFlipCGRect(::darwinToCGRect(ir), viewRect.size.height), subImage);
376 CGImageRelease(subImage);
377 }
378 }
379}
380
381void UIFrameBufferQuartz2D::resizeEvent(UIResizeEvent *aEvent)
382{
383#if 0
384 printf ("fmt=%lu, vram=%X, bpp=%lu, bpl=%lu, width=%lu, height=%lu\n",
385 aEvent->pixelFormat(), (unsigned int)aEvent->VRAM(),
386 aEvent->bitsPerPixel(), aEvent->bytesPerLine(),
387 aEvent->width(), aEvent->height());
388#endif
389
390 /* Clean out old stuff */
391 clean();
392
393 m_width = aEvent->width();
394 m_height = aEvent->height();
395
396 bool remind = false;
397
398 /* We need a color space in any case */
399 CGColorSpaceRef cs = CGColorSpaceCreateDeviceRGB();
400 /* Check if we support the pixel format/colordepth and can use the guest VRAM directly.
401 * Mac OS X supports 16 bit also but not in the 565 mode. So we could use
402 * 32 bit only. */
403 if ( aEvent->pixelFormat() == FramebufferPixelFormat_FOURCC_RGB
404 && aEvent->bitsPerPixel() == 32)
405 {
406// printf ("VRAM\n");
407 /* Create the image copy of the framebuffer */
408 CGDataProviderRef dp = CGDataProviderCreateWithData(NULL, aEvent->VRAM(), aEvent->bytesPerLine() * m_height, NULL);
409 m_image = CGImageCreate(m_width, m_height, 8, aEvent->bitsPerPixel(), aEvent->bytesPerLine(), cs,
410 kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, dp, 0, false,
411 kCGRenderingIntentDefault);
412 m_pDataAddress = aEvent->VRAM();
413 CGDataProviderRelease (dp);
414 }
415 else
416 {
417 remind = true;
418// printf ("No VRAM\n");
419 /* Create the memory we need for our image copy
420 * Read somewhere that an alignment of 16 is
421 * best for optimal performance. So why not. */
422// int bitmapBytesPerRow = RT_ALIGN (m_width * 4, 16);
423 int bitmapBytesPerRow = m_width * 4;
424 int bitmapByteCount = (bitmapBytesPerRow * m_height);
425 m_pBitmapData = RTMemAllocZ(bitmapByteCount);
426 CGDataProviderRef dp = CGDataProviderCreateWithData(NULL, m_pBitmapData, bitmapByteCount, NULL);
427 m_image = CGImageCreate(m_width, m_height, 8, 32, bitmapBytesPerRow, cs,
428 kCGImageAlphaNoneSkipFirst | kCGBitmapByteOrder32Little, dp, 0, false,
429 kCGRenderingIntentDefault);
430 m_pDataAddress = static_cast<uchar*>(m_pBitmapData);
431 CGDataProviderRelease(dp);
432 }
433 CGColorSpaceRelease(cs);
434#ifdef VBOX_WITH_ICHAT_THEATER
435 setImageRef(m_image);
436#endif
437
438// if (remind)
439// vboxProblem().remindAboutWrongColorDepth(aEvent->bitsPerPixel(), 32);
440}
441
442void UIFrameBufferQuartz2D::clean()
443{
444 if (m_image)
445 {
446 CGImageRelease(m_image);
447 m_image = NULL;
448 }
449 if (m_pBitmapData)
450 {
451 RTMemFree(m_pBitmapData);
452 m_pBitmapData = NULL;
453 }
454 if (mRegion)
455 {
456 RTMemFree((void *)mRegion);
457 mRegion = NULL;
458 }
459 if (mRegionUnused)
460 {
461 RTMemFree((void *)mRegionUnused);
462 mRegionUnused = NULL;
463 }
464}
465
466#ifdef VBOX_WITH_VIDEOHWACCEL
467void UIFrameBufferQuartz2D::setView(UIMachineView *pView)
468{
469 if (pView)
470 m_pMachineLogic = pView->machineLogic();
471
472 UIFrameBuffer::setView(pView);
473}
474#endif
475
476#endif /* defined (VBOX_GUI_USE_QUARTZ2D) */
477
Note: See TracBrowser for help on using the repository browser.

© 2023 Oracle
ContactPrivacy policyTerms of Use