VirtualBox

source: vbox/trunk/src/VBox/Main/DisplayImpl.cpp@ 429

Last change on this file since 429 was 422, checked in by vboxsync, 18 years ago

Assertion details.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.6 KB
Line 
1/** @file
2 *
3 * VirtualBox COM class implementation
4 */
5
6/*
7 * Copyright (C) 2006 InnoTek Systemberatung GmbH
8 *
9 * This file is part of VirtualBox Open Source Edition (OSE), as
10 * available from http://www.virtualbox.org. This file is free software;
11 * you can redistribute it and/or modify it under the terms of the GNU
12 * General Public License as published by the Free Software Foundation,
13 * in version 2 as it comes in the "COPYING" file of the VirtualBox OSE
14 * distribution. VirtualBox OSE is distributed in the hope that it will
15 * be useful, but WITHOUT ANY WARRANTY of any kind.
16 *
17 * If you received this file as part of a commercial VirtualBox
18 * distribution, then only the terms of your commercial VirtualBox
19 * license agreement apply instead of the previous paragraph.
20 */
21
22#include "DisplayImpl.h"
23#include "FramebufferImpl.h"
24#include "ConsoleImpl.h"
25#include "ConsoleVRDPServer.h"
26#include "VMMDev.h"
27
28#include "Logging.h"
29
30#include <iprt/semaphore.h>
31#include <iprt/thread.h>
32#include <iprt/asm.h>
33
34#include <VBox/pdm.h>
35#include <VBox/cfgm.h>
36#include <VBox/err.h>
37#include <VBox/vm.h>
38
39/**
40 * Display driver instance data.
41 */
42typedef struct DRVMAINDISPLAY
43{
44 /** Pointer to the display object. */
45 Display *pDisplay;
46 /** Pointer to the driver instance structure. */
47 PPDMDRVINS pDrvIns;
48 /** Pointer to the keyboard port interface of the driver/device above us. */
49 PPDMIDISPLAYPORT pUpPort;
50 /** Our display connector interface. */
51 PDMIDISPLAYCONNECTOR Connector;
52} DRVMAINDISPLAY, *PDRVMAINDISPLAY;
53
54/** Converts PDMIDISPLAYCONNECTOR pointer to a DRVMAINDISPLAY pointer. */
55#define PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface) ( (PDRVMAINDISPLAY) ((uintptr_t)pInterface - RT_OFFSETOF(DRVMAINDISPLAY, Connector)) )
56
57#ifdef DEBUG_sunlover
58static STAMPROFILE StatDisplayRefresh;
59static int stam = 0;
60#endif /* DEBUG_sunlover */
61
62// constructor / destructor
63/////////////////////////////////////////////////////////////////////////////
64
65HRESULT Display::FinalConstruct()
66{
67 mpVbvaMemory = NULL;
68 mfVideoAccelEnabled = false;
69 mfVideoAccelVRDP = false;
70 mfu32SupportedOrders = 0;
71 mcVideoAccelVRDPRefs = 0;
72
73 mpPendingVbvaMemory = NULL;
74 mfPendingVideoAccelEnable = false;
75
76 mfMachineRunning = false;
77
78 mpu8VbvaPartial = NULL;
79 mcbVbvaPartial = 0;
80
81 mParent = NULL;
82 mpDrv = NULL;
83 mpVMMDev = NULL;
84 mfVMMDevInited = false;
85 RTSemEventMultiCreate(&mResizeSem);
86 RTSemEventMultiCreate(&mUpdateSem);
87
88 mLastAddress = NULL;
89 mLastLineSize = 0;
90 mLastColorDepth = 0,
91 mLastWidth = 0;
92 mLastHeight = 0;
93
94 return S_OK;
95}
96
97void Display::FinalRelease()
98{
99 if (isReady())
100 uninit();
101}
102
103// public initializer/uninitializer for internal purposes only
104/////////////////////////////////////////////////////////////////////////////
105
106/**
107 * Initializes the display object.
108 *
109 * @returns COM result indicator
110 * @param parent handle of our parent object
111 * @param qemuConsoleData address of common console data structure
112 */
113HRESULT Display::init (Console *parent)
114{
115 LogFlowFunc (("isReady=%d", isReady()));
116
117 ComAssertRet (parent, E_INVALIDARG);
118
119 AutoLock alock (this);
120 ComAssertRet (!isReady(), E_UNEXPECTED);
121
122 mParent = parent;
123
124 /* reset the event sems */
125 RTSemEventMultiReset(mResizeSem);
126 RTSemEventMultiReset(mUpdateSem);
127
128 // by default, we have an internal framebuffer which is
129 // NULL, i.e. a black hole for no display output
130 mFramebuffer = 0;
131 mInternalFramebuffer = true;
132 mFramebufferOpened = false;
133 mSupportedAccelOps = 0;
134
135 mParent->RegisterCallback(this);
136
137 setReady (true);
138 return S_OK;
139}
140
141/**
142 * Uninitializes the instance and sets the ready flag to FALSE.
143 * Called either from FinalRelease() or by the parent when it gets destroyed.
144 */
145void Display::uninit()
146{
147 LogFlowFunc (("isReady=%d\n", isReady()));
148
149 AutoLock alock (this);
150 AssertReturn (isReady(), (void) 0);
151
152 mFramebuffer.setNull();
153 RTSemEventMultiDestroy(mResizeSem);
154 RTSemEventMultiDestroy(mUpdateSem);
155
156 if (mParent)
157 {
158 mParent->UnregisterCallback(this);
159 }
160
161 if (mpDrv)
162 mpDrv->pDisplay = NULL;
163 mpDrv = NULL;
164 mpVMMDev = NULL;
165 mfVMMDevInited = true;
166
167 setReady (false);
168}
169
170// IConsoleCallback method
171STDMETHODIMP Display::OnStateChange(MachineState_T machineState)
172{
173 if (machineState == MachineState_Running)
174 {
175 LogFlowFunc (("Machine running\n"));
176
177 mfMachineRunning = true;
178 }
179 else
180 {
181 mfMachineRunning = false;
182 }
183 return S_OK;
184}
185
186// public methods only for internal purposes
187/////////////////////////////////////////////////////////////////////////////
188
189void Display::callFramebufferResize (FramebufferPixelFormat_T pixelFormat, void *pvVRAM, uint32_t cbLine, int w, int h)
190{
191 Assert (!mFramebuffer.isNull());
192
193 /* Call the framebuffer to try and set required pixelFormat. */
194 BOOL finished = TRUE;
195
196 /* Reset the event here. It could be signalled before it gets to after 'if (!finished)' */
197 RTSemEventMultiReset(mResizeSem);
198
199 mFramebuffer->RequestResize (pixelFormat, (ULONG)pvVRAM, cbLine, w, h, &finished);
200
201 if (!finished)
202 {
203 LogFlowFunc (("External framebuffer wants us to wait!\n"));
204
205 /* Note: The previously obtained framebuffer lock is preserved.
206 * The EMT keeps the lock until the resize process completes.
207 */
208
209 /* The framebuffer needs more time to process
210 * the event so we have to halt the VM until it's done.
211 */
212 RTSemEventMultiWait(mResizeSem, RT_INDEFINITE_WAIT); /** @todo r=bird: this is a serious deadlock point, where EMT is stuck while the main thread is doing VMR3Req for some reason. */
213 }
214}
215
216/**
217 * Handles display resize event.
218 *
219 * @param w New display width
220 * @param h New display height
221 *
222 * @thread EMT
223 */
224void Display::handleDisplayResize (uint32_t bpp, void *pvVRAM, uint32_t cbLine, int w, int h)
225{
226 LogRel (("Display::handleDisplayResize(): pvVRAM=%p w=%d h=%d bpp=%d cbLine=0x%X\n",
227 pvVRAM, w, h, bpp, cbLine));
228
229 /* If there is no framebuffer, this call is not interesting. */
230 if (mFramebuffer.isNull())
231 {
232 return;
233 }
234
235 mLastAddress = pvVRAM;
236 mLastLineSize = cbLine;
237 mLastColorDepth = bpp,
238 mLastWidth = w;
239 mLastHeight = h;
240
241 FramebufferPixelFormat_T pixelFormat;
242
243 switch (bpp)
244 {
245 case 32: pixelFormat = FramebufferPixelFormat_PixelFormatRGB32; break;
246 case 24: pixelFormat = FramebufferPixelFormat_PixelFormatRGB24; break;
247 case 16: pixelFormat = FramebufferPixelFormat_PixelFormatRGB16; break;
248 default: pixelFormat = FramebufferPixelFormat_PixelFormatDefault; cbLine = 0;
249 }
250
251 mFramebuffer->Lock();
252
253 callFramebufferResize (pixelFormat, pvVRAM, cbLine, w, h);
254
255 /* Indicated whether the FB should be set to a default format in case something went wrong. */
256 bool fResetFormat = false;
257
258 /* Check the framebuffer pixel format. */
259 FramebufferPixelFormat_T newPixelFormat;
260
261 mFramebuffer->COMGETTER(PixelFormat) (&newPixelFormat);
262
263 if (newPixelFormat == FramebufferPixelFormat_PixelFormatDefault)
264 {
265 mpDrv->pUpPort->pfnSetupVRAM (mpDrv->pUpPort, NULL, 0);
266 }
267 else
268 {
269 Assert(pixelFormat == newPixelFormat);
270
271 /* Framebuffer successfully set the requested format. */
272 ULONG address;
273 mFramebuffer->COMGETTER(Address) (&address);
274
275 ULONG lineSize;
276 mFramebuffer->COMGETTER(LineSize) (&lineSize);
277 Assert(lineSize == cbLine);
278
279 /* Trust Framebuffer implementation that it allocated a buffer,
280 * which size is a multiple of PAGE_SIZE.
281 */
282 uint32_t cbBuffer = cbLine * h;
283
284 int rc = mpDrv->pUpPort->pfnSetupVRAM (mpDrv->pUpPort, (void *)address, cbBuffer);
285
286 if (VBOX_FAILURE(rc))
287 {
288 LogFlowFunc (("pfnSetupVRAM returns %Vrc\n", rc));
289 fResetFormat = true;
290 }
291
292#ifdef DEBUG_sunlover
293 if (!stam)
294 {
295 /* protect mpVM */
296 Console::SafeVMPtr pVM (mParent);
297 AssertComRC (pVM.rc());
298
299 STAM_REG(pVM, &StatDisplayRefresh, STAMTYPE_PROFILE, "/PROF/Display/Refresh", STAMUNIT_TICKS_PER_CALL, "Time spent in EMT for display updates.");
300 stam = 1;
301 }
302#endif /* DEBUG_sunlover */
303 }
304
305 if (fResetFormat)
306 {
307 callFramebufferResize (FramebufferPixelFormat_PixelFormatDefault, NULL, 0, w, h);
308 }
309
310 updateDisplayData();
311
312 LogFlowFunc (("Calling VRDP\n"));
313
314 mParent->consoleVRDPServer()->SendResize();
315
316 mFramebuffer->Unlock();
317
318 return;
319}
320
321static void checkCoordBounds (int *px, int *py, int *pw, int *ph, int cx, int cy)
322{
323 /* Correct negative x and y coordinates. */
324 if (*px < 0)
325 {
326 *px += *pw; /* Compute xRight which is also the new width. */
327
328 *pw = (*px < 0)? 0: *px;
329
330 *px = 0;
331 }
332
333 if (*py < 0)
334 {
335 *py += *ph; /* Compute xBottom, which is also the new height. */
336
337 *ph = (*py < 0)? 0: *py;
338
339 *py = 0;
340 }
341
342 /* Also check if coords are greater than the display resolution. */
343 if (*px + *pw > cx)
344 {
345 *pw = cx > *px? cx - *px: 0;
346 }
347
348 if (*py + *ph > cy)
349 {
350 *ph = cy > *py? cy - *py: 0;
351 }
352}
353
354/**
355 * Handles display update event.
356 *
357 * @param x Update area x coordinate
358 * @param y Update area y coordinate
359 * @param w Update area width
360 * @param h Update area height
361 *
362 * @thread EMT
363 */
364void Display::handleDisplayUpdate (int x, int y, int w, int h)
365{
366 // if there is no framebuffer, this call is not interesting
367 if (mFramebuffer.isNull())
368 return;
369
370 mFramebuffer->Lock();
371
372#ifdef DEBUG_sunlover
373 LogFlowFunc (("%d,%d %dx%d (%d,%d)\n",
374 x, y, w, h, mpDrv->Connector.cx, mpDrv->Connector.cy));
375#endif /* DEBUG_sunlover */
376
377 checkCoordBounds (&x, &y, &w, &h, mpDrv->Connector.cx, mpDrv->Connector.cy);
378
379#ifdef DEBUG_sunlover
380 LogFlowFunc (("%d,%d %dx%d (checked)\n", x, y, w, h));
381#endif /* DEBUG_sunlover */
382
383 /* special processing for the internal framebuffer */
384 if (mInternalFramebuffer)
385 {
386 mFramebuffer->Unlock();
387 } else
388 {
389 /* callback into the framebuffer to notify it */
390 BOOL finished = FALSE;
391
392 RTSemEventMultiReset(mUpdateSem);
393
394 mFramebuffer->NotifyUpdate(x, y, w, h, &finished);
395
396 if (!finished)
397 {
398 /*
399 * the framebuffer needs more time to process
400 * the event so we have to halt the VM until it's done
401 */
402 mFramebuffer->Unlock();
403 RTSemEventMultiWait(mUpdateSem, RT_INDEFINITE_WAIT);
404 } else
405 {
406 mFramebuffer->Unlock();
407 }
408
409 if (!mfVideoAccelEnabled)
410 {
411 /* When VBVA is enabled, the VRDP server is informed in the VideoAccelFlush.
412 * Inform the server here only if VBVA is disabled.
413 */
414 mParent->consoleVRDPServer()->SendUpdateBitmap(x, y, w, h);
415 }
416 }
417 return;
418}
419
420typedef struct _VBVADIRTYREGION
421{
422 /* Copies of object's pointers used by vbvaRgn functions. */
423 IFramebuffer *pFramebuffer;
424 Display *pDisplay;
425 PPDMIDISPLAYPORT pPort;
426
427 /* Merged rectangles. */
428 int32_t xLeft;
429 int32_t xRight;
430 int32_t yTop;
431 int32_t yBottom;
432
433} VBVADIRTYREGION;
434
435static void vbvaRgnInit (VBVADIRTYREGION *prgn, IFramebuffer *pfb, Display *pd, PPDMIDISPLAYPORT pp)
436{
437 memset (prgn, 0, sizeof (VBVADIRTYREGION));
438
439 prgn->pFramebuffer = pfb;
440 prgn->pDisplay = pd;
441 prgn->pPort = pp;
442
443 return;
444}
445
446static void vbvaRgnDirtyRect (VBVADIRTYREGION *prgn, VBVACMDHDR *phdr)
447{
448 LogFlowFunc (("x = %d, y = %d, w = %d, h = %d\n",
449 phdr->x, phdr->y, phdr->w, phdr->h));
450
451 /*
452 * Here update rectangles are accumulated to form an update area.
453 * @todo
454 * Now the simpliest method is used which builds one rectangle that
455 * includes all update areas. A bit more advanced method can be
456 * employed here. The method should be fast however.
457 */
458 if (phdr->w == 0 || phdr->h == 0)
459 {
460 /* Empty rectangle. */
461 return;
462 }
463
464 int32_t xRight = phdr->x + phdr->w;
465 int32_t yBottom = phdr->y + phdr->h;
466
467 if (prgn->xRight == 0)
468 {
469 /* This is the first rectangle to be added. */
470 prgn->xLeft = phdr->x;
471 prgn->yTop = phdr->y;
472 prgn->xRight = xRight;
473 prgn->yBottom = yBottom;
474 }
475 else
476 {
477 /* Adjust region coordinates. */
478 if (prgn->xLeft > phdr->x)
479 {
480 prgn->xLeft = phdr->x;
481 }
482
483 if (prgn->yTop > phdr->y)
484 {
485 prgn->yTop = phdr->y;
486 }
487
488 if (prgn->xRight < xRight)
489 {
490 prgn->xRight = xRight;
491 }
492
493 if (prgn->yBottom < yBottom)
494 {
495 prgn->yBottom = yBottom;
496 }
497 }
498
499 return;
500}
501
502static void vbvaRgnUpdateFramebuffer (VBVADIRTYREGION *prgn)
503{
504 uint32_t w = prgn->xRight - prgn->xLeft;
505 uint32_t h = prgn->yBottom - prgn->yTop;
506
507 if (prgn->pFramebuffer && w != 0 && h != 0)
508 {
509 prgn->pPort->pfnUpdateDisplayRect (prgn->pPort, prgn->xLeft, prgn->yTop, w, h);
510 prgn->pDisplay->handleDisplayUpdate (prgn->xLeft, prgn->yTop, w, h);
511 }
512}
513
514static void vbvaSetMemoryFlags (VBVAMEMORY *pVbvaMemory,
515 bool fVideoAccelEnabled,
516 bool fVideoAccelVRDP,
517 uint32_t fu32SupportedOrders)
518{
519 if (pVbvaMemory)
520 {
521 /* This called only on changes in mode. So reset VRDP always. */
522 uint32_t fu32Flags = VBVA_F_MODE_VRDP_RESET;
523
524 if (fVideoAccelEnabled)
525 {
526 fu32Flags |= VBVA_F_MODE_ENABLED;
527
528 if (fVideoAccelVRDP)
529 {
530 fu32Flags |= VBVA_F_MODE_VRDP | VBVA_F_MODE_VRDP_ORDER_MASK;
531
532 pVbvaMemory->fu32SupportedOrders = fu32SupportedOrders;
533 }
534 }
535
536 pVbvaMemory->fu32ModeFlags = fu32Flags;
537 }
538}
539
540bool Display::VideoAccelAllowed (void)
541{
542 return true;
543}
544
545/**
546 * @thread EMT
547 */
548int Display::VideoAccelEnable (bool fEnable, VBVAMEMORY *pVbvaMemory)
549{
550 int rc = VINF_SUCCESS;
551
552 /* Called each time the guest wants to use acceleration,
553 * or when the VGA device disables acceleration,
554 * or when restoring the saved state with accel enabled.
555 *
556 * VGA device disables acceleration on each video mode change
557 * and on reset.
558 *
559 * Guest enabled acceleration at will. And it has to enable
560 * acceleration after a mode change.
561 */
562 LogFlowFunc (("mfVideoAccelEnabled = %d, fEnable = %d, pVbvaMemory = %p\n",
563 mfVideoAccelEnabled, fEnable, pVbvaMemory));
564
565 /* Strictly check parameters. Callers must not pass anything in the case. */
566 Assert((fEnable && pVbvaMemory) || (!fEnable && pVbvaMemory == NULL));
567
568 if (!VideoAccelAllowed ())
569 {
570 return VERR_NOT_SUPPORTED;
571 }
572
573 /*
574 * Verify that the VM is in running state. If it is not,
575 * then this must be postponed until it goes to running.
576 */
577 if (!mfMachineRunning)
578 {
579 Assert (!mfVideoAccelEnabled);
580
581 LogFlowFunc (("Machine is not yet running.\n"));
582
583 if (fEnable)
584 {
585 mfPendingVideoAccelEnable = fEnable;
586 mpPendingVbvaMemory = pVbvaMemory;
587 }
588
589 return rc;
590 }
591
592 /* Check that current status is not being changed */
593 if (mfVideoAccelEnabled == fEnable)
594 {
595 return rc;
596 }
597
598 if (mfVideoAccelEnabled)
599 {
600 /* Process any pending orders and empty the VBVA ring buffer. */
601 VideoAccelFlush ();
602 }
603
604 if (!fEnable && mpVbvaMemory)
605 {
606 mpVbvaMemory->fu32ModeFlags &= ~VBVA_F_MODE_ENABLED;
607 }
608
609 /* Safety precaution. There is no more VBVA until everything is setup! */
610 mpVbvaMemory = NULL;
611 mfVideoAccelEnabled = false;
612
613 /* Update entire display. */
614 mpDrv->pUpPort->pfnUpdateDisplayAll(mpDrv->pUpPort);
615
616 /* Everything OK. VBVA status can be changed. */
617
618 /* Notify the VMMDev, which saves VBVA status in the saved state,
619 * and needs to know current status.
620 */
621 PPDMIVMMDEVPORT pVMMDevPort = mParent->getVMMDev()->getVMMDevPort ();
622
623 if (pVMMDevPort)
624 {
625 pVMMDevPort->pfnVBVAChange (pVMMDevPort, fEnable);
626 }
627
628 if (fEnable)
629 {
630 mpVbvaMemory = pVbvaMemory;
631 mfVideoAccelEnabled = true;
632
633 /* Initialize the hardware memory. */
634 vbvaSetMemoryFlags (mpVbvaMemory, mfVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders);
635 mpVbvaMemory->off32Data = 0;
636 mpVbvaMemory->off32Free = 0;
637
638 memset (mpVbvaMemory->aRecords, 0, sizeof (mpVbvaMemory->aRecords));
639 mpVbvaMemory->indexRecordFirst = 0;
640 mpVbvaMemory->indexRecordFree = 0;
641
642 LogRel(("VBVA: Enabled.\n"));
643 }
644 else
645 {
646 LogRel(("VBVA: Disabled.\n"));
647 }
648
649 LogFlowFunc (("VideoAccelEnable: rc = %Vrc.\n", rc));
650
651 return rc;
652}
653
654#ifdef VBOX_VRDP
655#ifdef VRDP_MC
656/* Called always by one VRDP server thread. Can be thread-unsafe.
657 */
658void Display::VideoAccelVRDP (bool fEnable)
659{
660 /* Supporting all orders. */
661 uint32_t fu32SupportedOrders = ~0;
662
663 int c = fEnable?
664 ASMAtomicIncS32 (&mcVideoAccelVRDPRefs):
665 ASMAtomicDecS32 (&mcVideoAccelVRDPRefs);
666
667 Assert (c >= 0);
668
669 if (c == 0)
670 {
671 /* The last client has disconnected, and the accel can be
672 * disabled.
673 */
674 Assert (fEnable == false);
675
676 mfVideoAccelVRDP = false;
677 mfu32SupportedOrders = 0;
678
679 vbvaSetMemoryFlags (mpVbvaMemory, mfVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders);
680
681 LogRel(("VBVA: VRDP acceleration has been disabled.\n"));
682 }
683 else if ( c == 1
684 && !mfVideoAccelVRDP)
685 {
686 /* The first client has connected. Enable the accel.
687 */
688 Assert (fEnable == true);
689
690 mfVideoAccelVRDP = true;
691 /* Supporting all orders. */
692 mfu32SupportedOrders = ~0;
693
694 vbvaSetMemoryFlags (mpVbvaMemory, mfVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders);
695
696 LogRel(("VBVA: VRDP acceleration has been requested.\n"));
697 }
698 else
699 {
700 /* A client is connected or disconnected but there is no change in the
701 * accel state. It remains enabled.
702 */
703 Assert (mfVideoAccelVRDP == true);
704 }
705}
706#else
707void Display::VideoAccelVRDP (bool fEnable, uint32_t fu32SupportedOrders)
708{
709 Assert (mfVideoAccelVRDP != fEnable);
710
711 mfVideoAccelVRDP = fEnable;
712
713 if (fEnable)
714 {
715 mfu32SupportedOrders = fu32SupportedOrders;
716 }
717 else
718 {
719 mfu32SupportedOrders = 0;
720 }
721
722 vbvaSetMemoryFlags (mpVbvaMemory, mfVideoAccelEnabled, mfVideoAccelVRDP, mfu32SupportedOrders);
723
724 LogRel(("VBVA: VRDP acceleration has been %s.\n", fEnable? "requested": "disabled"));
725}
726#endif /* VRDP_MC */
727#endif /* VBOX_VRDP */
728
729static bool vbvaVerifyRingBuffer (VBVAMEMORY *pVbvaMemory)
730{
731 return true;
732}
733
734static void vbvaFetchBytes (VBVAMEMORY *pVbvaMemory, uint8_t *pu8Dst, uint32_t cbDst)
735{
736 if (cbDst >= VBVA_RING_BUFFER_SIZE)
737 {
738 AssertMsgFailed (("cbDst = 0x%08X, ring buffer size 0x%08X", cbDst, VBVA_RING_BUFFER_SIZE));
739 return;
740 }
741
742 uint32_t u32BytesTillBoundary = VBVA_RING_BUFFER_SIZE - pVbvaMemory->off32Data;
743 uint8_t *src = &pVbvaMemory->au8RingBuffer[pVbvaMemory->off32Data];
744 int32_t i32Diff = cbDst - u32BytesTillBoundary;
745
746 if (i32Diff <= 0)
747 {
748 /* Chunk will not cross buffer boundary. */
749 memcpy (pu8Dst, src, cbDst);
750 }
751 else
752 {
753 /* Chunk crosses buffer boundary. */
754 memcpy (pu8Dst, src, u32BytesTillBoundary);
755 memcpy (pu8Dst + u32BytesTillBoundary, &pVbvaMemory->au8RingBuffer[0], i32Diff);
756 }
757
758 /* Advance data offset. */
759 pVbvaMemory->off32Data = (pVbvaMemory->off32Data + cbDst) % VBVA_RING_BUFFER_SIZE;
760
761 return;
762}
763
764
765static bool vbvaPartialRead (uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, VBVAMEMORY *pVbvaMemory)
766{
767 uint8_t *pu8New;
768
769 LogFlow(("MAIN::DisplayImpl::vbvaPartialRead: p = %p, cb = %d, cbRecord 0x%08X\n",
770 *ppu8, *pcb, cbRecord));
771
772 if (*ppu8)
773 {
774 Assert (*pcb);
775 pu8New = (uint8_t *)RTMemRealloc (*ppu8, cbRecord);
776 }
777 else
778 {
779 Assert (!*pcb);
780 pu8New = (uint8_t *)RTMemAlloc (cbRecord);
781 }
782
783 if (!pu8New)
784 {
785 /* Memory allocation failed, fail the function. */
786 Log(("MAIN::vbvaPartialRead: failed to (re)alocate memory for partial record!!! cbRecord 0x%08X\n",
787 cbRecord));
788
789 if (*ppu8)
790 {
791 RTMemFree (*ppu8);
792 }
793
794 *ppu8 = NULL;
795 *pcb = 0;
796
797 return false;
798 }
799
800 /* Fetch data from the ring buffer. */
801 vbvaFetchBytes (pVbvaMemory, pu8New + *pcb, cbRecord - *pcb);
802
803 *ppu8 = pu8New;
804 *pcb = cbRecord;
805
806 return true;
807}
808
809/* For contiguous chunks just return the address in the buffer.
810 * For crossing boundary - allocate a buffer from heap.
811 */
812bool Display::vbvaFetchCmd (VBVACMDHDR **ppHdr, uint32_t *pcbCmd)
813{
814 uint32_t indexRecordFirst = mpVbvaMemory->indexRecordFirst;
815 uint32_t indexRecordFree = mpVbvaMemory->indexRecordFree;
816
817#ifdef DEBUG_sunlover
818 LogFlowFunc (("first = %d, free = %d\n",
819 indexRecordFirst, indexRecordFree));
820#endif /* DEBUG_sunlover */
821
822 if (!vbvaVerifyRingBuffer (mpVbvaMemory))
823 {
824 return false;
825 }
826
827 if (indexRecordFirst == indexRecordFree)
828 {
829 /* No records to process. Return without assigning output variables. */
830 return true;
831 }
832
833 VBVARECORD *pRecord = &mpVbvaMemory->aRecords[indexRecordFirst];
834
835#ifdef DEBUG_sunlover
836 LogFlowFunc (("cbRecord = 0x%08X\n", pRecord->cbRecord));
837#endif /* DEBUG_sunlover */
838
839 uint32_t cbRecord = pRecord->cbRecord & ~VBVA_F_RECORD_PARTIAL;
840
841 if (mcbVbvaPartial)
842 {
843 /* There is a partial read in process. Continue with it. */
844
845 Assert (mpu8VbvaPartial);
846
847 LogFlowFunc (("continue partial record mcbVbvaPartial = %d cbRecord 0x%08X, first = %d, free = %d\n",
848 mcbVbvaPartial, pRecord->cbRecord, indexRecordFirst, indexRecordFree));
849
850 if (cbRecord > mcbVbvaPartial)
851 {
852 /* New data has been added to the record. */
853 if (!vbvaPartialRead (&mpu8VbvaPartial, &mcbVbvaPartial, cbRecord, mpVbvaMemory))
854 {
855 return false;
856 }
857 }
858
859 if (!(pRecord->cbRecord & VBVA_F_RECORD_PARTIAL))
860 {
861 /* The record is completed by guest. Return it to the caller. */
862 *ppHdr = (VBVACMDHDR *)mpu8VbvaPartial;
863 *pcbCmd = mcbVbvaPartial;
864
865 mpu8VbvaPartial = NULL;
866 mcbVbvaPartial = 0;
867
868 /* Advance the record index. */
869 mpVbvaMemory->indexRecordFirst = (indexRecordFirst + 1) % VBVA_MAX_RECORDS;
870
871#ifdef DEBUG_sunlover
872 LogFlowFunc (("partial done ok, data = %d, free = %d\n",
873 mpVbvaMemory->off32Data, mpVbvaMemory->off32Free));
874#endif /* DEBUG_sunlover */
875 }
876
877 return true;
878 }
879
880 /* A new record need to be processed. */
881 if (pRecord->cbRecord & VBVA_F_RECORD_PARTIAL)
882 {
883 /* Current record is being written by guest. '=' is important here. */
884 if (cbRecord >= VBVA_RING_BUFFER_SIZE - VBVA_RING_BUFFER_THRESHOLD)
885 {
886 /* Partial read must be started. */
887 if (!vbvaPartialRead (&mpu8VbvaPartial, &mcbVbvaPartial, cbRecord, mpVbvaMemory))
888 {
889 return false;
890 }
891
892 LogFlowFunc (("started partial record mcbVbvaPartial = 0x%08X cbRecord 0x%08X, first = %d, free = %d\n",
893 mcbVbvaPartial, pRecord->cbRecord, indexRecordFirst, indexRecordFree));
894 }
895
896 return true;
897 }
898
899 /* Current record is complete. If it is not empty, process it. */
900 if (cbRecord)
901 {
902 /* The size of largest contiguos chunk in the ring biffer. */
903 uint32_t u32BytesTillBoundary = VBVA_RING_BUFFER_SIZE - mpVbvaMemory->off32Data;
904
905 /* The ring buffer pointer. */
906 uint8_t *au8RingBuffer = &mpVbvaMemory->au8RingBuffer[0];
907
908 /* The pointer to data in the ring buffer. */
909 uint8_t *src = &au8RingBuffer[mpVbvaMemory->off32Data];
910
911 /* Fetch or point the data. */
912 if (u32BytesTillBoundary >= cbRecord)
913 {
914 /* The command does not cross buffer boundary. Return address in the buffer. */
915 *ppHdr = (VBVACMDHDR *)src;
916
917 /* Advance data offset. */
918 mpVbvaMemory->off32Data = (mpVbvaMemory->off32Data + cbRecord) % VBVA_RING_BUFFER_SIZE;
919 }
920 else
921 {
922 /* The command crosses buffer boundary. Rare case, so not optimized. */
923 uint8_t *dst = (uint8_t *)RTMemAlloc (cbRecord);
924
925 if (!dst)
926 {
927 LogFlowFunc (("could not allocate %d bytes from heap!!!\n", cbRecord));
928 mpVbvaMemory->off32Data = (mpVbvaMemory->off32Data + cbRecord) % VBVA_RING_BUFFER_SIZE;
929 return false;
930 }
931
932 vbvaFetchBytes (mpVbvaMemory, dst, cbRecord);
933
934 *ppHdr = (VBVACMDHDR *)dst;
935
936#ifdef DEBUG_sunlover
937 LogFlowFunc (("Allocated from heap %p\n", dst));
938#endif /* DEBUG_sunlover */
939 }
940 }
941
942 *pcbCmd = cbRecord;
943
944 /* Advance the record index. */
945 mpVbvaMemory->indexRecordFirst = (indexRecordFirst + 1) % VBVA_MAX_RECORDS;
946
947#ifdef DEBUG_sunlover
948 LogFlowFunc (("done ok, data = %d, free = %d\n",
949 mpVbvaMemory->off32Data, mpVbvaMemory->off32Free));
950#endif /* DEBUG_sunlover */
951
952 return true;
953}
954
955void Display::vbvaReleaseCmd (VBVACMDHDR *pHdr, int32_t cbCmd)
956{
957 uint8_t *au8RingBuffer = mpVbvaMemory->au8RingBuffer;
958
959 if ( (uint8_t *)pHdr >= au8RingBuffer
960 && (uint8_t *)pHdr < &au8RingBuffer[VBVA_RING_BUFFER_SIZE])
961 {
962 /* The pointer is inside ring buffer. Must be continuous chunk. */
963 Assert (VBVA_RING_BUFFER_SIZE - ((uint8_t *)pHdr - au8RingBuffer) >= cbCmd);
964
965 /* Do nothing. */
966
967 Assert (!mpu8VbvaPartial && mcbVbvaPartial == 0);
968 }
969 else
970 {
971 /* The pointer is outside. It is then an allocated copy. */
972
973#ifdef DEBUG_sunlover
974 LogFlowFunc (("Free heap %p\n", pHdr));
975#endif /* DEBUG_sunlover */
976
977 if ((uint8_t *)pHdr == mpu8VbvaPartial)
978 {
979 mpu8VbvaPartial = NULL;
980 mcbVbvaPartial = 0;
981 }
982 else
983 {
984 Assert (!mpu8VbvaPartial && mcbVbvaPartial == 0);
985 }
986
987 RTMemFree (pHdr);
988 }
989
990 return;
991}
992
993
994/**
995 * Called regularly on the DisplayRefresh timer.
996 * Also on behalf of guest, when the ring buffer is full.
997 *
998 * @thread EMT
999 */
1000void Display::VideoAccelFlush (void)
1001{
1002#ifdef DEBUG_sunlover
1003 LogFlowFunc (("mfVideoAccelEnabled = %d\n", mfVideoAccelEnabled));
1004#endif /* DEBUG_sunlover */
1005
1006 if (!mfVideoAccelEnabled)
1007 {
1008 Log(("Display::VideoAccelFlush: called with disabled VBVA!!! Ignoring.\n"));
1009 return;
1010 }
1011
1012 /* Here VBVA is enabled and we have the accelerator memory pointer. */
1013 Assert(mpVbvaMemory);
1014
1015#ifdef DEBUG_sunlover
1016 LogFlowFunc (("indexRecordFirst = %d, indexRecordFree = %d, off32Data = %d, off32Free = %d\n",
1017 mpVbvaMemory->indexRecordFirst, mpVbvaMemory->indexRecordFree, mpVbvaMemory->off32Data, mpVbvaMemory->off32Free));
1018#endif /* DEBUG_sunlover */
1019
1020 /* Quick check for "nothing to update" case. */
1021 if (mpVbvaMemory->indexRecordFirst == mpVbvaMemory->indexRecordFree)
1022 {
1023 return;
1024 }
1025
1026 /* Process the ring buffer */
1027
1028 bool fFramebufferIsNull = mFramebuffer.isNull();
1029
1030 if (!fFramebufferIsNull)
1031 {
1032 mFramebuffer->Lock();
1033 }
1034
1035 /* Initialize dirty rectangles accumulator. */
1036 VBVADIRTYREGION rgn;
1037 vbvaRgnInit (&rgn, mFramebuffer, this, mpDrv->pUpPort);
1038
1039 for (;;)
1040 {
1041 VBVACMDHDR *phdr = NULL;
1042 uint32_t cbCmd = ~0;
1043
1044 /* Fetch the command data. */
1045 if (!vbvaFetchCmd (&phdr, &cbCmd))
1046 {
1047 Log(("Display::VideoAccelFlush: unable to fetch command. off32Data = %d, off32Free = %d. Disabling VBVA!!!\n",
1048 mpVbvaMemory->off32Data, mpVbvaMemory->off32Free));
1049
1050 /* Disable VBVA on those processing errors. */
1051 VideoAccelEnable (false, NULL);
1052
1053 break;
1054 }
1055
1056 if (cbCmd == uint32_t(~0))
1057 {
1058 /* No more commands yet in the queue. */
1059 break;
1060 }
1061
1062 if (cbCmd != 0 && !fFramebufferIsNull)
1063 {
1064#ifdef DEBUG_sunlover
1065 LogFlowFunc (("hdr: cbCmd = %d, x=%d, y=%d, w=%d, h=%d\n",
1066 cbCmd, phdr->x, phdr->y, phdr->w, phdr->h));
1067#endif /* DEBUG_sunlover */
1068
1069 /* Handle the command.
1070 *
1071 * Guest is responsible for updating the guest video memory.
1072 * The Windows guest does all drawing using Eng*.
1073 *
1074 * For local output, only dirty rectangle information is used
1075 * to update changed areas.
1076 *
1077 * Dirty rectangles are accumulated to exclude overlapping updates and
1078 * group small updates to a larger one.
1079 */
1080
1081 /* Accumulate the update. */
1082 vbvaRgnDirtyRect (&rgn, phdr);
1083
1084 /* Forward the command to VRDP server. */
1085 mParent->consoleVRDPServer()->SendUpdate (phdr, cbCmd);
1086 }
1087
1088 vbvaReleaseCmd (phdr, cbCmd);
1089 }
1090
1091 if (!fFramebufferIsNull)
1092 {
1093 mFramebuffer->Unlock ();
1094 }
1095
1096 /* Draw the framebuffer. */
1097 vbvaRgnUpdateFramebuffer (&rgn);
1098}
1099
1100
1101// IDisplay properties
1102/////////////////////////////////////////////////////////////////////////////
1103
1104/**
1105 * Returns the current display width in pixel
1106 *
1107 * @returns COM status code
1108 * @param width Address of result variable.
1109 */
1110STDMETHODIMP Display::COMGETTER(Width) (ULONG *width)
1111{
1112 if (!width)
1113 return E_POINTER;
1114
1115 AutoLock alock (this);
1116 CHECK_READY();
1117
1118 CHECK_CONSOLE_DRV (mpDrv);
1119
1120 *width = mpDrv->Connector.cx;
1121 return S_OK;
1122}
1123
1124/**
1125 * Returns the current display height in pixel
1126 *
1127 * @returns COM status code
1128 * @param height Address of result variable.
1129 */
1130STDMETHODIMP Display::COMGETTER(Height) (ULONG *height)
1131{
1132 if (!height)
1133 return E_POINTER;
1134
1135 AutoLock alock (this);
1136 CHECK_READY();
1137
1138 CHECK_CONSOLE_DRV (mpDrv);
1139
1140 *height = mpDrv->Connector.cy;
1141 return S_OK;
1142}
1143
1144/**
1145 * Returns the current display color depth in bits
1146 *
1147 * @returns COM status code
1148 * @param colorDepth Address of result variable.
1149 */
1150STDMETHODIMP Display::COMGETTER(ColorDepth) (ULONG *colorDepth)
1151{
1152 if (!colorDepth)
1153 return E_INVALIDARG;
1154
1155 AutoLock alock (this);
1156 CHECK_READY();
1157
1158 CHECK_CONSOLE_DRV (mpDrv);
1159
1160 uint32_t cBits = 0;
1161 int rc = mpDrv->pUpPort->pfnQueryColorDepth(mpDrv->pUpPort, &cBits);
1162 AssertRC(rc);
1163 *colorDepth = cBits;
1164 return S_OK;
1165}
1166
1167
1168// IDisplay methods
1169/////////////////////////////////////////////////////////////////////////////
1170
1171STDMETHODIMP Display::SetupInternalFramebuffer (ULONG depth)
1172{
1173 LogFlowFunc (("\n"));
1174
1175 AutoLock lock (this);
1176 CHECK_READY();
1177
1178 /*
1179 * Create an internal framebuffer only if depth is not zero. Otherwise, we
1180 * reset back to the "black hole" state as it was at Display construction.
1181 */
1182 ComPtr <IFramebuffer> frameBuf;
1183 if (depth)
1184 {
1185 ComObjPtr <InternalFramebuffer> internal;
1186 internal.createObject();
1187 internal->init (640, 480, depth);
1188 frameBuf = internal; // query interface
1189 }
1190
1191 Console::SafeVMPtrQuiet pVM (mParent);
1192 if (pVM.isOk())
1193 {
1194 /* send request to the EMT thread */
1195 PVMREQ pReq = NULL;
1196 int vrc = VMR3ReqCall (pVM, &pReq, RT_INDEFINITE_WAIT,
1197 (PFNRT) changeFramebuffer, 3,
1198 this, static_cast <IFramebuffer *> (frameBuf),
1199 true /* aInternal */);
1200 if (VBOX_SUCCESS (vrc))
1201 vrc = pReq->iStatus;
1202 VMR3ReqFree (pReq);
1203
1204 ComAssertRCRet (vrc, E_FAIL);
1205 }
1206 else
1207 {
1208 /* No VM is created (VM is powered off), do a direct call */
1209 int vrc = changeFramebuffer (this, frameBuf, true /* aInternal */);
1210 ComAssertRCRet (vrc, E_FAIL);
1211 }
1212
1213 return S_OK;
1214}
1215
1216STDMETHODIMP Display::LockFramebuffer (ULONG *address)
1217{
1218 if (!address)
1219 return E_POINTER;
1220
1221 AutoLock lock(this);
1222 CHECK_READY();
1223
1224 /* only allowed for internal framebuffers */
1225 if (mInternalFramebuffer && !mFramebufferOpened && !mFramebuffer.isNull())
1226 {
1227 CHECK_CONSOLE_DRV (mpDrv);
1228
1229 mFramebuffer->Lock();
1230 mFramebufferOpened = true;
1231 *address = (ULONG)mpDrv->Connector.pu8Data;
1232 return S_OK;
1233 }
1234
1235 return setError (E_FAIL,
1236 tr ("Framebuffer locking is allowed only for the internal framebuffer"));
1237}
1238
1239STDMETHODIMP Display::UnlockFramebuffer()
1240{
1241 AutoLock lock(this);
1242 CHECK_READY();
1243
1244 if (mFramebufferOpened)
1245 {
1246 CHECK_CONSOLE_DRV (mpDrv);
1247
1248 mFramebuffer->Unlock();
1249 mFramebufferOpened = false;
1250 return S_OK;
1251 }
1252
1253 return setError (E_FAIL,
1254 tr ("Framebuffer locking is allowed only for the internal framebuffer"));
1255}
1256
1257STDMETHODIMP Display::RegisterExternalFramebuffer (IFramebuffer *frameBuf)
1258{
1259 LogFlowFunc (("\n"));
1260
1261 if (!frameBuf)
1262 return E_POINTER;
1263
1264 AutoLock lock (this);
1265 CHECK_READY();
1266
1267 Console::SafeVMPtrQuiet pVM (mParent);
1268 if (pVM.isOk())
1269 {
1270 /* send request to the EMT thread */
1271 PVMREQ pReq = NULL;
1272 int vrc = VMR3ReqCall (pVM, &pReq, RT_INDEFINITE_WAIT,
1273 (PFNRT) changeFramebuffer, 3,
1274 this, frameBuf, false /* aInternal */);
1275 if (VBOX_SUCCESS (vrc))
1276 vrc = pReq->iStatus;
1277 VMR3ReqFree (pReq);
1278
1279 ComAssertRCRet (vrc, E_FAIL);
1280 }
1281 else
1282 {
1283 /* No VM is created (VM is powered off), do a direct call */
1284 int vrc = changeFramebuffer (this, frameBuf, false /* aInternal */);
1285 ComAssertRCRet (vrc, E_FAIL);
1286 }
1287
1288 return S_OK;
1289}
1290
1291STDMETHODIMP Display::SetVideoModeHint(ULONG aWidth, ULONG aHeight, ULONG aColorDepth)
1292{
1293 AutoLock lock(this);
1294 CHECK_READY();
1295
1296 CHECK_CONSOLE_DRV (mpDrv);
1297
1298 /*
1299 * Do some rough checks for valid input
1300 */
1301 ULONG width = aWidth;
1302 if (!width)
1303 width = mpDrv->Connector.cx;
1304 ULONG height = aHeight;
1305 if (!height)
1306 height = mpDrv->Connector.cy;
1307 ULONG bpp = aColorDepth;
1308 if (!bpp)
1309 {
1310 uint32_t cBits = 0;
1311 int rc = mpDrv->pUpPort->pfnQueryColorDepth(mpDrv->pUpPort, &cBits);
1312 AssertRC(rc);
1313 bpp = cBits;
1314 }
1315 ULONG vramSize;
1316 mParent->machine()->COMGETTER(VRAMSize)(&vramSize);
1317 /* enough VRAM? */
1318 if ((width * height * (bpp / 8)) > (vramSize * 1024 * 1024))
1319 return setError(E_FAIL, tr("Not enough VRAM for the selected video mode"));
1320
1321 LogRel(("Display::SetVideoModeHint: got a video mode hint (%dx%dx%d)\n",
1322 aWidth, aHeight, bpp));
1323 if (mParent->getVMMDev())
1324 mParent->getVMMDev()->getVMMDevPort()->pfnRequestDisplayChange(mParent->getVMMDev()->getVMMDevPort(), aWidth, aHeight, aColorDepth);
1325 return S_OK;
1326}
1327
1328/**
1329 * Takes a screen shot of the requested size and copies it to the buffer
1330 * allocated by the caller. The screen shot is always a 32-bpp image, so the
1331 * size of the buffer must be at least (((width * 32 + 31) / 32) * 4) * height
1332 * (i.e. dword-aligned). If the requested screen shot dimentions differ from
1333 * the actual VM display size then the screen shot image is stretched and/or
1334 * shrunk accordingly.
1335 *
1336 * @returns COM status code
1337 * @param address the address of the buffer allocated by the caller
1338 * @param width the width of the screenshot to take
1339 * @param height the height of the screenshot to take
1340 */
1341STDMETHODIMP Display::TakeScreenShot (ULONG address, ULONG width, ULONG height)
1342{
1343 /// @todo (r=dmik) this function may take too long to complete if the VM
1344 // is doing something like saving state right now. Which, in case if it
1345 // is called on the GUI thread, will make it unresponsive. We should
1346 // check the machine state here (by enclosing the check and VMRequCall
1347 // within the Console lock to make it atomic).
1348
1349 LogFlowFuncEnter();
1350 LogFlowFunc (("address=%p, width=%d, height=%d\n",
1351 address, width, height));
1352
1353 if (!address)
1354 return E_POINTER;
1355 if (!width || !height)
1356 return E_INVALIDARG;
1357
1358 AutoLock lock(this);
1359 CHECK_READY();
1360
1361 CHECK_CONSOLE_DRV (mpDrv);
1362
1363 Console::SafeVMPtr pVM (mParent);
1364 CheckComRCReturnRC (pVM.rc());
1365
1366 HRESULT rc = S_OK;
1367
1368 LogFlowFunc (("Sending SCREENSHOT request\n"));
1369
1370 /*
1371 * First try use the graphics device features for making a snapshot.
1372 * This does not support streatching, is an optional feature (returns not supported).
1373 *
1374 * Note: It may cause a display resize. Watch out for deadlocks.
1375 */
1376 int rcVBox = VERR_NOT_SUPPORTED;
1377 if ( mpDrv->Connector.cx == width
1378 && mpDrv->Connector.cy == height)
1379 {
1380 PVMREQ pReq;
1381 size_t cbData = RT_ALIGN_Z(width, 4) * 4 * height;
1382 rcVBox = VMR3ReqCall(pVM, &pReq, RT_INDEFINITE_WAIT,
1383 (PFNRT)mpDrv->pUpPort->pfnSnapshot, 6, mpDrv->pUpPort,
1384 (void *)address, cbData, NULL, NULL, NULL);
1385 if (VBOX_SUCCESS(rcVBox))
1386 {
1387 rcVBox = pReq->iStatus;
1388 VMR3ReqFree(pReq);
1389 }
1390 }
1391
1392 /*
1393 * If the function returns not supported, or if streaching is requested,
1394 * we'll have to do all the work ourselves using the framebuffer data.
1395 */
1396 if (rcVBox == VERR_NOT_SUPPORTED || rcVBox == VERR_NOT_IMPLEMENTED)
1397 {
1398 /** @todo implement snapshot streching and generic snapshot fallback. */
1399 rc = setError (E_NOTIMPL, tr ("This feature is not implemented"));
1400 }
1401 else if (VBOX_FAILURE(rcVBox))
1402 rc = setError (E_FAIL,
1403 tr ("Could not take a screenshot (%Vrc)"), rcVBox);
1404
1405 LogFlowFunc (("rc=%08X\n", rc));
1406 LogFlowFuncLeave();
1407 return rc;
1408}
1409
1410/**
1411 * Draws an image of the specified size from the given buffer to the given
1412 * point on the VM display. The image is assumed to have a 32-bit depth, so the
1413 * size of one scanline must be ((width * 32 + 31) / 32) * 4), i.e.
1414 * dword-aligned. The total image size in the buffer is height * scanline size.
1415 *
1416 * @returns COM status code
1417 * @param address the address of the buffer containing the image
1418 * @param x the x coordinate on the VM display to place the image to
1419 * @param y the y coordinate on the VM display to place the image to
1420 * @param width the width of the image in the buffer
1421 * @param height the height of the image in the buffer
1422 */
1423STDMETHODIMP Display::DrawToScreen (ULONG address, ULONG x, ULONG y,
1424 ULONG width, ULONG height)
1425{
1426 /// @todo (r=dmik) this function may take too long to complete if the VM
1427 // is doing something like saving state right now. Which, in case if it
1428 // is called on the GUI thread, will make it unresponsive. We should
1429 // check the machine state here (by enclosing the check and VMRequCall
1430 // within the Console lock to make it atomic).
1431
1432 LogFlowFuncEnter();
1433 LogFlowFunc (("address=%p, x=%d, y=%d, width=%d, height=%d\n",
1434 address, x, y, width, height));
1435
1436 AutoLock lock(this);
1437 CHECK_READY();
1438
1439 CHECK_CONSOLE_DRV (mpDrv);
1440
1441 Console::SafeVMPtr pVM (mParent);
1442 CheckComRCReturnRC (pVM.rc());
1443
1444 /*
1445 * Again we're lazy and make the graphics device do all the
1446 * dirty convertion work.
1447 */
1448 PVMREQ pReq;
1449 int rcVBox = VMR3ReqCall(pVM, &pReq, RT_INDEFINITE_WAIT,
1450 (PFNRT)mpDrv->pUpPort->pfnDisplayBlt, 6, mpDrv->pUpPort,
1451 (void *)address, x, y, width, height);
1452 if (VBOX_SUCCESS(rcVBox))
1453 {
1454 rcVBox = pReq->iStatus;
1455 VMR3ReqFree(pReq);
1456 }
1457
1458 /*
1459 * If the function returns not supported, we'll have to do all the
1460 * work ourselves using the framebuffer.
1461 */
1462 HRESULT rc = S_OK;
1463 if (rcVBox == VERR_NOT_SUPPORTED || rcVBox == VERR_NOT_IMPLEMENTED)
1464 {
1465 /** @todo implement generic fallback for screen blitting. */
1466 rc = E_NOTIMPL;
1467 }
1468 else if (VBOX_FAILURE(rcVBox))
1469 rc = setError (E_FAIL,
1470 tr ("Could not draw to the screen (%Vrc)"), rcVBox);
1471//@todo
1472// else
1473// {
1474// /* All ok. Redraw the screen. */
1475// handleDisplayUpdate (x, y, width, height);
1476// }
1477
1478 LogFlowFunc (("rc=%08X\n", rc));
1479 LogFlowFuncLeave();
1480 return rc;
1481}
1482
1483/**
1484 * Does a full invalidation of the VM display and instructs the VM
1485 * to update it immediately.
1486 *
1487 * @returns COM status code
1488 */
1489STDMETHODIMP Display::InvalidateAndUpdate()
1490{
1491 LogFlowFuncEnter();
1492
1493 AutoLock lock(this);
1494 CHECK_READY();
1495
1496 CHECK_CONSOLE_DRV (mpDrv);
1497
1498 Console::SafeVMPtr pVM (mParent);
1499 CheckComRCReturnRC (pVM.rc());
1500
1501 HRESULT rc = S_OK;
1502
1503 LogFlowFunc (("Sending DPYUPDATE request\n"));
1504
1505 /* pdm.h says that this has to be called from the EMT thread */
1506 PVMREQ pReq;
1507 int rcVBox = VMR3ReqCallVoid(pVM, &pReq, RT_INDEFINITE_WAIT,
1508 (PFNRT)mpDrv->pUpPort->pfnUpdateDisplayAll, 1, mpDrv->pUpPort);
1509 if (VBOX_SUCCESS(rcVBox))
1510 VMR3ReqFree(pReq);
1511
1512 if (VBOX_FAILURE(rcVBox))
1513 rc = setError (E_FAIL,
1514 tr ("Could not invalidate and update the screen (%Vrc)"), rcVBox);
1515
1516 LogFlowFunc (("rc=%08X\n", rc));
1517 LogFlowFuncLeave();
1518 return rc;
1519}
1520
1521/**
1522 * Notification that the framebuffer has completed the
1523 * asynchronous resize processing
1524 *
1525 * @returns COM status code
1526 */
1527STDMETHODIMP Display::ResizeCompleted()
1528{
1529 LogFlowFunc (("\n"));
1530
1531 /// @todo (dmik) can we AutoLock alock (this); here?
1532 // do it when we switch this class to VirtualBoxBase_NEXT.
1533 // This will require general code review and may add some details.
1534 // In particular, we may want to check whether EMT is really waiting for
1535 // this notification, etc. It might be also good to obey the caller to make
1536 // sure this method is not called from more than one thread at a time
1537 // (and therefore don't use Display lock at all here to save some
1538 // milliseconds).
1539 CHECK_READY();
1540
1541 /* this is only valid for external framebuffers */
1542 if (mInternalFramebuffer)
1543 return setError (E_FAIL,
1544 tr ("Resize completed notification is valid only "
1545 "for external framebuffers"));
1546
1547 /* signal our semaphore */
1548 RTSemEventMultiSignal(mResizeSem);
1549
1550 return S_OK;
1551}
1552
1553/**
1554 * Notification that the framebuffer has completed the
1555 * asynchronous update processing
1556 *
1557 * @returns COM status code
1558 */
1559STDMETHODIMP Display::UpdateCompleted()
1560{
1561 LogFlowFunc (("\n"));
1562
1563 /// @todo (dmik) can we AutoLock alock (this); here?
1564 // do it when we switch this class to VirtualBoxBase_NEXT.
1565 // Tthis will require general code review and may add some details.
1566 // In particular, we may want to check whether EMT is really waiting for
1567 // this notification, etc. It might be also good to obey the caller to make
1568 // sure this method is not called from more than one thread at a time
1569 // (and therefore don't use Display lock at all here to save some
1570 // milliseconds).
1571 CHECK_READY();
1572
1573 /* this is only valid for external framebuffers */
1574 if (mInternalFramebuffer)
1575 return setError (E_FAIL,
1576 tr ("Resize completed notification is valid only "
1577 "for external framebuffers"));
1578
1579 mFramebuffer->Lock();
1580 /* signal our semaphore */
1581 RTSemEventMultiSignal(mUpdateSem);
1582 mFramebuffer->Unlock();
1583
1584 return S_OK;
1585}
1586
1587// private methods
1588/////////////////////////////////////////////////////////////////////////////
1589
1590/**
1591 * Helper to update the display information from the framebuffer.
1592 *
1593 * @param aCheckParams true to compare the parameters of the current framebuffer
1594 * and the new one and issue handleDisplayResize()
1595 * if they differ.
1596 * @thread EMT
1597 */
1598void Display::updateDisplayData (bool aCheckParams /* = false */)
1599{
1600 /* the driver might not have been constructed yet */
1601 if (!mpDrv)
1602 return;
1603
1604#if DEBUG
1605 /*
1606 * Sanity check. Note that this method may be called on EMT after Console
1607 * has started the power down procedure (but before our #drvDestruct() is
1608 * called, in which case pVM will aleady be NULL but mpDrv will not). Since
1609 * we don't really need pVM to proceed, we avoid this check in the release
1610 * build to save some ms (necessary to construct SafeVMPtrQuiet) in this
1611 * time-critical method.
1612 */
1613 Console::SafeVMPtrQuiet pVM (mParent);
1614 if (pVM.isOk())
1615 VM_ASSERT_EMT (pVM.raw());
1616#endif
1617
1618 if (mFramebuffer)
1619 {
1620 HRESULT rc;
1621 ULONG address = 0;
1622 rc = mFramebuffer->COMGETTER(Address) (&address);
1623 AssertComRC (rc);
1624 ULONG lineSize = 0;
1625 rc = mFramebuffer->COMGETTER(LineSize) (&lineSize);
1626 AssertComRC (rc);
1627 ULONG colorDepth = 0;
1628 rc = mFramebuffer->COMGETTER(ColorDepth) (&colorDepth);
1629 AssertComRC (rc);
1630 ULONG width = 0;
1631 rc = mFramebuffer->COMGETTER(Width) (&width);
1632 AssertComRC (rc);
1633 ULONG height = 0;
1634 rc = mFramebuffer->COMGETTER(Height) (&height);
1635 AssertComRC (rc);
1636
1637 /*
1638 * Check current parameters with new ones and issue handleDisplayResize()
1639 * to let the new frame buffer adjust itself properly. Note that it will
1640 * result into a recursive updateDisplayData() call but with
1641 * aCheckOld = false.
1642 */
1643 if (aCheckParams &&
1644 (mLastAddress != (uint8_t *) address ||
1645 mLastLineSize != lineSize ||
1646 mLastColorDepth != colorDepth ||
1647 mLastWidth != (int) width ||
1648 mLastHeight != (int) height))
1649 {
1650 handleDisplayResize (mLastColorDepth,
1651 mLastAddress,
1652 mLastLineSize,
1653 mLastWidth,
1654 mLastHeight);
1655 return;
1656 }
1657
1658 mpDrv->Connector.pu8Data = (uint8_t *) address;
1659 mpDrv->Connector.cbScanline = lineSize;
1660 mpDrv->Connector.cBits = colorDepth;
1661 mpDrv->Connector.cx = width;
1662 mpDrv->Connector.cy = height;
1663 }
1664 else
1665 {
1666 /* black hole */
1667 mpDrv->Connector.pu8Data = NULL;
1668 mpDrv->Connector.cbScanline = 0;
1669 mpDrv->Connector.cBits = 0;
1670 mpDrv->Connector.cx = 0;
1671 mpDrv->Connector.cy = 0;
1672 }
1673}
1674
1675/**
1676 * Changes the current frame buffer. Called on EMT to avoid both
1677 * race conditions and excessive locking.
1678 *
1679 * @note locks this object for writing
1680 * @thread EMT
1681 */
1682/* static */
1683DECLCALLBACK(int) Display::changeFramebuffer (Display *that, IFramebuffer *aFB,
1684 bool aInternal)
1685{
1686 LogFlowFunc (("\n"));
1687
1688 AssertReturn (that, VERR_INVALID_PARAMETER);
1689 AssertReturn (aFB || aInternal, VERR_INVALID_PARAMETER);
1690
1691 /// @todo (r=dmik) AutoCaller
1692
1693 AutoLock alock (that);
1694
1695 that->mFramebuffer = aFB;
1696 that->mInternalFramebuffer = aInternal;
1697 that->mSupportedAccelOps = 0;
1698
1699 /* determine which acceleration functions are supported by this framebuffer */
1700 if (aFB && !aInternal)
1701 {
1702 HRESULT rc;
1703 BOOL accelSupported = FALSE;
1704 rc = aFB->OperationSupported (
1705 FramebufferAccelerationOperation_SolidFillAcceleration, &accelSupported);
1706 AssertComRC (rc);
1707 if (accelSupported)
1708 that->mSupportedAccelOps |=
1709 FramebufferAccelerationOperation_SolidFillAcceleration;
1710 accelSupported = FALSE;
1711 rc = aFB->OperationSupported (
1712 FramebufferAccelerationOperation_ScreenCopyAcceleration, &accelSupported);
1713 AssertComRC (rc);
1714 if (accelSupported)
1715 that->mSupportedAccelOps |=
1716 FramebufferAccelerationOperation_ScreenCopyAcceleration;
1717 }
1718
1719 that->mParent->consoleVRDPServer()->
1720 SetFramebuffer (aFB, aFB ? VRDP_EXTERNAL_FRAMEBUFFER :
1721 VRDP_INTERNAL_FRAMEBUFFER);
1722
1723 that->updateDisplayData (true /* aCheckParams */);
1724
1725 return VINF_SUCCESS;
1726}
1727
1728/**
1729 * Handle display resize event.
1730 *
1731 * @see PDMIDISPLAYCONNECTOR::pfnResize
1732 */
1733DECLCALLBACK(void) Display::displayResizeCallback(PPDMIDISPLAYCONNECTOR pInterface,
1734 uint32_t bpp, void *pvVRAM, uint32_t cbLine, uint32_t cx, uint32_t cy)
1735{
1736 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1737
1738 LogFlowFunc (("bpp %d, pvVRAM %p, cbLine %d, cx %d, cy %d\n",
1739 bpp, pvVRAM, cbLine, cx, cy));
1740
1741 pDrv->pDisplay->handleDisplayResize(bpp, pvVRAM, cbLine, cx, cy);
1742}
1743
1744/**
1745 * Handle display update.
1746 *
1747 * @see PDMIDISPLAYCONNECTOR::pfnUpdateRect
1748 */
1749DECLCALLBACK(void) Display::displayUpdateCallback(PPDMIDISPLAYCONNECTOR pInterface,
1750 uint32_t x, uint32_t y, uint32_t cx, uint32_t cy)
1751{
1752 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1753
1754#ifdef DEBUG_sunlover
1755 LogFlowFunc (("pDrv->pDisplay->mfVideoAccelEnabled = %d, %d,%d %dx%d\n",
1756 pDrv->pDisplay->mfVideoAccelEnabled, x, y, cx, cy));
1757#endif /* DEBUG_sunlover */
1758
1759 /* This call does update regardless of VBVA status.
1760 * But in VBVA mode this is called only as result of
1761 * pfnUpdateDisplayAll in the VGA device.
1762 */
1763
1764 pDrv->pDisplay->handleDisplayUpdate(x, y, cx, cy);
1765}
1766
1767/**
1768 * Periodic display refresh callback.
1769 *
1770 * @see PDMIDISPLAYCONNECTOR::pfnRefresh
1771 */
1772DECLCALLBACK(void) Display::displayRefreshCallback(PPDMIDISPLAYCONNECTOR pInterface)
1773{
1774 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1775
1776#ifdef DEBUG_sunlover
1777 STAM_PROFILE_START(&StatDisplayRefresh, a);
1778#endif /* DEBUG_sunlover */
1779
1780#ifdef DEBUG_sunlover
1781 LogFlowFunc (("pDrv->pDisplay->mfVideoAccelEnabled = %d\n",
1782 pDrv->pDisplay->mfVideoAccelEnabled));
1783#endif /* DEBUG_sunlover */
1784
1785 Display *pDisplay = pDrv->pDisplay;
1786
1787 if (pDisplay->mFramebuffer.isNull())
1788 {
1789 /*
1790 * Do nothing in the "black hole" mode to avoid copying guest
1791 * video memory to the frame buffer
1792 */
1793 }
1794 else
1795 {
1796 if (pDisplay->mfPendingVideoAccelEnable)
1797 {
1798 /* Acceleration was enabled while machine was not yet running
1799 * due to restoring from saved state. Update entire display and
1800 * actually enable acceleration.
1801 */
1802 Assert(pDisplay->mpPendingVbvaMemory);
1803
1804 /* Acceleration can not be yet enabled.*/
1805 Assert(pDisplay->mpVbvaMemory == NULL);
1806 Assert(!pDisplay->mfVideoAccelEnabled);
1807
1808 if (pDisplay->mfMachineRunning)
1809 {
1810 pDisplay->VideoAccelEnable (pDisplay->mfPendingVideoAccelEnable,
1811 pDisplay->mpPendingVbvaMemory);
1812
1813 /* Reset the pending state. */
1814 pDisplay->mfPendingVideoAccelEnable = false;
1815 pDisplay->mpPendingVbvaMemory = NULL;
1816 }
1817 }
1818 else
1819 {
1820 Assert(pDisplay->mpPendingVbvaMemory == NULL);
1821
1822 if (pDisplay->mfVideoAccelEnabled)
1823 {
1824 Assert(pDisplay->mpVbvaMemory);
1825 pDisplay->VideoAccelFlush ();
1826 }
1827 else
1828 {
1829 Assert(pDrv->Connector.pu8Data);
1830 pDrv->pUpPort->pfnUpdateDisplay(pDrv->pUpPort);
1831 }
1832 }
1833#ifdef VRDP_MC
1834 /* Inform to VRDP server that the current display update sequence is
1835 * completed. At this moment the framebuffer memory contains a definite
1836 * image, that is synchronized with the orders already sent to VRDP client.
1837 * The server can now process redraw requests from clients or initial
1838 * fullscreen updates for new clients.
1839 */
1840 Assert (pDisplay->mParent && pDisplay->mParent->consoleVRDPServer());
1841 pDisplay->mParent->consoleVRDPServer()->SendUpdate (NULL, 0);
1842#endif /* VRDP_MC */
1843 }
1844
1845#ifdef DEBUG_sunlover
1846 STAM_PROFILE_STOP(&StatDisplayRefresh, a);
1847#endif /* DEBUG_sunlover */
1848}
1849
1850/**
1851 * Reset notification
1852 *
1853 * @see PDMIDISPLAYCONNECTOR::pfnReset
1854 */
1855DECLCALLBACK(void) Display::displayResetCallback(PPDMIDISPLAYCONNECTOR pInterface)
1856{
1857 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1858
1859 LogFlowFunc (("\n"));
1860
1861 /* Disable VBVA mode. */
1862 pDrv->pDisplay->VideoAccelEnable (false, NULL);
1863}
1864
1865/**
1866 * LFBModeChange notification
1867 *
1868 * @see PDMIDISPLAYCONNECTOR::pfnLFBModeChange
1869 */
1870DECLCALLBACK(void) Display::displayLFBModeChangeCallback(PPDMIDISPLAYCONNECTOR pInterface, bool fEnabled)
1871{
1872 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1873
1874 LogFlowFunc (("fEnabled=%d\n", fEnabled));
1875
1876 NOREF(fEnabled);
1877
1878 /* Disable VBVA mode in any case. The guest driver reenables VBVA mode if necessary. */
1879 pDrv->pDisplay->VideoAccelEnable (false, NULL);
1880}
1881
1882/**
1883 * Queries an interface to the driver.
1884 *
1885 * @returns Pointer to interface.
1886 * @returns NULL if the interface was not supported by the driver.
1887 * @param pInterface Pointer to this interface structure.
1888 * @param enmInterface The requested interface identification.
1889 */
1890DECLCALLBACK(void *) Display::drvQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
1891{
1892 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1893 PDRVMAINDISPLAY pDrv = PDMINS2DATA(pDrvIns, PDRVMAINDISPLAY);
1894 switch (enmInterface)
1895 {
1896 case PDMINTERFACE_BASE:
1897 return &pDrvIns->IBase;
1898 case PDMINTERFACE_DISPLAY_CONNECTOR:
1899 return &pDrv->Connector;
1900 default:
1901 return NULL;
1902 }
1903}
1904
1905
1906/**
1907 * Destruct a display driver instance.
1908 *
1909 * @returns VBox status.
1910 * @param pDrvIns The driver instance data.
1911 */
1912DECLCALLBACK(void) Display::drvDestruct(PPDMDRVINS pDrvIns)
1913{
1914 PDRVMAINDISPLAY pData = PDMINS2DATA(pDrvIns, PDRVMAINDISPLAY);
1915 LogFlowFunc (("iInstance=%d\n", pDrvIns->iInstance));
1916 if (pData->pDisplay)
1917 {
1918 AutoLock displayLock (pData->pDisplay);
1919 pData->pDisplay->mpDrv = NULL;
1920 pData->pDisplay->mpVMMDev = NULL;
1921 pData->pDisplay->mLastAddress = NULL;
1922 pData->pDisplay->mLastLineSize = 0;
1923 pData->pDisplay->mLastColorDepth = 0,
1924 pData->pDisplay->mLastWidth = 0;
1925 pData->pDisplay->mLastHeight = 0;
1926 }
1927}
1928
1929
1930/**
1931 * Construct a display driver instance.
1932 *
1933 * @returns VBox status.
1934 * @param pDrvIns The driver instance data.
1935 * If the registration structure is needed, pDrvIns->pDrvReg points to it.
1936 * @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
1937 * of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
1938 * iInstance it's expected to be used a bit in this function.
1939 */
1940DECLCALLBACK(int) Display::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
1941{
1942 PDRVMAINDISPLAY pData = PDMINS2DATA(pDrvIns, PDRVMAINDISPLAY);
1943 LogFlowFunc (("iInstance=%d\n", pDrvIns->iInstance));
1944
1945 /*
1946 * Validate configuration.
1947 */
1948 if (!CFGMR3AreValuesValid(pCfgHandle, "Object\0"))
1949 return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
1950 PPDMIBASE pBaseIgnore;
1951 int rc = pDrvIns->pDrvHlp->pfnAttach(pDrvIns, &pBaseIgnore);
1952 if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
1953 {
1954 AssertMsgFailed(("Configuration error: Not possible to attach anything to this driver!\n"));
1955 return VERR_PDM_DRVINS_NO_ATTACH;
1956 }
1957
1958 /*
1959 * Init Interfaces.
1960 */
1961 pDrvIns->IBase.pfnQueryInterface = Display::drvQueryInterface;
1962
1963 pData->Connector.pfnResize = Display::displayResizeCallback;
1964 pData->Connector.pfnUpdateRect = Display::displayUpdateCallback;
1965 pData->Connector.pfnRefresh = Display::displayRefreshCallback;
1966 pData->Connector.pfnReset = Display::displayResetCallback;
1967 pData->Connector.pfnLFBModeChange = Display::displayLFBModeChangeCallback;
1968
1969 /*
1970 * Get the IDisplayPort interface of the above driver/device.
1971 */
1972 pData->pUpPort = (PPDMIDISPLAYPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_DISPLAY_PORT);
1973 if (!pData->pUpPort)
1974 {
1975 AssertMsgFailed(("Configuration error: No display port interface above!\n"));
1976 return VERR_PDM_MISSING_INTERFACE_ABOVE;
1977 }
1978
1979 /*
1980 * Get the Display object pointer and update the mpDrv member.
1981 */
1982 void *pv;
1983 rc = CFGMR3QueryPtr(pCfgHandle, "Object", &pv);
1984 if (VBOX_FAILURE(rc))
1985 {
1986 AssertMsgFailed(("Configuration error: No/bad \"Object\" value! rc=%Vrc\n", rc));
1987 return rc;
1988 }
1989 pData->pDisplay = (Display *)pv; /** @todo Check this cast! */
1990 pData->pDisplay->mpDrv = pData;
1991
1992 /*
1993 * Update our display information according to the framebuffer
1994 */
1995 pData->pDisplay->updateDisplayData();
1996
1997 /*
1998 * Start periodic screen refreshes
1999 */
2000 pData->pUpPort->pfnSetRefreshRate(pData->pUpPort, 20);
2001
2002 return VINF_SUCCESS;
2003}
2004
2005
2006/**
2007 * Display driver registration record.
2008 */
2009const PDMDRVREG Display::DrvReg =
2010{
2011 /* u32Version */
2012 PDM_DRVREG_VERSION,
2013 /* szDriverName */
2014 "MainDisplay",
2015 /* pszDescription */
2016 "Main display driver (Main as in the API).",
2017 /* fFlags */
2018 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2019 /* fClass. */
2020 PDM_DRVREG_CLASS_DISPLAY,
2021 /* cMaxInstances */
2022 ~0,
2023 /* cbInstance */
2024 sizeof(DRVMAINDISPLAY),
2025 /* pfnConstruct */
2026 Display::drvConstruct,
2027 /* pfnDestruct */
2028 Display::drvDestruct,
2029 /* pfnIOCtl */
2030 NULL,
2031 /* pfnPowerOn */
2032 NULL,
2033 /* pfnReset */
2034 NULL,
2035 /* pfnSuspend */
2036 NULL,
2037 /* pfnResume */
2038 NULL,
2039 /* pfnDetach */
2040 NULL
2041};
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette