1 | /* $Id: Framebuffer.cpp 106061 2024-09-16 14:03:52Z vboxsync $ */
|
---|
2 | /** @file
|
---|
3 | * VBoxSDL - Implementation of VBoxSDLFB (SDL framebuffer) class
|
---|
4 | */
|
---|
5 |
|
---|
6 | /*
|
---|
7 | * Copyright (C) 2006-2024 Oracle and/or its affiliates.
|
---|
8 | *
|
---|
9 | * This file is part of VirtualBox base platform packages, as
|
---|
10 | * available from https://www.virtualbox.org.
|
---|
11 | *
|
---|
12 | * This program is free software; you can redistribute it and/or
|
---|
13 | * modify it under the terms of the GNU General Public License
|
---|
14 | * as published by the Free Software Foundation, in version 3 of the
|
---|
15 | * License.
|
---|
16 | *
|
---|
17 | * This program is distributed in the hope that it will be useful, but
|
---|
18 | * WITHOUT ANY WARRANTY; without even the implied warranty of
|
---|
19 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
---|
20 | * General Public License for more details.
|
---|
21 | *
|
---|
22 | * You should have received a copy of the GNU General Public License
|
---|
23 | * along with this program; if not, see <https://www.gnu.org/licenses>.
|
---|
24 | *
|
---|
25 | * SPDX-License-Identifier: GPL-3.0-only
|
---|
26 | */
|
---|
27 |
|
---|
28 | #define LOG_GROUP LOG_GROUP_GUI
|
---|
29 |
|
---|
30 | #include <iprt/asm.h>
|
---|
31 | #include <iprt/stream.h>
|
---|
32 | #include <iprt/env.h>
|
---|
33 | #include <iprt/errcore.h>
|
---|
34 | #include <iprt/mem.h>
|
---|
35 | #include <iprt/string.h>
|
---|
36 | #include <iprt/semaphore.h>
|
---|
37 | #include <VBox/log.h>
|
---|
38 |
|
---|
39 | #include "Framebuffer.h"
|
---|
40 | #include "Display.h"
|
---|
41 | #include "Ico64x01.h"
|
---|
42 |
|
---|
43 | #include <SDL.h>
|
---|
44 |
|
---|
45 | static bool gfSdlInitialized = false; /**< if SDL was initialized */
|
---|
46 | static SDL_Surface *gWMIcon = NULL; /**< the application icon */
|
---|
47 | static RTNATIVETHREAD gSdlNativeThread = NIL_RTNATIVETHREAD; /**< the SDL thread */
|
---|
48 |
|
---|
49 | DECL_HIDDEN_DATA(RTSEMEVENT) g_EventSemSDLEvents;
|
---|
50 | DECL_HIDDEN_DATA(volatile int32_t) g_cNotifyUpdateEventsPending;
|
---|
51 |
|
---|
52 | /**
|
---|
53 | * Ensure that an SDL event is really enqueued. Try multiple times if necessary.
|
---|
54 | */
|
---|
55 | int PushSDLEventForSure(SDL_Event *event)
|
---|
56 | {
|
---|
57 | int ntries = 10;
|
---|
58 | for (; ntries > 0; ntries--)
|
---|
59 | {
|
---|
60 | int rc = SDL_PushEvent(event);
|
---|
61 | RTSemEventSignal(g_EventSemSDLEvents);
|
---|
62 | if (rc == 1)
|
---|
63 | return 0;
|
---|
64 | Log(("PushSDLEventForSure: waiting for 2ms (rc = %d)\n", rc));
|
---|
65 | RTThreadSleep(2);
|
---|
66 | }
|
---|
67 | LogRel(("WARNING: Failed to enqueue SDL event %d.%d!\n",
|
---|
68 | event->type, event->type == SDL_USEREVENT ? event->user.type : 0));
|
---|
69 | return -1;
|
---|
70 | }
|
---|
71 |
|
---|
72 | #if defined(VBOXSDL_WITH_X11) || defined(RT_OS_DARWIN)
|
---|
73 | /**
|
---|
74 | * Special SDL_PushEvent function for NotifyUpdate events. These events may occur in bursts
|
---|
75 | * so make sure they don't flood the SDL event queue.
|
---|
76 | */
|
---|
77 | void PushNotifyUpdateEvent(SDL_Event *event)
|
---|
78 | {
|
---|
79 | int rc = SDL_PushEvent(event);
|
---|
80 | bool fSuccess = (rc == 1);
|
---|
81 |
|
---|
82 | RTSemEventSignal(g_EventSemSDLEvents);
|
---|
83 | AssertMsg(fSuccess, ("SDL_PushEvent returned SDL error\n"));
|
---|
84 | /* A global counter is faster than SDL_PeepEvents() */
|
---|
85 | if (fSuccess)
|
---|
86 | ASMAtomicIncS32(&g_cNotifyUpdateEventsPending);
|
---|
87 | /* In order to not flood the SDL event queue, yield the CPU or (if there are already many
|
---|
88 | * events queued) even sleep */
|
---|
89 | if (g_cNotifyUpdateEventsPending > 96)
|
---|
90 | {
|
---|
91 | /* Too many NotifyUpdate events, sleep for a small amount to give the main thread time
|
---|
92 | * to handle these events. The SDL queue can hold up to 128 events. */
|
---|
93 | Log(("PushNotifyUpdateEvent: Sleep 1ms\n"));
|
---|
94 | RTThreadSleep(1);
|
---|
95 | }
|
---|
96 | else
|
---|
97 | RTThreadYield();
|
---|
98 | }
|
---|
99 | #endif /* VBOXSDL_WITH_X11 */
|
---|
100 |
|
---|
101 |
|
---|
102 | //
|
---|
103 | // Constructor / destructor
|
---|
104 | //
|
---|
105 |
|
---|
106 | Framebuffer::Framebuffer(Display *pDisplay, uint32_t uScreenId,
|
---|
107 | bool fFullscreen, bool fResizable, bool fShowSDLConfig,
|
---|
108 | bool fKeepHostRes, uint32_t u32FixedWidth,
|
---|
109 | uint32_t u32FixedHeight, uint32_t u32FixedBPP,
|
---|
110 | bool fUpdateImage)
|
---|
111 | {
|
---|
112 | LogFlow(("Framebuffer::Framebuffer\n"));
|
---|
113 |
|
---|
114 | m_pDisplay = pDisplay;
|
---|
115 | mScreenId = uScreenId;
|
---|
116 | mfUpdateImage = fUpdateImage;
|
---|
117 | mpWindow = NULL;
|
---|
118 | mpTexture = NULL;
|
---|
119 | mpRenderer = NULL;
|
---|
120 | mSurfVRAM = NULL;
|
---|
121 | mfInitialized = false;
|
---|
122 | mfFullscreen = fFullscreen;
|
---|
123 | mfKeepHostRes = fKeepHostRes;
|
---|
124 | mTopOffset = 0;
|
---|
125 | mfResizable = fResizable;
|
---|
126 | mfShowSDLConfig = fShowSDLConfig;
|
---|
127 | mFixedSDLWidth = u32FixedWidth;
|
---|
128 | mFixedSDLHeight = u32FixedHeight;
|
---|
129 | mFixedSDLBPP = u32FixedBPP;
|
---|
130 | mCenterXOffset = 0;
|
---|
131 | mCenterYOffset = 0;
|
---|
132 | /* Start with standard screen dimensions. */
|
---|
133 | mGuestXRes = 640;
|
---|
134 | mGuestYRes = 480;
|
---|
135 | mPtrVRAM = NULL;
|
---|
136 | mBitsPerPixel = 0;
|
---|
137 | mBytesPerLine = 0;
|
---|
138 | mfSameSizeRequested = false;
|
---|
139 | mfUpdates = false;
|
---|
140 |
|
---|
141 | int rc = RTCritSectInit(&mUpdateLock);
|
---|
142 | AssertMsg(rc == VINF_SUCCESS, ("Error from RTCritSectInit!\n"));
|
---|
143 |
|
---|
144 | resizeGuest();
|
---|
145 | mfInitialized = true;
|
---|
146 |
|
---|
147 | rc = SDL_GetRendererInfo(mpRenderer, &mRenderInfo);
|
---|
148 | if (RT_SUCCESS(rc))
|
---|
149 | {
|
---|
150 | if (fShowSDLConfig)
|
---|
151 | RTPrintf("Render info:\n"
|
---|
152 | " Name: %s\n"
|
---|
153 | " Render flags: 0x%x\n"
|
---|
154 | " SDL video driver: %s\n",
|
---|
155 | mRenderInfo.name,
|
---|
156 | mRenderInfo.flags,
|
---|
157 | RTEnvGet("SDL_VIDEODRIVER"));
|
---|
158 | }
|
---|
159 | }
|
---|
160 |
|
---|
161 |
|
---|
162 | Framebuffer::~Framebuffer()
|
---|
163 | {
|
---|
164 | LogFlow(("Framebuffer::~Framebuffer\n"));
|
---|
165 | if (mSurfVRAM)
|
---|
166 | {
|
---|
167 | SDL_FreeSurface(mSurfVRAM);
|
---|
168 | mSurfVRAM = NULL;
|
---|
169 | }
|
---|
170 | RTCritSectDelete(&mUpdateLock);
|
---|
171 | }
|
---|
172 |
|
---|
173 | /* static */
|
---|
174 | bool Framebuffer::init(bool fShowSDLConfig)
|
---|
175 | {
|
---|
176 | LogFlow(("VBoxSDLFB::init\n"));
|
---|
177 |
|
---|
178 | /* memorize the thread that inited us, that's the SDL thread */
|
---|
179 | gSdlNativeThread = RTThreadNativeSelf();
|
---|
180 |
|
---|
181 | #ifdef RT_OS_WINDOWS
|
---|
182 | /* default to DirectX if nothing else set */
|
---|
183 | if (!RTEnvExist("SDL_VIDEODRIVER"))
|
---|
184 | {
|
---|
185 | RTEnvSet("SDL_VIDEODRIVER", "directx");
|
---|
186 | }
|
---|
187 | #endif
|
---|
188 | #ifdef VBOXSDL_WITH_X11
|
---|
189 | /* On some X servers the mouse is stuck inside the bottom right corner.
|
---|
190 | * See http://wiki.clug.org.za/wiki/QEMU_mouse_not_working */
|
---|
191 | RTEnvSet("SDL_VIDEO_X11_DGAMOUSE", "0");
|
---|
192 | #endif
|
---|
193 |
|
---|
194 | /* create SDL event semaphore */
|
---|
195 | int vrc = RTSemEventCreate(&g_EventSemSDLEvents);
|
---|
196 | AssertReleaseRC(vrc);
|
---|
197 |
|
---|
198 | int rc = SDL_InitSubSystem(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_NOPARACHUTE);
|
---|
199 | if (rc != 0)
|
---|
200 | {
|
---|
201 | RTPrintf("SDL Error: '%s'\n", SDL_GetError());
|
---|
202 | return false;
|
---|
203 | }
|
---|
204 | gfSdlInitialized = true;
|
---|
205 |
|
---|
206 | RT_NOREF(fShowSDLConfig);
|
---|
207 | return true;
|
---|
208 | }
|
---|
209 |
|
---|
210 | /**
|
---|
211 | * Terminate SDL
|
---|
212 | *
|
---|
213 | * @remarks must be called from the SDL thread!
|
---|
214 | */
|
---|
215 | void Framebuffer::uninit()
|
---|
216 | {
|
---|
217 | if (gfSdlInitialized)
|
---|
218 | {
|
---|
219 | AssertMsg(gSdlNativeThread == RTThreadNativeSelf(), ("Wrong thread! SDL is not threadsafe!\n"));
|
---|
220 | SDL_QuitSubSystem(SDL_INIT_VIDEO);
|
---|
221 | }
|
---|
222 | }
|
---|
223 |
|
---|
224 | /**
|
---|
225 | * Returns the current framebuffer width in pixels.
|
---|
226 | *
|
---|
227 | * @returns COM status code
|
---|
228 | * @param width Address of result buffer.
|
---|
229 | */
|
---|
230 | uint32_t Framebuffer::getWidth()
|
---|
231 | {
|
---|
232 | LogFlow(("Framebuffer::getWidth\n"));
|
---|
233 | return mGuestXRes;
|
---|
234 | }
|
---|
235 |
|
---|
236 | /**
|
---|
237 | * Returns the current framebuffer height in pixels.
|
---|
238 | *
|
---|
239 | * @returns COM status code
|
---|
240 | * @param height Address of result buffer.
|
---|
241 | */
|
---|
242 | uint32_t Framebuffer::getHeight()
|
---|
243 | {
|
---|
244 | LogFlow(("Framebuffer::getHeight\n"));
|
---|
245 | return mGuestYRes;
|
---|
246 | }
|
---|
247 |
|
---|
248 | /**
|
---|
249 | * Return the current framebuffer color depth.
|
---|
250 | *
|
---|
251 | * @returns COM status code
|
---|
252 | * @param bitsPerPixel Address of result variable
|
---|
253 | */
|
---|
254 | uint32_t Framebuffer::getBitsPerPixel()
|
---|
255 | {
|
---|
256 | LogFlow(("Framebuffer::getBitsPerPixel\n"));
|
---|
257 | /* get the information directly from the surface in use */
|
---|
258 | Assert(mSurfVRAM);
|
---|
259 | return mSurfVRAM ? mSurfVRAM->format->BitsPerPixel : 0;
|
---|
260 | }
|
---|
261 |
|
---|
262 | /**
|
---|
263 | * Return the current framebuffer line size in bytes.
|
---|
264 | *
|
---|
265 | * @returns COM status code.
|
---|
266 | * @param lineSize Address of result variable.
|
---|
267 | */
|
---|
268 | uint32_t Framebuffer::getBytesPerLine()
|
---|
269 | {
|
---|
270 | LogFlow(("Framebuffer::getBytesPerLine\n"));
|
---|
271 | /* get the information directly from the surface */
|
---|
272 | Assert(mSurfVRAM);
|
---|
273 | return mSurfVRAM ? mSurfVRAM->pitch : 0;
|
---|
274 | }
|
---|
275 |
|
---|
276 | /**
|
---|
277 | * Return the current framebuffer pixel data buffer.
|
---|
278 | *
|
---|
279 | * @returns COM status code.
|
---|
280 | * @param lineSize Address of result variable.
|
---|
281 | */
|
---|
282 | uint8_t *Framebuffer::getPixelData()
|
---|
283 | {
|
---|
284 | LogFlow(("Framebuffer::getPixelData\n"));
|
---|
285 | /* get the information directly from the surface */
|
---|
286 | Assert(mSurfVRAM);
|
---|
287 | return mSurfVRAM ? (uint8_t *)mSurfVRAM->pixels : NULL;
|
---|
288 | }
|
---|
289 |
|
---|
290 | /**
|
---|
291 | * Notify framebuffer of an update.
|
---|
292 | *
|
---|
293 | * @returns COM status code
|
---|
294 | * @param x Update region upper left corner x value.
|
---|
295 | * @param y Update region upper left corner y value.
|
---|
296 | * @param w Update region width in pixels.
|
---|
297 | * @param h Update region height in pixels.
|
---|
298 | * @param finished Address of output flag whether the update
|
---|
299 | * could be fully processed in this call (which
|
---|
300 | * has to return immediately) or VBox should wait
|
---|
301 | * for a call to the update complete API before
|
---|
302 | * continuing with display updates.
|
---|
303 | */
|
---|
304 | int Framebuffer::notifyUpdate(uint32_t x, uint32_t y,
|
---|
305 | uint32_t w, uint32_t h)
|
---|
306 | {
|
---|
307 | /*
|
---|
308 | * The input values are in guest screen coordinates.
|
---|
309 | */
|
---|
310 | LogFlow(("Framebuffer::notifyUpdate: x = %d, y = %d, w = %d, h = %d\n",
|
---|
311 | x, y, w, h));
|
---|
312 |
|
---|
313 | #if defined(VBOXSDL_WITH_X11) || defined(RT_OS_DARWIN)
|
---|
314 | /*
|
---|
315 | * SDL does not allow us to make this call from any other thread than
|
---|
316 | * the main SDL thread (which initialized the video mode). So we have
|
---|
317 | * to send an event to the main SDL thread and process it there. For
|
---|
318 | * sake of simplicity, we encode all information in the event parameters.
|
---|
319 | */
|
---|
320 | SDL_Event event;
|
---|
321 | event.type = SDL_USEREVENT;
|
---|
322 | event.user.code = mScreenId;
|
---|
323 | event.user.type = SDL_USER_EVENT_UPDATERECT;
|
---|
324 |
|
---|
325 | SDL_Rect *pUpdateRect = (SDL_Rect *)RTMemAlloc(sizeof(SDL_Rect));
|
---|
326 | AssertPtrReturn(pUpdateRect, VERR_NO_MEMORY);
|
---|
327 | pUpdateRect->x = x;
|
---|
328 | pUpdateRect->y = y;
|
---|
329 | pUpdateRect->w = w;
|
---|
330 | pUpdateRect->h = h;
|
---|
331 | event.user.data1 = pUpdateRect;
|
---|
332 |
|
---|
333 | event.user.data2 = NULL;
|
---|
334 | PushNotifyUpdateEvent(&event);
|
---|
335 | #else /* !VBOXSDL_WITH_X11 */
|
---|
336 | update(x, y, w, h, true /* fGuestRelative */);
|
---|
337 | #endif /* !VBOXSDL_WITH_X11 */
|
---|
338 |
|
---|
339 | return VINF_SUCCESS;
|
---|
340 | }
|
---|
341 |
|
---|
342 |
|
---|
343 | int Framebuffer::NotifyUpdateImage(uint32_t aX,
|
---|
344 | uint32_t aY,
|
---|
345 | uint32_t aWidth,
|
---|
346 | uint32_t aHeight,
|
---|
347 | void *pvImage)
|
---|
348 | {
|
---|
349 | LogFlow(("NotifyUpdateImage: %d,%d %dx%d\n", aX, aY, aWidth, aHeight));
|
---|
350 |
|
---|
351 | /* Copy to mSurfVRAM. */
|
---|
352 | SDL_Rect srcRect;
|
---|
353 | SDL_Rect dstRect;
|
---|
354 | srcRect.x = 0;
|
---|
355 | srcRect.y = 0;
|
---|
356 | srcRect.w = (uint16_t)aWidth;
|
---|
357 | srcRect.h = (uint16_t)aHeight;
|
---|
358 | dstRect.x = (int16_t)aX;
|
---|
359 | dstRect.y = (int16_t)aY;
|
---|
360 | dstRect.w = (uint16_t)aWidth;
|
---|
361 | dstRect.h = (uint16_t)aHeight;
|
---|
362 |
|
---|
363 | const uint32_t Rmask = 0x00FF0000, Gmask = 0x0000FF00, Bmask = 0x000000FF, Amask = 0;
|
---|
364 | SDL_Surface *surfSrc = SDL_CreateRGBSurfaceFrom(pvImage, aWidth, aHeight, 32, aWidth * 4,
|
---|
365 | Rmask, Gmask, Bmask, Amask);
|
---|
366 | if (surfSrc)
|
---|
367 | {
|
---|
368 | RTCritSectEnter(&mUpdateLock);
|
---|
369 | if (mfUpdates)
|
---|
370 | SDL_BlitSurface(surfSrc, &srcRect, mSurfVRAM, &dstRect);
|
---|
371 | RTCritSectLeave(&mUpdateLock);
|
---|
372 |
|
---|
373 | SDL_FreeSurface(surfSrc);
|
---|
374 | }
|
---|
375 |
|
---|
376 | return notifyUpdate(aX, aY, aWidth, aHeight);
|
---|
377 | }
|
---|
378 |
|
---|
379 | int Framebuffer::notifyChange(uint32_t aScreenId,
|
---|
380 | uint32_t aXOrigin,
|
---|
381 | uint32_t aYOrigin,
|
---|
382 | uint32_t aWidth,
|
---|
383 | uint32_t aHeight)
|
---|
384 | {
|
---|
385 | LogRel(("NotifyChange: %d %d,%d %dx%d\n",
|
---|
386 | aScreenId, aXOrigin, aYOrigin, aWidth, aHeight));
|
---|
387 |
|
---|
388 | RTCritSectEnter(&mUpdateLock);
|
---|
389 |
|
---|
390 | /* Disable screen updates. */
|
---|
391 | mfUpdates = false;
|
---|
392 |
|
---|
393 | mGuestXRes = aWidth;
|
---|
394 | mGuestYRes = aHeight;
|
---|
395 | mPtrVRAM = NULL;
|
---|
396 | mBitsPerPixel = 0;
|
---|
397 | mBytesPerLine = 0;
|
---|
398 |
|
---|
399 | RTCritSectLeave(&mUpdateLock);
|
---|
400 |
|
---|
401 | SDL_Event event;
|
---|
402 | event.type = SDL_USEREVENT;
|
---|
403 | event.user.type = SDL_USER_EVENT_NOTIFYCHANGE;
|
---|
404 | event.user.code = mScreenId;
|
---|
405 |
|
---|
406 | PushSDLEventForSure(&event);
|
---|
407 |
|
---|
408 | RTThreadYield();
|
---|
409 |
|
---|
410 | return VINF_SUCCESS;
|
---|
411 | }
|
---|
412 |
|
---|
413 | //
|
---|
414 | // Internal public methods
|
---|
415 | //
|
---|
416 |
|
---|
417 | /* This method runs on the main SDL thread. */
|
---|
418 | void Framebuffer::notifyChange(uint32_t aScreenId)
|
---|
419 | {
|
---|
420 | /* Disable screen updates. */
|
---|
421 | RTCritSectEnter(&mUpdateLock);
|
---|
422 |
|
---|
423 | mPtrVRAM = NULL;
|
---|
424 | mBitsPerPixel = 32;
|
---|
425 | mBytesPerLine = mGuestXRes * 4;
|
---|
426 |
|
---|
427 | RTCritSectLeave(&mUpdateLock);
|
---|
428 |
|
---|
429 | resizeGuest();
|
---|
430 |
|
---|
431 | m_pDisplay->i_invalidateAndUpdateScreen(aScreenId);
|
---|
432 | }
|
---|
433 |
|
---|
434 | /**
|
---|
435 | * Method that does the actual resize of the guest framebuffer and
|
---|
436 | * then changes the SDL framebuffer setup.
|
---|
437 | */
|
---|
438 | void Framebuffer::resizeGuest()
|
---|
439 | {
|
---|
440 | LogFlowFunc (("mGuestXRes: %d, mGuestYRes: %d\n", mGuestXRes, mGuestYRes));
|
---|
441 | AssertMsg(gSdlNativeThread == RTThreadNativeSelf(),
|
---|
442 | ("Wrong thread! SDL is not threadsafe!\n"));
|
---|
443 |
|
---|
444 | RTCritSectEnter(&mUpdateLock);
|
---|
445 |
|
---|
446 | const uint32_t Rmask = 0x00FF0000, Gmask = 0x0000FF00, Bmask = 0x000000FF, Amask = 0;
|
---|
447 |
|
---|
448 | /* first free the current surface */
|
---|
449 | if (mSurfVRAM)
|
---|
450 | {
|
---|
451 | SDL_FreeSurface(mSurfVRAM);
|
---|
452 | mSurfVRAM = NULL;
|
---|
453 | }
|
---|
454 |
|
---|
455 | if (mPtrVRAM)
|
---|
456 | {
|
---|
457 | /* Create a source surface from the source bitmap. */
|
---|
458 | mSurfVRAM = SDL_CreateRGBSurfaceFrom(mPtrVRAM, mGuestXRes, mGuestYRes, mBitsPerPixel,
|
---|
459 | mBytesPerLine, Rmask, Gmask, Bmask, Amask);
|
---|
460 | LogFlow(("Framebuffer:: using the source bitmap\n"));
|
---|
461 | }
|
---|
462 | else
|
---|
463 | {
|
---|
464 | mSurfVRAM = SDL_CreateRGBSurface(SDL_SWSURFACE, mGuestXRes, mGuestYRes, 32,
|
---|
465 | Rmask, Gmask, Bmask, Amask);
|
---|
466 | LogFlow(("Framebuffer:: using SDL_SWSURFACE\n"));
|
---|
467 | }
|
---|
468 | LogFlow(("Framebuffer:: created VRAM surface %p\n", mSurfVRAM));
|
---|
469 |
|
---|
470 | if (mfSameSizeRequested)
|
---|
471 | {
|
---|
472 | mfSameSizeRequested = false;
|
---|
473 | LogFlow(("Framebuffer:: the same resolution requested, skipping the resize.\n"));
|
---|
474 | }
|
---|
475 | else
|
---|
476 | {
|
---|
477 | /* now adjust the SDL resolution */
|
---|
478 | resizeSDL();
|
---|
479 | }
|
---|
480 |
|
---|
481 | /* Enable screen updates. */
|
---|
482 | mfUpdates = true;
|
---|
483 |
|
---|
484 | RTCritSectLeave(&mUpdateLock);
|
---|
485 |
|
---|
486 | repaint();
|
---|
487 | }
|
---|
488 |
|
---|
489 | /**
|
---|
490 | * Sets SDL video mode. This is independent from guest video
|
---|
491 | * mode changes.
|
---|
492 | *
|
---|
493 | * @remarks Must be called from the SDL thread!
|
---|
494 | */
|
---|
495 | void Framebuffer::resizeSDL(void)
|
---|
496 | {
|
---|
497 | LogFlow(("VBoxSDL:resizeSDL\n"));
|
---|
498 |
|
---|
499 | const int cDisplays = SDL_GetNumVideoDisplays();
|
---|
500 | if (cDisplays > 0)
|
---|
501 | {
|
---|
502 | for (int d = 0; d < cDisplays; d++)
|
---|
503 | {
|
---|
504 | const int cDisplayModes = SDL_GetNumDisplayModes(d);
|
---|
505 | for (int m = 0; m < cDisplayModes; m++)
|
---|
506 | {
|
---|
507 | SDL_DisplayMode mode = { SDL_PIXELFORMAT_UNKNOWN, 0, 0, 0, 0 };
|
---|
508 | if (SDL_GetDisplayMode(d, m, &mode) != 0)
|
---|
509 | {
|
---|
510 | RTPrintf("Display #%d, mode %d:\t\t%i bpp\t%i x %i",
|
---|
511 | SDL_BITSPERPIXEL(mode.format), mode.w, mode.h);
|
---|
512 | }
|
---|
513 |
|
---|
514 | if (m == 0)
|
---|
515 | {
|
---|
516 | /*
|
---|
517 | * according to the SDL documentation, the API guarantees that
|
---|
518 | * the modes are sorted from larger to smaller, so we just
|
---|
519 | * take the first entry as the maximum.
|
---|
520 | */
|
---|
521 | mMaxScreenWidth = mode.w;
|
---|
522 | mMaxScreenHeight = mode.h;
|
---|
523 | }
|
---|
524 |
|
---|
525 | /* Keep going. */
|
---|
526 | }
|
---|
527 | }
|
---|
528 | }
|
---|
529 | else
|
---|
530 | AssertFailed(); /** @todo */
|
---|
531 |
|
---|
532 | uint32_t newWidth;
|
---|
533 | uint32_t newHeight;
|
---|
534 |
|
---|
535 | /* reset the centering offsets */
|
---|
536 | mCenterXOffset = 0;
|
---|
537 | mCenterYOffset = 0;
|
---|
538 |
|
---|
539 | /* we either have a fixed SDL resolution or we take the guest's */
|
---|
540 | if (mFixedSDLWidth != ~(uint32_t)0)
|
---|
541 | {
|
---|
542 | newWidth = mFixedSDLWidth;
|
---|
543 | newHeight = mFixedSDLHeight;
|
---|
544 | }
|
---|
545 | else
|
---|
546 | {
|
---|
547 | newWidth = RT_MIN(mGuestXRes, mMaxScreenWidth);
|
---|
548 | newHeight = RT_MIN(mGuestYRes, mMaxScreenHeight);
|
---|
549 | }
|
---|
550 |
|
---|
551 | /* we don't have any extra space by default */
|
---|
552 | mTopOffset = 0;
|
---|
553 |
|
---|
554 | int sdlWindowFlags = SDL_WINDOW_SHOWN;
|
---|
555 | if (mfResizable)
|
---|
556 | sdlWindowFlags |= SDL_WINDOW_RESIZABLE;
|
---|
557 | if (!mpWindow)
|
---|
558 | {
|
---|
559 | SDL_DisplayMode desktop_mode;
|
---|
560 | int x = 40 + mScreenId * 20;
|
---|
561 | int y = 40 + mScreenId * 15;
|
---|
562 |
|
---|
563 | SDL_GetDesktopDisplayMode(mScreenId, &desktop_mode);
|
---|
564 | /* create new window */
|
---|
565 |
|
---|
566 | char szTitle[64];
|
---|
567 | RTStrPrintf(szTitle, sizeof(szTitle), "SDL window %d", mScreenId);
|
---|
568 | mpWindow = SDL_CreateWindow(szTitle, x, y,
|
---|
569 | newWidth, newHeight, sdlWindowFlags);
|
---|
570 | mpRenderer = SDL_CreateRenderer(mpWindow, -1, 0 /* SDL_RendererFlags */);
|
---|
571 | if (mpRenderer)
|
---|
572 | {
|
---|
573 | SDL_GetRendererInfo(mpRenderer, &mRenderInfo);
|
---|
574 |
|
---|
575 | mpTexture = SDL_CreateTexture(mpRenderer, desktop_mode.format,
|
---|
576 | SDL_TEXTUREACCESS_STREAMING, newWidth, newHeight);
|
---|
577 | if (!mpTexture)
|
---|
578 | AssertReleaseFailed();
|
---|
579 | }
|
---|
580 | else
|
---|
581 | AssertReleaseFailed();
|
---|
582 |
|
---|
583 | if (12320 == g_cbIco64x01)
|
---|
584 | {
|
---|
585 | gWMIcon = SDL_CreateRGBSurface(0 /* Flags, must be 0 */, 64, 64, 24, 0xff, 0xff00, 0xff0000, 0);
|
---|
586 | /** @todo make it as simple as possible. No PNM interpreter here... */
|
---|
587 | if (gWMIcon)
|
---|
588 | {
|
---|
589 | memcpy(gWMIcon->pixels, g_abIco64x01+32, g_cbIco64x01-32);
|
---|
590 | SDL_SetWindowIcon(mpWindow, gWMIcon);
|
---|
591 | }
|
---|
592 | }
|
---|
593 | }
|
---|
594 | else
|
---|
595 | {
|
---|
596 | int w, h;
|
---|
597 | uint32_t format;
|
---|
598 | int access;
|
---|
599 |
|
---|
600 | /* resize current window */
|
---|
601 | SDL_GetWindowSize(mpWindow, &w, &h);
|
---|
602 |
|
---|
603 | if (w != (int)newWidth || h != (int)newHeight)
|
---|
604 | SDL_SetWindowSize(mpWindow, newWidth, newHeight);
|
---|
605 |
|
---|
606 | SDL_QueryTexture(mpTexture, &format, &access, &w, &h);
|
---|
607 | SDL_DestroyTexture(mpTexture);
|
---|
608 | mpTexture = SDL_CreateTexture(mpRenderer, format, access, newWidth, newHeight);
|
---|
609 | if (!mpTexture)
|
---|
610 | AssertReleaseFailed();
|
---|
611 | }
|
---|
612 | }
|
---|
613 |
|
---|
614 | /**
|
---|
615 | * Update specified framebuffer area. The coordinates can either be
|
---|
616 | * relative to the guest framebuffer or relative to the screen.
|
---|
617 | *
|
---|
618 | * @remarks Must be called from the SDL thread on Linux!
|
---|
619 | * @param x left column
|
---|
620 | * @param y top row
|
---|
621 | * @param w width in pixels
|
---|
622 | * @param h height in pixels
|
---|
623 | * @param fGuestRelative flag whether the above values are guest relative or screen relative;
|
---|
624 | */
|
---|
625 | void Framebuffer::update(int x, int y, int w, int h, bool fGuestRelative)
|
---|
626 | {
|
---|
627 | #if defined(VBOXSDL_WITH_X11) || defined(RT_OS_DARWIN)
|
---|
628 | AssertMsg(gSdlNativeThread == RTThreadNativeSelf(), ("Wrong thread! SDL is not threadsafe!\n"));
|
---|
629 | #endif
|
---|
630 | RTCritSectEnter(&mUpdateLock);
|
---|
631 | Log3Func(("mfUpdates=%RTbool %d,%d %dx%d\n", mfUpdates, x, y, w, h));
|
---|
632 | if (!mfUpdates)
|
---|
633 | {
|
---|
634 | RTCritSectLeave(&mUpdateLock);
|
---|
635 | return;
|
---|
636 | }
|
---|
637 |
|
---|
638 | Assert(mSurfVRAM);
|
---|
639 | if (!mSurfVRAM)
|
---|
640 | {
|
---|
641 | RTCritSectLeave(&mUpdateLock);
|
---|
642 | return;
|
---|
643 | }
|
---|
644 |
|
---|
645 | /* this is how many pixels we have to cut off from the height for this specific blit */
|
---|
646 | int const yCutoffGuest = 0;
|
---|
647 |
|
---|
648 | /**
|
---|
649 | * If we get a SDL window relative update, we
|
---|
650 | * just perform a full screen update to keep things simple.
|
---|
651 | *
|
---|
652 | * @todo improve
|
---|
653 | */
|
---|
654 | if (!fGuestRelative)
|
---|
655 | {
|
---|
656 | x = 0;
|
---|
657 | w = mGuestXRes;
|
---|
658 | y = 0;
|
---|
659 | h = mGuestYRes;
|
---|
660 | }
|
---|
661 |
|
---|
662 | SDL_Rect srcRect;
|
---|
663 | srcRect.x = x;
|
---|
664 | srcRect.y = y + yCutoffGuest;
|
---|
665 | srcRect.w = w;
|
---|
666 | srcRect.h = RT_MAX(0, h - yCutoffGuest);
|
---|
667 |
|
---|
668 | /*
|
---|
669 | * Destination rectangle is just offset by the label height.
|
---|
670 | * There are two cases though: label height is added to the
|
---|
671 | * guest resolution (mTopOffset == mLabelHeight; yCutoffGuest == 0)
|
---|
672 | * or the label cuts off a portion of the guest screen (mTopOffset == 0;
|
---|
673 | * yCutoffGuest >= 0)
|
---|
674 | */
|
---|
675 | SDL_Rect dstRect;
|
---|
676 | dstRect.x = x + mCenterXOffset;
|
---|
677 | dstRect.y = y + yCutoffGuest + mTopOffset + mCenterYOffset;
|
---|
678 | dstRect.w = w;
|
---|
679 | dstRect.h = RT_MAX(0, h - yCutoffGuest);
|
---|
680 |
|
---|
681 | /* Calculate the offset within the VRAM to update the streaming texture directly. */
|
---|
682 | uint8_t const *pbOff = (uint8_t *)mSurfVRAM->pixels
|
---|
683 | + (srcRect.y * mBytesPerLine) + (srcRect.x * (mBitsPerPixel / 8));
|
---|
684 | SDL_UpdateTexture(mpTexture, &srcRect, pbOff, mSurfVRAM->pitch);
|
---|
685 | SDL_RenderCopy(mpRenderer, mpTexture, NULL, NULL);
|
---|
686 |
|
---|
687 | RTCritSectLeave(&mUpdateLock);
|
---|
688 |
|
---|
689 | SDL_RenderPresent(mpRenderer);
|
---|
690 | }
|
---|
691 |
|
---|
692 | /**
|
---|
693 | * Repaint the whole framebuffer
|
---|
694 | *
|
---|
695 | * @remarks Must be called from the SDL thread!
|
---|
696 | */
|
---|
697 | void Framebuffer::repaint()
|
---|
698 | {
|
---|
699 | AssertMsg(gSdlNativeThread == RTThreadNativeSelf(), ("Wrong thread! SDL is not threadsafe!\n"));
|
---|
700 | LogFlow(("Framebuffer::repaint\n"));
|
---|
701 | int w, h;
|
---|
702 | uint32_t format;
|
---|
703 | int access;
|
---|
704 | SDL_QueryTexture(mpTexture, &format, &access, &w, &h);
|
---|
705 | update(0, 0, w, h, false /* fGuestRelative */);
|
---|
706 | }
|
---|
707 |
|
---|
708 | /**
|
---|
709 | * Toggle fullscreen mode
|
---|
710 | *
|
---|
711 | * @remarks Must be called from the SDL thread!
|
---|
712 | */
|
---|
713 | void Framebuffer::setFullscreen(bool fFullscreen)
|
---|
714 | {
|
---|
715 | AssertMsg(gSdlNativeThread == RTThreadNativeSelf(), ("Wrong thread! SDL is not threadsafe!\n"));
|
---|
716 | LogFlow(("Framebuffer::SetFullscreen: fullscreen: %d\n", fFullscreen));
|
---|
717 | mfFullscreen = fFullscreen;
|
---|
718 | /* only change the SDL resolution, do not touch the guest framebuffer */
|
---|
719 | resizeSDL();
|
---|
720 | repaint();
|
---|
721 | }
|
---|
722 |
|
---|
723 | /**
|
---|
724 | * Return the geometry of the host. This isn't very well tested but it seems
|
---|
725 | * to work at least on Linux hosts.
|
---|
726 | */
|
---|
727 | void Framebuffer::getFullscreenGeometry(uint32_t *width, uint32_t *height)
|
---|
728 | {
|
---|
729 | SDL_DisplayMode dm;
|
---|
730 | int rc = SDL_GetDesktopDisplayMode(0, &dm); /** @BUGBUG Handle multi monitor setups! */
|
---|
731 | if (rc == 0)
|
---|
732 | {
|
---|
733 | *width = dm.w;
|
---|
734 | *height = dm.w;
|
---|
735 | }
|
---|
736 | }
|
---|
737 |
|
---|
738 | int Framebuffer::setWindowTitle(const char *pcszTitle)
|
---|
739 | {
|
---|
740 | SDL_SetWindowTitle(mpWindow, pcszTitle);
|
---|
741 |
|
---|
742 | return VINF_SUCCESS;
|
---|
743 | }
|
---|