VirtualBox

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

Last change on this file since 455 was 431, checked in by vboxsync, 18 years ago

Log the video mode hint only when it is actually delivered to the guest.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 57.5 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 if (mParent->getVMMDev())
1322 mParent->getVMMDev()->getVMMDevPort()->pfnRequestDisplayChange(mParent->getVMMDev()->getVMMDevPort(), aWidth, aHeight, aColorDepth);
1323 return S_OK;
1324}
1325
1326/**
1327 * Takes a screen shot of the requested size and copies it to the buffer
1328 * allocated by the caller. The screen shot is always a 32-bpp image, so the
1329 * size of the buffer must be at least (((width * 32 + 31) / 32) * 4) * height
1330 * (i.e. dword-aligned). If the requested screen shot dimentions differ from
1331 * the actual VM display size then the screen shot image is stretched and/or
1332 * shrunk accordingly.
1333 *
1334 * @returns COM status code
1335 * @param address the address of the buffer allocated by the caller
1336 * @param width the width of the screenshot to take
1337 * @param height the height of the screenshot to take
1338 */
1339STDMETHODIMP Display::TakeScreenShot (ULONG address, ULONG width, ULONG height)
1340{
1341 /// @todo (r=dmik) this function may take too long to complete if the VM
1342 // is doing something like saving state right now. Which, in case if it
1343 // is called on the GUI thread, will make it unresponsive. We should
1344 // check the machine state here (by enclosing the check and VMRequCall
1345 // within the Console lock to make it atomic).
1346
1347 LogFlowFuncEnter();
1348 LogFlowFunc (("address=%p, width=%d, height=%d\n",
1349 address, width, height));
1350
1351 if (!address)
1352 return E_POINTER;
1353 if (!width || !height)
1354 return E_INVALIDARG;
1355
1356 AutoLock lock(this);
1357 CHECK_READY();
1358
1359 CHECK_CONSOLE_DRV (mpDrv);
1360
1361 Console::SafeVMPtr pVM (mParent);
1362 CheckComRCReturnRC (pVM.rc());
1363
1364 HRESULT rc = S_OK;
1365
1366 LogFlowFunc (("Sending SCREENSHOT request\n"));
1367
1368 /*
1369 * First try use the graphics device features for making a snapshot.
1370 * This does not support streatching, is an optional feature (returns not supported).
1371 *
1372 * Note: It may cause a display resize. Watch out for deadlocks.
1373 */
1374 int rcVBox = VERR_NOT_SUPPORTED;
1375 if ( mpDrv->Connector.cx == width
1376 && mpDrv->Connector.cy == height)
1377 {
1378 PVMREQ pReq;
1379 size_t cbData = RT_ALIGN_Z(width, 4) * 4 * height;
1380 rcVBox = VMR3ReqCall(pVM, &pReq, RT_INDEFINITE_WAIT,
1381 (PFNRT)mpDrv->pUpPort->pfnSnapshot, 6, mpDrv->pUpPort,
1382 (void *)address, cbData, NULL, NULL, NULL);
1383 if (VBOX_SUCCESS(rcVBox))
1384 {
1385 rcVBox = pReq->iStatus;
1386 VMR3ReqFree(pReq);
1387 }
1388 }
1389
1390 /*
1391 * If the function returns not supported, or if streaching is requested,
1392 * we'll have to do all the work ourselves using the framebuffer data.
1393 */
1394 if (rcVBox == VERR_NOT_SUPPORTED || rcVBox == VERR_NOT_IMPLEMENTED)
1395 {
1396 /** @todo implement snapshot streching and generic snapshot fallback. */
1397 rc = setError (E_NOTIMPL, tr ("This feature is not implemented"));
1398 }
1399 else if (VBOX_FAILURE(rcVBox))
1400 rc = setError (E_FAIL,
1401 tr ("Could not take a screenshot (%Vrc)"), rcVBox);
1402
1403 LogFlowFunc (("rc=%08X\n", rc));
1404 LogFlowFuncLeave();
1405 return rc;
1406}
1407
1408/**
1409 * Draws an image of the specified size from the given buffer to the given
1410 * point on the VM display. The image is assumed to have a 32-bit depth, so the
1411 * size of one scanline must be ((width * 32 + 31) / 32) * 4), i.e.
1412 * dword-aligned. The total image size in the buffer is height * scanline size.
1413 *
1414 * @returns COM status code
1415 * @param address the address of the buffer containing the image
1416 * @param x the x coordinate on the VM display to place the image to
1417 * @param y the y coordinate on the VM display to place the image to
1418 * @param width the width of the image in the buffer
1419 * @param height the height of the image in the buffer
1420 */
1421STDMETHODIMP Display::DrawToScreen (ULONG address, ULONG x, ULONG y,
1422 ULONG width, ULONG height)
1423{
1424 /// @todo (r=dmik) this function may take too long to complete if the VM
1425 // is doing something like saving state right now. Which, in case if it
1426 // is called on the GUI thread, will make it unresponsive. We should
1427 // check the machine state here (by enclosing the check and VMRequCall
1428 // within the Console lock to make it atomic).
1429
1430 LogFlowFuncEnter();
1431 LogFlowFunc (("address=%p, x=%d, y=%d, width=%d, height=%d\n",
1432 address, x, y, width, height));
1433
1434 AutoLock lock(this);
1435 CHECK_READY();
1436
1437 CHECK_CONSOLE_DRV (mpDrv);
1438
1439 Console::SafeVMPtr pVM (mParent);
1440 CheckComRCReturnRC (pVM.rc());
1441
1442 /*
1443 * Again we're lazy and make the graphics device do all the
1444 * dirty convertion work.
1445 */
1446 PVMREQ pReq;
1447 int rcVBox = VMR3ReqCall(pVM, &pReq, RT_INDEFINITE_WAIT,
1448 (PFNRT)mpDrv->pUpPort->pfnDisplayBlt, 6, mpDrv->pUpPort,
1449 (void *)address, x, y, width, height);
1450 if (VBOX_SUCCESS(rcVBox))
1451 {
1452 rcVBox = pReq->iStatus;
1453 VMR3ReqFree(pReq);
1454 }
1455
1456 /*
1457 * If the function returns not supported, we'll have to do all the
1458 * work ourselves using the framebuffer.
1459 */
1460 HRESULT rc = S_OK;
1461 if (rcVBox == VERR_NOT_SUPPORTED || rcVBox == VERR_NOT_IMPLEMENTED)
1462 {
1463 /** @todo implement generic fallback for screen blitting. */
1464 rc = E_NOTIMPL;
1465 }
1466 else if (VBOX_FAILURE(rcVBox))
1467 rc = setError (E_FAIL,
1468 tr ("Could not draw to the screen (%Vrc)"), rcVBox);
1469//@todo
1470// else
1471// {
1472// /* All ok. Redraw the screen. */
1473// handleDisplayUpdate (x, y, width, height);
1474// }
1475
1476 LogFlowFunc (("rc=%08X\n", rc));
1477 LogFlowFuncLeave();
1478 return rc;
1479}
1480
1481/**
1482 * Does a full invalidation of the VM display and instructs the VM
1483 * to update it immediately.
1484 *
1485 * @returns COM status code
1486 */
1487STDMETHODIMP Display::InvalidateAndUpdate()
1488{
1489 LogFlowFuncEnter();
1490
1491 AutoLock lock(this);
1492 CHECK_READY();
1493
1494 CHECK_CONSOLE_DRV (mpDrv);
1495
1496 Console::SafeVMPtr pVM (mParent);
1497 CheckComRCReturnRC (pVM.rc());
1498
1499 HRESULT rc = S_OK;
1500
1501 LogFlowFunc (("Sending DPYUPDATE request\n"));
1502
1503 /* pdm.h says that this has to be called from the EMT thread */
1504 PVMREQ pReq;
1505 int rcVBox = VMR3ReqCallVoid(pVM, &pReq, RT_INDEFINITE_WAIT,
1506 (PFNRT)mpDrv->pUpPort->pfnUpdateDisplayAll, 1, mpDrv->pUpPort);
1507 if (VBOX_SUCCESS(rcVBox))
1508 VMR3ReqFree(pReq);
1509
1510 if (VBOX_FAILURE(rcVBox))
1511 rc = setError (E_FAIL,
1512 tr ("Could not invalidate and update the screen (%Vrc)"), rcVBox);
1513
1514 LogFlowFunc (("rc=%08X\n", rc));
1515 LogFlowFuncLeave();
1516 return rc;
1517}
1518
1519/**
1520 * Notification that the framebuffer has completed the
1521 * asynchronous resize processing
1522 *
1523 * @returns COM status code
1524 */
1525STDMETHODIMP Display::ResizeCompleted()
1526{
1527 LogFlowFunc (("\n"));
1528
1529 /// @todo (dmik) can we AutoLock alock (this); here?
1530 // do it when we switch this class to VirtualBoxBase_NEXT.
1531 // This will require general code review and may add some details.
1532 // In particular, we may want to check whether EMT is really waiting for
1533 // this notification, etc. It might be also good to obey the caller to make
1534 // sure this method is not called from more than one thread at a time
1535 // (and therefore don't use Display lock at all here to save some
1536 // milliseconds).
1537 CHECK_READY();
1538
1539 /* this is only valid for external framebuffers */
1540 if (mInternalFramebuffer)
1541 return setError (E_FAIL,
1542 tr ("Resize completed notification is valid only "
1543 "for external framebuffers"));
1544
1545 /* signal our semaphore */
1546 RTSemEventMultiSignal(mResizeSem);
1547
1548 return S_OK;
1549}
1550
1551/**
1552 * Notification that the framebuffer has completed the
1553 * asynchronous update processing
1554 *
1555 * @returns COM status code
1556 */
1557STDMETHODIMP Display::UpdateCompleted()
1558{
1559 LogFlowFunc (("\n"));
1560
1561 /// @todo (dmik) can we AutoLock alock (this); here?
1562 // do it when we switch this class to VirtualBoxBase_NEXT.
1563 // Tthis will require general code review and may add some details.
1564 // In particular, we may want to check whether EMT is really waiting for
1565 // this notification, etc. It might be also good to obey the caller to make
1566 // sure this method is not called from more than one thread at a time
1567 // (and therefore don't use Display lock at all here to save some
1568 // milliseconds).
1569 CHECK_READY();
1570
1571 /* this is only valid for external framebuffers */
1572 if (mInternalFramebuffer)
1573 return setError (E_FAIL,
1574 tr ("Resize completed notification is valid only "
1575 "for external framebuffers"));
1576
1577 mFramebuffer->Lock();
1578 /* signal our semaphore */
1579 RTSemEventMultiSignal(mUpdateSem);
1580 mFramebuffer->Unlock();
1581
1582 return S_OK;
1583}
1584
1585// private methods
1586/////////////////////////////////////////////////////////////////////////////
1587
1588/**
1589 * Helper to update the display information from the framebuffer.
1590 *
1591 * @param aCheckParams true to compare the parameters of the current framebuffer
1592 * and the new one and issue handleDisplayResize()
1593 * if they differ.
1594 * @thread EMT
1595 */
1596void Display::updateDisplayData (bool aCheckParams /* = false */)
1597{
1598 /* the driver might not have been constructed yet */
1599 if (!mpDrv)
1600 return;
1601
1602#if DEBUG
1603 /*
1604 * Sanity check. Note that this method may be called on EMT after Console
1605 * has started the power down procedure (but before our #drvDestruct() is
1606 * called, in which case pVM will aleady be NULL but mpDrv will not). Since
1607 * we don't really need pVM to proceed, we avoid this check in the release
1608 * build to save some ms (necessary to construct SafeVMPtrQuiet) in this
1609 * time-critical method.
1610 */
1611 Console::SafeVMPtrQuiet pVM (mParent);
1612 if (pVM.isOk())
1613 VM_ASSERT_EMT (pVM.raw());
1614#endif
1615
1616 if (mFramebuffer)
1617 {
1618 HRESULT rc;
1619 ULONG address = 0;
1620 rc = mFramebuffer->COMGETTER(Address) (&address);
1621 AssertComRC (rc);
1622 ULONG lineSize = 0;
1623 rc = mFramebuffer->COMGETTER(LineSize) (&lineSize);
1624 AssertComRC (rc);
1625 ULONG colorDepth = 0;
1626 rc = mFramebuffer->COMGETTER(ColorDepth) (&colorDepth);
1627 AssertComRC (rc);
1628 ULONG width = 0;
1629 rc = mFramebuffer->COMGETTER(Width) (&width);
1630 AssertComRC (rc);
1631 ULONG height = 0;
1632 rc = mFramebuffer->COMGETTER(Height) (&height);
1633 AssertComRC (rc);
1634
1635 /*
1636 * Check current parameters with new ones and issue handleDisplayResize()
1637 * to let the new frame buffer adjust itself properly. Note that it will
1638 * result into a recursive updateDisplayData() call but with
1639 * aCheckOld = false.
1640 */
1641 if (aCheckParams &&
1642 (mLastAddress != (uint8_t *) address ||
1643 mLastLineSize != lineSize ||
1644 mLastColorDepth != colorDepth ||
1645 mLastWidth != (int) width ||
1646 mLastHeight != (int) height))
1647 {
1648 handleDisplayResize (mLastColorDepth,
1649 mLastAddress,
1650 mLastLineSize,
1651 mLastWidth,
1652 mLastHeight);
1653 return;
1654 }
1655
1656 mpDrv->Connector.pu8Data = (uint8_t *) address;
1657 mpDrv->Connector.cbScanline = lineSize;
1658 mpDrv->Connector.cBits = colorDepth;
1659 mpDrv->Connector.cx = width;
1660 mpDrv->Connector.cy = height;
1661 }
1662 else
1663 {
1664 /* black hole */
1665 mpDrv->Connector.pu8Data = NULL;
1666 mpDrv->Connector.cbScanline = 0;
1667 mpDrv->Connector.cBits = 0;
1668 mpDrv->Connector.cx = 0;
1669 mpDrv->Connector.cy = 0;
1670 }
1671}
1672
1673/**
1674 * Changes the current frame buffer. Called on EMT to avoid both
1675 * race conditions and excessive locking.
1676 *
1677 * @note locks this object for writing
1678 * @thread EMT
1679 */
1680/* static */
1681DECLCALLBACK(int) Display::changeFramebuffer (Display *that, IFramebuffer *aFB,
1682 bool aInternal)
1683{
1684 LogFlowFunc (("\n"));
1685
1686 AssertReturn (that, VERR_INVALID_PARAMETER);
1687 AssertReturn (aFB || aInternal, VERR_INVALID_PARAMETER);
1688
1689 /// @todo (r=dmik) AutoCaller
1690
1691 AutoLock alock (that);
1692
1693 that->mFramebuffer = aFB;
1694 that->mInternalFramebuffer = aInternal;
1695 that->mSupportedAccelOps = 0;
1696
1697 /* determine which acceleration functions are supported by this framebuffer */
1698 if (aFB && !aInternal)
1699 {
1700 HRESULT rc;
1701 BOOL accelSupported = FALSE;
1702 rc = aFB->OperationSupported (
1703 FramebufferAccelerationOperation_SolidFillAcceleration, &accelSupported);
1704 AssertComRC (rc);
1705 if (accelSupported)
1706 that->mSupportedAccelOps |=
1707 FramebufferAccelerationOperation_SolidFillAcceleration;
1708 accelSupported = FALSE;
1709 rc = aFB->OperationSupported (
1710 FramebufferAccelerationOperation_ScreenCopyAcceleration, &accelSupported);
1711 AssertComRC (rc);
1712 if (accelSupported)
1713 that->mSupportedAccelOps |=
1714 FramebufferAccelerationOperation_ScreenCopyAcceleration;
1715 }
1716
1717 that->mParent->consoleVRDPServer()->
1718 SetFramebuffer (aFB, aFB ? VRDP_EXTERNAL_FRAMEBUFFER :
1719 VRDP_INTERNAL_FRAMEBUFFER);
1720
1721 that->updateDisplayData (true /* aCheckParams */);
1722
1723 return VINF_SUCCESS;
1724}
1725
1726/**
1727 * Handle display resize event.
1728 *
1729 * @see PDMIDISPLAYCONNECTOR::pfnResize
1730 */
1731DECLCALLBACK(void) Display::displayResizeCallback(PPDMIDISPLAYCONNECTOR pInterface,
1732 uint32_t bpp, void *pvVRAM, uint32_t cbLine, uint32_t cx, uint32_t cy)
1733{
1734 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1735
1736 LogFlowFunc (("bpp %d, pvVRAM %p, cbLine %d, cx %d, cy %d\n",
1737 bpp, pvVRAM, cbLine, cx, cy));
1738
1739 pDrv->pDisplay->handleDisplayResize(bpp, pvVRAM, cbLine, cx, cy);
1740}
1741
1742/**
1743 * Handle display update.
1744 *
1745 * @see PDMIDISPLAYCONNECTOR::pfnUpdateRect
1746 */
1747DECLCALLBACK(void) Display::displayUpdateCallback(PPDMIDISPLAYCONNECTOR pInterface,
1748 uint32_t x, uint32_t y, uint32_t cx, uint32_t cy)
1749{
1750 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1751
1752#ifdef DEBUG_sunlover
1753 LogFlowFunc (("pDrv->pDisplay->mfVideoAccelEnabled = %d, %d,%d %dx%d\n",
1754 pDrv->pDisplay->mfVideoAccelEnabled, x, y, cx, cy));
1755#endif /* DEBUG_sunlover */
1756
1757 /* This call does update regardless of VBVA status.
1758 * But in VBVA mode this is called only as result of
1759 * pfnUpdateDisplayAll in the VGA device.
1760 */
1761
1762 pDrv->pDisplay->handleDisplayUpdate(x, y, cx, cy);
1763}
1764
1765/**
1766 * Periodic display refresh callback.
1767 *
1768 * @see PDMIDISPLAYCONNECTOR::pfnRefresh
1769 */
1770DECLCALLBACK(void) Display::displayRefreshCallback(PPDMIDISPLAYCONNECTOR pInterface)
1771{
1772 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1773
1774#ifdef DEBUG_sunlover
1775 STAM_PROFILE_START(&StatDisplayRefresh, a);
1776#endif /* DEBUG_sunlover */
1777
1778#ifdef DEBUG_sunlover
1779 LogFlowFunc (("pDrv->pDisplay->mfVideoAccelEnabled = %d\n",
1780 pDrv->pDisplay->mfVideoAccelEnabled));
1781#endif /* DEBUG_sunlover */
1782
1783 Display *pDisplay = pDrv->pDisplay;
1784
1785 if (pDisplay->mFramebuffer.isNull())
1786 {
1787 /*
1788 * Do nothing in the "black hole" mode to avoid copying guest
1789 * video memory to the frame buffer
1790 */
1791 }
1792 else
1793 {
1794 if (pDisplay->mfPendingVideoAccelEnable)
1795 {
1796 /* Acceleration was enabled while machine was not yet running
1797 * due to restoring from saved state. Update entire display and
1798 * actually enable acceleration.
1799 */
1800 Assert(pDisplay->mpPendingVbvaMemory);
1801
1802 /* Acceleration can not be yet enabled.*/
1803 Assert(pDisplay->mpVbvaMemory == NULL);
1804 Assert(!pDisplay->mfVideoAccelEnabled);
1805
1806 if (pDisplay->mfMachineRunning)
1807 {
1808 pDisplay->VideoAccelEnable (pDisplay->mfPendingVideoAccelEnable,
1809 pDisplay->mpPendingVbvaMemory);
1810
1811 /* Reset the pending state. */
1812 pDisplay->mfPendingVideoAccelEnable = false;
1813 pDisplay->mpPendingVbvaMemory = NULL;
1814 }
1815 }
1816 else
1817 {
1818 Assert(pDisplay->mpPendingVbvaMemory == NULL);
1819
1820 if (pDisplay->mfVideoAccelEnabled)
1821 {
1822 Assert(pDisplay->mpVbvaMemory);
1823 pDisplay->VideoAccelFlush ();
1824 }
1825 else
1826 {
1827 Assert(pDrv->Connector.pu8Data);
1828 pDrv->pUpPort->pfnUpdateDisplay(pDrv->pUpPort);
1829 }
1830 }
1831#ifdef VRDP_MC
1832 /* Inform to VRDP server that the current display update sequence is
1833 * completed. At this moment the framebuffer memory contains a definite
1834 * image, that is synchronized with the orders already sent to VRDP client.
1835 * The server can now process redraw requests from clients or initial
1836 * fullscreen updates for new clients.
1837 */
1838 Assert (pDisplay->mParent && pDisplay->mParent->consoleVRDPServer());
1839 pDisplay->mParent->consoleVRDPServer()->SendUpdate (NULL, 0);
1840#endif /* VRDP_MC */
1841 }
1842
1843#ifdef DEBUG_sunlover
1844 STAM_PROFILE_STOP(&StatDisplayRefresh, a);
1845#endif /* DEBUG_sunlover */
1846}
1847
1848/**
1849 * Reset notification
1850 *
1851 * @see PDMIDISPLAYCONNECTOR::pfnReset
1852 */
1853DECLCALLBACK(void) Display::displayResetCallback(PPDMIDISPLAYCONNECTOR pInterface)
1854{
1855 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1856
1857 LogFlowFunc (("\n"));
1858
1859 /* Disable VBVA mode. */
1860 pDrv->pDisplay->VideoAccelEnable (false, NULL);
1861}
1862
1863/**
1864 * LFBModeChange notification
1865 *
1866 * @see PDMIDISPLAYCONNECTOR::pfnLFBModeChange
1867 */
1868DECLCALLBACK(void) Display::displayLFBModeChangeCallback(PPDMIDISPLAYCONNECTOR pInterface, bool fEnabled)
1869{
1870 PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface);
1871
1872 LogFlowFunc (("fEnabled=%d\n", fEnabled));
1873
1874 NOREF(fEnabled);
1875
1876 /* Disable VBVA mode in any case. The guest driver reenables VBVA mode if necessary. */
1877 pDrv->pDisplay->VideoAccelEnable (false, NULL);
1878}
1879
1880/**
1881 * Queries an interface to the driver.
1882 *
1883 * @returns Pointer to interface.
1884 * @returns NULL if the interface was not supported by the driver.
1885 * @param pInterface Pointer to this interface structure.
1886 * @param enmInterface The requested interface identification.
1887 */
1888DECLCALLBACK(void *) Display::drvQueryInterface(PPDMIBASE pInterface, PDMINTERFACE enmInterface)
1889{
1890 PPDMDRVINS pDrvIns = PDMIBASE_2_PDMDRV(pInterface);
1891 PDRVMAINDISPLAY pDrv = PDMINS2DATA(pDrvIns, PDRVMAINDISPLAY);
1892 switch (enmInterface)
1893 {
1894 case PDMINTERFACE_BASE:
1895 return &pDrvIns->IBase;
1896 case PDMINTERFACE_DISPLAY_CONNECTOR:
1897 return &pDrv->Connector;
1898 default:
1899 return NULL;
1900 }
1901}
1902
1903
1904/**
1905 * Destruct a display driver instance.
1906 *
1907 * @returns VBox status.
1908 * @param pDrvIns The driver instance data.
1909 */
1910DECLCALLBACK(void) Display::drvDestruct(PPDMDRVINS pDrvIns)
1911{
1912 PDRVMAINDISPLAY pData = PDMINS2DATA(pDrvIns, PDRVMAINDISPLAY);
1913 LogFlowFunc (("iInstance=%d\n", pDrvIns->iInstance));
1914 if (pData->pDisplay)
1915 {
1916 AutoLock displayLock (pData->pDisplay);
1917 pData->pDisplay->mpDrv = NULL;
1918 pData->pDisplay->mpVMMDev = NULL;
1919 pData->pDisplay->mLastAddress = NULL;
1920 pData->pDisplay->mLastLineSize = 0;
1921 pData->pDisplay->mLastColorDepth = 0,
1922 pData->pDisplay->mLastWidth = 0;
1923 pData->pDisplay->mLastHeight = 0;
1924 }
1925}
1926
1927
1928/**
1929 * Construct a display driver instance.
1930 *
1931 * @returns VBox status.
1932 * @param pDrvIns The driver instance data.
1933 * If the registration structure is needed, pDrvIns->pDrvReg points to it.
1934 * @param pCfgHandle Configuration node handle for the driver. Use this to obtain the configuration
1935 * of the driver instance. It's also found in pDrvIns->pCfgHandle, but like
1936 * iInstance it's expected to be used a bit in this function.
1937 */
1938DECLCALLBACK(int) Display::drvConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfgHandle)
1939{
1940 PDRVMAINDISPLAY pData = PDMINS2DATA(pDrvIns, PDRVMAINDISPLAY);
1941 LogFlowFunc (("iInstance=%d\n", pDrvIns->iInstance));
1942
1943 /*
1944 * Validate configuration.
1945 */
1946 if (!CFGMR3AreValuesValid(pCfgHandle, "Object\0"))
1947 return VERR_PDM_DRVINS_UNKNOWN_CFG_VALUES;
1948 PPDMIBASE pBaseIgnore;
1949 int rc = pDrvIns->pDrvHlp->pfnAttach(pDrvIns, &pBaseIgnore);
1950 if (rc != VERR_PDM_NO_ATTACHED_DRIVER)
1951 {
1952 AssertMsgFailed(("Configuration error: Not possible to attach anything to this driver!\n"));
1953 return VERR_PDM_DRVINS_NO_ATTACH;
1954 }
1955
1956 /*
1957 * Init Interfaces.
1958 */
1959 pDrvIns->IBase.pfnQueryInterface = Display::drvQueryInterface;
1960
1961 pData->Connector.pfnResize = Display::displayResizeCallback;
1962 pData->Connector.pfnUpdateRect = Display::displayUpdateCallback;
1963 pData->Connector.pfnRefresh = Display::displayRefreshCallback;
1964 pData->Connector.pfnReset = Display::displayResetCallback;
1965 pData->Connector.pfnLFBModeChange = Display::displayLFBModeChangeCallback;
1966
1967 /*
1968 * Get the IDisplayPort interface of the above driver/device.
1969 */
1970 pData->pUpPort = (PPDMIDISPLAYPORT)pDrvIns->pUpBase->pfnQueryInterface(pDrvIns->pUpBase, PDMINTERFACE_DISPLAY_PORT);
1971 if (!pData->pUpPort)
1972 {
1973 AssertMsgFailed(("Configuration error: No display port interface above!\n"));
1974 return VERR_PDM_MISSING_INTERFACE_ABOVE;
1975 }
1976
1977 /*
1978 * Get the Display object pointer and update the mpDrv member.
1979 */
1980 void *pv;
1981 rc = CFGMR3QueryPtr(pCfgHandle, "Object", &pv);
1982 if (VBOX_FAILURE(rc))
1983 {
1984 AssertMsgFailed(("Configuration error: No/bad \"Object\" value! rc=%Vrc\n", rc));
1985 return rc;
1986 }
1987 pData->pDisplay = (Display *)pv; /** @todo Check this cast! */
1988 pData->pDisplay->mpDrv = pData;
1989
1990 /*
1991 * Update our display information according to the framebuffer
1992 */
1993 pData->pDisplay->updateDisplayData();
1994
1995 /*
1996 * Start periodic screen refreshes
1997 */
1998 pData->pUpPort->pfnSetRefreshRate(pData->pUpPort, 20);
1999
2000 return VINF_SUCCESS;
2001}
2002
2003
2004/**
2005 * Display driver registration record.
2006 */
2007const PDMDRVREG Display::DrvReg =
2008{
2009 /* u32Version */
2010 PDM_DRVREG_VERSION,
2011 /* szDriverName */
2012 "MainDisplay",
2013 /* pszDescription */
2014 "Main display driver (Main as in the API).",
2015 /* fFlags */
2016 PDM_DRVREG_FLAGS_HOST_BITS_DEFAULT,
2017 /* fClass. */
2018 PDM_DRVREG_CLASS_DISPLAY,
2019 /* cMaxInstances */
2020 ~0,
2021 /* cbInstance */
2022 sizeof(DRVMAINDISPLAY),
2023 /* pfnConstruct */
2024 Display::drvConstruct,
2025 /* pfnDestruct */
2026 Display::drvDestruct,
2027 /* pfnIOCtl */
2028 NULL,
2029 /* pfnPowerOn */
2030 NULL,
2031 /* pfnReset */
2032 NULL,
2033 /* pfnSuspend */
2034 NULL,
2035 /* pfnResume */
2036 NULL,
2037 /* pfnDetach */
2038 NULL
2039};
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