VirtualBox

source: vbox/trunk/src/VBox/HostServices/SharedOpenGL/crserverlib/server_muralfbo.c@ 43182

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

crOpenGL: 1. VRDP+3D fixes, 2. Generic bugfixes, 3. Intel GPU-related fixes

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 19.5 KB
Line 
1/* $Id: server_muralfbo.c 43182 2012-09-04 19:21:41Z vboxsync $ */
2
3/** @file
4 * VBox crOpenGL: Window to FBO redirect support.
5 */
6
7/*
8 * Copyright (C) 2010 Oracle Corporation
9 *
10 * This file is part of VirtualBox Open Source Edition (OSE), as
11 * available from http://www.215389.xyz. This file is free software;
12 * you can redistribute it and/or modify it under the terms of the GNU
13 * General Public License (GPL) as published by the Free Software
14 * Foundation, in version 2 as it comes in the "COPYING" file of the
15 * VirtualBox OSE distribution. VirtualBox OSE is distributed in the
16 * hope that it will be useful, but WITHOUT ANY WARRANTY of any kind.
17 */
18
19#include "server.h"
20#include "cr_string.h"
21#include "cr_mem.h"
22#include "render/renderspu.h"
23
24static int crServerGetPointScreen(GLint x, GLint y)
25{
26 int i;
27
28 for (i=0; i<cr_server.screenCount; ++i)
29 {
30 if ((x>=cr_server.screen[i].x && x<cr_server.screen[i].x+(int)cr_server.screen[i].w)
31 && (y>=cr_server.screen[i].y && y<cr_server.screen[i].y+(int)cr_server.screen[i].h))
32 {
33 return i;
34 }
35 }
36
37 return -1;
38}
39
40static GLboolean crServerMuralCoverScreen(CRMuralInfo *mural, int sId)
41{
42 return mural->gX < cr_server.screen[sId].x
43 && mural->gX+(int)mural->width > cr_server.screen[sId].x+(int)cr_server.screen[sId].w
44 && mural->gY < cr_server.screen[sId].y
45 && mural->gY+(int)mural->height > cr_server.screen[sId].y+(int)cr_server.screen[sId].h;
46}
47
48/* Called when a new CRMuralInfo is created
49 * or when OutputRedirect status is changed.
50 */
51void crServerSetupOutputRedirect(CRMuralInfo *mural)
52{
53 /* Unset the previous redirect. */
54 if (mural->pvOutputRedirectInstance)
55 {
56 cr_server.outputRedirect.CROREnd(mural->pvOutputRedirectInstance);
57 mural->pvOutputRedirectInstance = NULL;
58 }
59
60 /* Setup a new redirect. */
61 if (cr_server.bUseOutputRedirect)
62 {
63 /* Query supported formats. */
64 uint32_t cbFormats = 4096;
65 char *pachFormats = (char *)crAlloc(cbFormats);
66
67 if (pachFormats)
68 {
69 int rc = cr_server.outputRedirect.CRORContextProperty(cr_server.outputRedirect.pvContext,
70 0 /* H3DOR_PROP_FORMATS */, // @todo from a header
71 pachFormats, cbFormats, &cbFormats);
72 if (RT_SUCCESS(rc))
73 {
74 if (RTStrStr(pachFormats, "H3DOR_FMT_RGBA_TOPDOWN"))
75 {
76 cr_server.outputRedirect.CRORBegin(cr_server.outputRedirect.pvContext,
77 &mural->pvOutputRedirectInstance,
78 "H3DOR_FMT_RGBA_TOPDOWN"); // @todo from a header
79 }
80 }
81
82 crFree(pachFormats);
83 }
84
85 /* If this is not NULL then there was a supported format. */
86 if (mural->pvOutputRedirectInstance)
87 {
88 cr_server.outputRedirect.CRORGeometry(mural->pvOutputRedirectInstance,
89 mural->hX, mural->hY,
90 mural->width, mural->height);
91 // @todo the code assumes that RTRECT == four of GLInts
92 cr_server.outputRedirect.CRORVisibleRegion(mural->pvOutputRedirectInstance,
93 mural->cVisibleRects, (RTRECT *)mural->pVisibleRects);
94 }
95 }
96}
97
98void crServerCheckMuralGeometry(CRMuralInfo *mural)
99{
100 int tlS, brS, trS, blS;
101 int overlappingScreenCount, primaryS, i;
102
103 if (!mural->width || !mural->height)
104 return;
105
106 if (cr_server.screenCount<2 && !cr_server.bForceOffscreenRendering)
107 {
108 CRScreenViewportInfo *pVieport = &cr_server.screenVieport[mural->screenId];
109 CRASSERT(cr_server.screenCount>0);
110
111 mural->hX = mural->gX-cr_server.screen[0].x;
112 mural->hY = mural->gY-cr_server.screen[0].y;
113
114 cr_server.head_spu->dispatch_table.WindowPosition(mural->spuWindow, mural->hX - pVieport->x, mural->hY - pVieport->y);
115
116 return;
117 }
118
119 tlS = crServerGetPointScreen(mural->gX, mural->gY);
120 brS = crServerGetPointScreen(mural->gX+mural->width-1, mural->gY+mural->height-1);
121
122 if (tlS==brS && tlS>=0)
123 {
124 overlappingScreenCount = 1;
125 primaryS = tlS;
126 }
127 else
128 {
129 trS = crServerGetPointScreen(mural->gX+mural->width-1, mural->gY);
130 blS = crServerGetPointScreen(mural->gX, mural->gY+mural->height-1);
131
132 primaryS = -1; overlappingScreenCount = 0;
133 for (i=0; i<cr_server.screenCount; ++i)
134 {
135 if ((i==tlS) || (i==brS) || (i==trS) || (i==blS)
136 || crServerMuralCoverScreen(mural, i))
137 {
138 overlappingScreenCount++;
139 primaryS = primaryS<0 ? i:primaryS;
140 }
141 }
142
143 if (!overlappingScreenCount)
144 {
145 primaryS = 0;
146 }
147 }
148
149 if (primaryS!=mural->screenId)
150 {
151 mural->screenId = primaryS;
152
153 renderspuSetWindowId(cr_server.screen[primaryS].winID);
154 renderspuReparentWindow(mural->spuWindow);
155 renderspuSetWindowId(cr_server.screen[0].winID);
156 }
157
158 mural->hX = mural->gX-cr_server.screen[primaryS].x;
159 mural->hY = mural->gY-cr_server.screen[primaryS].y;
160
161 if (overlappingScreenCount<2 && !cr_server.bForceOffscreenRendering)
162 {
163 CRScreenViewportInfo *pVieport = &cr_server.screenVieport[mural->screenId];
164
165 if (mural->bUseFBO)
166 {
167 crServerRedirMuralFBO(mural, GL_FALSE);
168 crServerDeleteMuralFBO(mural);
169 }
170
171 cr_server.head_spu->dispatch_table.WindowPosition(mural->spuWindow, mural->hX - pVieport->x, mural->hY - pVieport->y);
172 }
173 else
174 {
175 if (mural->spuWindow)
176 {
177 if (!mural->bUseFBO)
178 {
179 crServerRedirMuralFBO(mural, GL_TRUE);
180 }
181 else
182 {
183 if (mural->width!=mural->fboWidth
184 || mural->height!=mural->height)
185 {
186 crServerRedirMuralFBO(mural, GL_FALSE);
187 crServerDeleteMuralFBO(mural);
188 crServerRedirMuralFBO(mural, GL_TRUE);
189 }
190 }
191 }
192#ifdef DEBUG_misha
193 else
194 {
195 Assert(!mural->bUseFBO);
196 }
197#endif
198
199 if (!mural->bUseFBO)
200 {
201 CRScreenViewportInfo *pVieport = &cr_server.screenVieport[mural->screenId];
202
203 cr_server.head_spu->dispatch_table.WindowPosition(mural->spuWindow, mural->hX - pVieport->x, mural->hY - pVieport->y);
204 }
205 }
206
207 if (mural->pvOutputRedirectInstance)
208 {
209 cr_server.outputRedirect.CRORGeometry(mural->pvOutputRedirectInstance,
210 mural->hX, mural->hY,
211 mural->width, mural->height);
212 }
213}
214
215GLboolean crServerSupportRedirMuralFBO(void)
216{
217 static GLboolean fInited = GL_FALSE;
218 static GLboolean fSupported = GL_FALSE;
219 if (!fInited)
220 {
221 const GLubyte* pExt = cr_server.head_spu->dispatch_table.GetString(GL_REAL_EXTENSIONS);
222
223 fSupported = ( NULL!=crStrstr((const char*)pExt, "GL_ARB_framebuffer_object")
224 || NULL!=crStrstr((const char*)pExt, "GL_EXT_framebuffer_object"))
225 && NULL!=crStrstr((const char*)pExt, "GL_ARB_texture_non_power_of_two");
226 fInited = GL_TRUE;
227 }
228 return fSupported;
229}
230
231void crServerRedirMuralFBO(CRMuralInfo *mural, GLboolean redir)
232{
233 if (redir)
234 {
235 if (!crServerSupportRedirMuralFBO())
236 {
237 crWarning("FBO not supported, can't redirect window output");
238 return;
239 }
240
241 cr_server.head_spu->dispatch_table.WindowShow(mural->spuWindow, GL_FALSE);
242
243 if (mural->idFBO==0)
244 {
245 crServerCreateMuralFBO(mural);
246 }
247
248 if (!crStateGetCurrent()->framebufferobject.drawFB)
249 {
250 cr_server.head_spu->dispatch_table.BindFramebufferEXT(GL_DRAW_FRAMEBUFFER, mural->idFBO);
251 }
252 if (!crStateGetCurrent()->framebufferobject.readFB)
253 {
254 cr_server.head_spu->dispatch_table.BindFramebufferEXT(GL_READ_FRAMEBUFFER, mural->idFBO);
255 }
256
257 if (cr_server.curClient && cr_server.curClient->currentMural == mural)
258 {
259 crStateGetCurrent()->buffer.width = 0;
260 crStateGetCurrent()->buffer.height = 0;
261 }
262 }
263 else
264 {
265 cr_server.head_spu->dispatch_table.WindowShow(mural->spuWindow, mural->bVisible);
266
267 if (mural->bUseFBO && crServerSupportRedirMuralFBO())
268 {
269 if (!crStateGetCurrent()->framebufferobject.drawFB)
270 {
271 cr_server.head_spu->dispatch_table.BindFramebufferEXT(GL_DRAW_FRAMEBUFFER, 0);
272 }
273 if (!crStateGetCurrent()->framebufferobject.readFB)
274 {
275 cr_server.head_spu->dispatch_table.BindFramebufferEXT(GL_READ_FRAMEBUFFER, 0);
276 }
277 }
278
279 if (cr_server.curClient && cr_server.curClient->currentMural == mural)
280 {
281 crStateGetCurrent()->buffer.width = mural->width;
282 crStateGetCurrent()->buffer.height = mural->height;
283 }
284 }
285
286 mural->bUseFBO = redir;
287}
288
289void crServerCreateMuralFBO(CRMuralInfo *mural)
290{
291 CRContext *ctx = crStateGetCurrent();
292 GLuint uid;
293 GLenum status;
294 SPUDispatchTable *gl = &cr_server.head_spu->dispatch_table;
295
296 CRASSERT(mural->idFBO==0);
297
298 /*Color texture*/
299 gl->GenTextures(1, &mural->idColorTex);
300 gl->BindTexture(GL_TEXTURE_2D, mural->idColorTex);
301 gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
302 gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
303 gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
304 gl->TexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
305 if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB))
306 {
307 gl->BindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, 0);
308 }
309 gl->TexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, mural->width, mural->height,
310 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL);
311
312 /*Depth&Stencil*/
313 gl->GenRenderbuffersEXT(1, &mural->idDepthStencilRB);
314 gl->BindRenderbufferEXT(GL_RENDERBUFFER_EXT, mural->idDepthStencilRB);
315 gl->RenderbufferStorageEXT(GL_RENDERBUFFER_EXT, GL_DEPTH24_STENCIL8_EXT,
316 mural->width, mural->height);
317
318 /*FBO*/
319 gl->GenFramebuffersEXT(1, &mural->idFBO);
320 gl->BindFramebufferEXT(GL_FRAMEBUFFER_EXT, mural->idFBO);
321
322 gl->FramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0_EXT,
323 GL_TEXTURE_2D, mural->idColorTex, 0);
324 gl->FramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_DEPTH_ATTACHMENT_EXT,
325 GL_RENDERBUFFER_EXT, mural->idDepthStencilRB);
326 gl->FramebufferRenderbufferEXT(GL_FRAMEBUFFER_EXT, GL_STENCIL_ATTACHMENT_EXT,
327 GL_RENDERBUFFER_EXT, mural->idDepthStencilRB);
328
329 status = gl->CheckFramebufferStatusEXT(GL_FRAMEBUFFER_EXT);
330 if (status!=GL_FRAMEBUFFER_COMPLETE_EXT)
331 {
332 crWarning("FBO status(0x%x) isn't complete", status);
333 }
334
335 mural->fboWidth = mural->width;
336 mural->fboHeight = mural->height;
337
338 /*PBO*/
339 if (cr_server.bUsePBOForReadback)
340 {
341 gl->GenBuffersARB(1, &mural->idPBO);
342 gl->BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mural->idPBO);
343 gl->BufferDataARB(GL_PIXEL_PACK_BUFFER_ARB, mural->width*mural->height*4, 0, GL_STREAM_READ_ARB);
344 gl->BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, ctx->bufferobject.packBuffer->hwid);
345
346 if (!mural->idPBO)
347 {
348 crWarning("PBO create failed");
349 }
350 }
351
352 /*Restore gl state*/
353 uid = ctx->texture.unit[ctx->texture.curTextureUnit].currentTexture2D->hwid;
354 gl->BindTexture(GL_TEXTURE_2D, uid);
355
356 uid = ctx->framebufferobject.renderbuffer ? ctx->framebufferobject.renderbuffer->hwid:0;
357 gl->BindRenderbufferEXT(GL_RENDERBUFFER_EXT, uid);
358
359 uid = ctx->framebufferobject.drawFB ? ctx->framebufferobject.drawFB->hwid:0;
360 gl->BindFramebufferEXT(GL_DRAW_FRAMEBUFFER, uid);
361
362 uid = ctx->framebufferobject.readFB ? ctx->framebufferobject.readFB->hwid:0;
363 gl->BindFramebufferEXT(GL_READ_FRAMEBUFFER, uid);
364
365 if (crStateIsBufferBound(GL_PIXEL_UNPACK_BUFFER_ARB))
366 {
367 gl->BindBufferARB(GL_PIXEL_UNPACK_BUFFER_ARB, ctx->bufferobject.unpackBuffer->hwid);
368 }
369}
370
371void crServerDeleteMuralFBO(CRMuralInfo *mural)
372{
373 CRASSERT(!mural->bUseFBO);
374
375 if (mural->idFBO!=0)
376 {
377 cr_server.head_spu->dispatch_table.DeleteTextures(1, &mural->idColorTex);
378 cr_server.head_spu->dispatch_table.DeleteRenderbuffersEXT(1, &mural->idDepthStencilRB);
379 cr_server.head_spu->dispatch_table.DeleteFramebuffersEXT(1, &mural->idFBO);
380
381 mural->idFBO = 0;
382 mural->idColorTex = 0;
383 mural->idDepthStencilRB = 0;
384 }
385
386 if (mural->idPBO!=0)
387 {
388 CRASSERT(cr_server.bUsePBOForReadback);
389 cr_server.head_spu->dispatch_table.DeleteBuffersARB(1, &mural->idPBO);
390 mural->idPBO = 0;
391 }
392}
393
394#define MIN(a, b) ((a) < (b) ? (a) : (b))
395#define MAX(a, b) ((a) > (b) ? (a) : (b))
396
397static GLboolean crServerIntersectRect(CRrecti *a, CRrecti *b, CRrecti *rect)
398{
399 CRASSERT(a && b && rect);
400
401 rect->x1 = MAX(a->x1, b->x1);
402 rect->x2 = MIN(a->x2, b->x2);
403 rect->y1 = MAX(a->y1, b->y1);
404 rect->y2 = MIN(a->y2, b->y2);
405
406 return (rect->x2>rect->x1) && (rect->y2>rect->y1);
407}
408
409static GLboolean crServerIntersectScreen(CRMuralInfo *mural, int sId, CRrecti *rect)
410{
411 rect->x1 = MAX(mural->gX, cr_server.screen[sId].x);
412 rect->x2 = MIN(mural->gX+(int)mural->fboWidth, cr_server.screen[sId].x+(int)cr_server.screen[sId].w);
413 rect->y1 = MAX(mural->gY, cr_server.screen[sId].y);
414 rect->y2 = MIN(mural->gY+(int)mural->fboHeight, cr_server.screen[sId].y+(int)cr_server.screen[sId].h);
415
416 return (rect->x2>rect->x1) && (rect->y2>rect->y1);
417}
418
419static void crServerCopySubImage(char *pDst, char* pSrc, CRrecti *pRect, int srcWidth, int srcHeight)
420{
421 int i;
422 int dstrowsize = 4*(pRect->x2-pRect->x1);
423 int srcrowsize = 4*srcWidth;
424 int height = pRect->y2-pRect->y1;
425
426 pSrc += 4*pRect->x1 + srcrowsize*(srcHeight-1-pRect->y1);
427
428 for (i=0; i<height; ++i)
429 {
430 crMemcpy(pDst, pSrc, dstrowsize);
431
432 pSrc -= srcrowsize;
433 pDst += dstrowsize;
434 }
435}
436
437static void crServerTransformRect(CRrecti *pDst, CRrecti *pSrc, int dx, int dy)
438{
439 pDst->x1 = pSrc->x1+dx;
440 pDst->x2 = pSrc->x2+dx;
441 pDst->y1 = pSrc->y1+dy;
442 pDst->y2 = pSrc->y2+dy;
443}
444
445void crServerPresentFBO(CRMuralInfo *mural)
446{
447 char *pixels=NULL, *tmppixels;
448 GLuint uid;
449 int i, j;
450 CRrecti rect, rectwr, sectr;
451 GLboolean bUsePBO;
452 CRContext *ctx = crStateGetCurrent();
453
454 CRASSERT(cr_server.pfnPresentFBO);
455
456 if (!mural->bVisible)
457 {
458 return;
459 }
460
461 if (!mural->width || !mural->height)
462 {
463 return;
464 }
465
466 if (cr_server.bUsePBOForReadback && !mural->idPBO)
467 {
468 crWarning("Mural doesn't have PBO even though bUsePBOForReadback is set!");
469 }
470
471 bUsePBO = cr_server.bUsePBOForReadback && mural->idPBO;
472
473 cr_server.head_spu->dispatch_table.BindTexture(GL_TEXTURE_2D, mural->idColorTex);
474
475 if (bUsePBO)
476 {
477 CRASSERT(mural->idPBO);
478 cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, mural->idPBO);
479 }
480 else
481 {
482 if (crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB))
483 {
484 cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, 0);
485 }
486
487 pixels = crAlloc(4*mural->fboWidth*mural->fboHeight);
488 if (!pixels)
489 {
490 crWarning("Out of memory in crServerPresentFBO");
491 return;
492 }
493 }
494
495 /*read the texture, note pixels are NULL for PBO case as it's offset in the buffer*/
496 cr_server.head_spu->dispatch_table.GetTexImage(GL_TEXTURE_2D, 0, GL_BGRA, GL_UNSIGNED_BYTE, pixels);
497
498 /*restore gl state*/
499 uid = ctx->texture.unit[ctx->texture.curTextureUnit].currentTexture2D->hwid;
500 cr_server.head_spu->dispatch_table.BindTexture(GL_TEXTURE_2D, uid);
501
502 if (bUsePBO)
503 {
504 pixels = cr_server.head_spu->dispatch_table.MapBufferARB(GL_PIXEL_PACK_BUFFER_ARB, GL_READ_ONLY);
505 if (!pixels)
506 {
507 crWarning("Failed to MapBuffer in crServerPresentFBO");
508 return;
509 }
510 }
511
512 for (i=0; i<cr_server.screenCount; ++i)
513 {
514 if (crServerIntersectScreen(mural, i, &rect))
515 {
516 /* rect in window relative coords */
517 crServerTransformRect(&rectwr, &rect, -mural->gX, -mural->gY);
518
519 if (!mural->pVisibleRects)
520 {
521 /*we don't get any rects info for guest compiz windows, so we treat windows as visible unless explicitly received 0 visible rects*/
522 if (!mural->bReceivedRects)
523 {
524 tmppixels = crAlloc(4*(rect.x2-rect.x1)*(rect.y2-rect.y1));
525 if (!tmppixels)
526 {
527 crWarning("Out of memory in crServerPresentFBO");
528 crFree(pixels);
529 return;
530 }
531
532 crServerCopySubImage(tmppixels, pixels, &rectwr, mural->fboWidth, mural->fboHeight);
533 /*Note: pfnPresentFBO would free tmppixels*/
534 cr_server.pfnPresentFBO(tmppixels, i, rect.x1-cr_server.screen[i].x, rect.y1-cr_server.screen[i].y, rect.x2-rect.x1, rect.y2-rect.y1);
535 }
536 }
537 else
538 {
539 for (j=0; j<mural->cVisibleRects; ++j)
540 {
541 if (crServerIntersectRect(&rectwr, (CRrecti*) &mural->pVisibleRects[4*j], &sectr))
542 {
543 tmppixels = crAlloc(4*(sectr.x2-sectr.x1)*(sectr.y2-sectr.y1));
544 if (!tmppixels)
545 {
546 crWarning("Out of memory in crServerPresentFBO");
547 crFree(pixels);
548 return;
549 }
550
551 crServerCopySubImage(tmppixels, pixels, &sectr, mural->fboWidth, mural->fboHeight);
552 /*Note: pfnPresentFBO would free tmppixels*/
553 cr_server.pfnPresentFBO(tmppixels, i,
554 sectr.x1+mural->gX-cr_server.screen[i].x,
555 sectr.y1+mural->gY-cr_server.screen[i].y,
556 sectr.x2-sectr.x1, sectr.y2-sectr.y1);
557 }
558 }
559 }
560 }
561 }
562
563 if (mural->pvOutputRedirectInstance)
564 {
565 /* @todo find out why presentfbo is not called but crorframe is called. */
566 cr_server.outputRedirect.CRORFrame(mural->pvOutputRedirectInstance,
567 pixels,
568 4 * mural->fboWidth * mural->fboHeight);
569 }
570
571 if (bUsePBO)
572 {
573 cr_server.head_spu->dispatch_table.UnmapBufferARB(GL_PIXEL_PACK_BUFFER_ARB);
574 cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, ctx->bufferobject.packBuffer->hwid);
575 }
576 else
577 {
578 crFree(pixels);
579 if (crStateIsBufferBound(GL_PIXEL_PACK_BUFFER_ARB))
580 {
581 cr_server.head_spu->dispatch_table.BindBufferARB(GL_PIXEL_PACK_BUFFER_ARB, ctx->bufferobject.packBuffer->hwid);
582 }
583 }
584}
585
586GLboolean crServerIsRedirectedToFBO()
587{
588 return cr_server.curClient
589 && cr_server.curClient->currentMural
590 && cr_server.curClient->currentMural->bUseFBO;
591}
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