PsychError SCREENTexturizeOffscreenWindows(void) { PsychWindowRecordType **windowRecordArray; int i, numWindows; //all subfunctions should have these two lines. PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; //check for superfluous or missing arguments PsychErrorExit(PsychCapNumInputArgs(0)); PsychErrorExit(PsychRequireNumInputArgs(0)); PsychCreateVolatileWindowRecordPointerList(&numWindows, &windowRecordArray); for(i=0;i<numWindows;i++){ if(PsychIsOffscreenWindow(windowRecordArray[i])){ PsychUpdateTargetWindowFromTargetDisplay(windowRecordArray[i]); PsychAllocateTexture(windowRecordArray[i]); PsychBindTexture(windowRecordArray[i]); PsychUpdateTexture(windowRecordArray[i]); } } PsychDestroyVolatileWindowRecordPointerList(windowRecordArray); return(PsychError_none); }
PsychError SCREENGetCapturedImage(void) { PsychWindowRecordType *windowRecord; PsychWindowRecordType *textureRecord; PsychRectType rect; double summed_intensity; int capturehandle = -1; int waitForImage = TRUE; int specialmode = 0; double timeout, tnow; double presentation_timestamp = 0; int rc=-1; double targetmemptr = 0; double* tsummed = NULL; psych_uint8 *targetmatrixptr = NULL; static rawcapimgdata rawCaptureBuffer = {0, 0, 0, NULL}; // All sub functions should have these two lines PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()) {PsychGiveHelp(); return(PsychError_none);}; PsychErrorExit(PsychCapNumInputArgs(6)); // Max. 6 input args. PsychErrorExit(PsychRequireNumInputArgs(2)); // Min. 2 input args required. PsychErrorExit(PsychCapNumOutputArgs(4)); // Max. 4 output args. // Get the window record from the window record argument and get info from the window record PsychAllocInWindowRecordArg(kPsychUseDefaultArgPosition, TRUE, &windowRecord); // Only onscreen windows allowed: if(!PsychIsOnscreenWindow(windowRecord) && !PsychIsOffscreenWindow(windowRecord)) { PsychErrorExitMsg(PsychError_user, "GetCapturedImage called on something else than an onscreen window or offscreen window."); } // Get the handle: PsychCopyInIntegerArg(2, TRUE, &capturehandle); if (capturehandle==-1) { PsychErrorExitMsg(PsychError_user, "GetCapturedImage called without valid handle to a capture object."); } // Get the 'waitForImage' flag: If waitForImage == true == 1, we'll do a blocking wait for // arrival of a new image. Otherwise we will return with a 0-Handle if there // isn't any new image available. PsychCopyInIntegerArg(3, FALSE, &waitForImage); // Special case waitForImage == 4? This would ask to call into the capture driver, but // not wait for any image to arrive and not return any information. This is only useful // on OS/X and Windows when using the capture engine for video recording to harddisk. In // that case we are not interested at all in the captured live video, we just want it to // get written to harddisk in the background. To keep the video encoder going, we need to // call its SGIdle() routine and waitForImage==4 does just that, call SGIdle(). if (waitForImage == 4) { // Perform the null-call to the capture engine, ie a SGIdle() on OS/X and Windows: PsychGetTextureFromCapture(windowRecord, capturehandle, 4, 0.0, NULL, NULL, NULL, NULL); // Done. Nothing to return... return(PsychError_none); } // Get the optional textureRecord for the optional texture handle. If the calling script // provides the texture handle of an existing Psychtoolbox texture that has a matching // format, then that texture is recycled by overwriting its previous content with the // image data from the new captured image. This can save some overhead for texture destruction // and recreation. While this is probably not noticeable on mid- to high-end gfx cards with // rectangle texture support, it can provide a significant speedup on low-end gfx cards with // only power-of-two texture support. textureRecord = NULL; if ((PsychGetNumInputArgs()>=4) && PsychIsWindowIndexArg(4)) PsychAllocInWindowRecordArg(4, FALSE, &textureRecord); // Get the optional specialmode flag: PsychCopyInIntegerArg(5, FALSE, &specialmode); // Set a 10 second maximum timeout for waiting for new frames: PsychGetAdjustedPrecisionTimerSeconds(&timeout); timeout+=10; while (rc==-1) { // We pass a checkForImage value of 2 if waitForImage>0. This way we can signal if we are in polling or blocking mode. // With the libdc1394 engine this allows to do a real blocking wait in the driver -- much more efficient than the spin-waiting approach! rc = PsychGetTextureFromCapture(windowRecord, capturehandle, ((waitForImage>0 && waitForImage<3) ? 2 : 1), 0.0, NULL, &presentation_timestamp, NULL, &rawCaptureBuffer); PsychGetAdjustedPrecisionTimerSeconds(&tnow); if (rc==-2 || (tnow > timeout)) { // No image available and there won't be any in the future, because capture has been stopped or there is a timeout: if (tnow > timeout) printf("PTB-WARNING: In Screen('GetCapturedImage') timed out waiting for a new frame. No video data in over 10 seconds!\n"); // No new texture available: Return a negative handle: PsychCopyOutDoubleArg(1, TRUE, -1); // ...and an invalid timestamp: PsychCopyOutDoubleArg(2, FALSE, -1); PsychCopyOutDoubleArg(3, FALSE, 0); PsychCopyOutDoubleArg(4, FALSE, 0); // Ready! return(PsychError_none); } else if (rc==-1 && (waitForImage == 0 || waitForImage == 3)) { // We should just poll once and no new texture available: Return a null-handle: PsychCopyOutDoubleArg(1, TRUE, 0); // ...and the current timestamp: PsychCopyOutDoubleArg(2, FALSE, presentation_timestamp); PsychCopyOutDoubleArg(3, FALSE, 0); PsychCopyOutDoubleArg(4, FALSE, 0); // Ready! return(PsychError_none); } else if (rc==-1 && waitForImage != 0) { // No new texture available yet. Just sleep a bit and then retry... PsychYieldIntervalSeconds(0.002); } } // rc == 0 --> New image available: Go ahead... if (waitForImage!=2 && waitForImage!=3) { // Ok, we need a texture for the image. Did script provide an old one for recycling? if (textureRecord) { // Old texture provided for reuse? Some basic sanity check: Everything else is // up to the lower level PsychGetTextureFromCapture() routine. if(!PsychIsOffscreenWindow(textureRecord)) { PsychErrorExitMsg(PsychError_user, "GetCapturedImage provided with something else than a texture as fourth call parameter."); } } else { // No old texture provided: Create a new texture record: PsychCreateWindowRecord(&textureRecord); // Set mode to 'Texture': textureRecord->windowType=kPsychTexture; // We need to assign the screen number of the onscreen-window. textureRecord->screenNumber=windowRecord->screenNumber; // It defaults to a 32 bit texture for captured images. On Linux, this will be overriden, // if optimized formats exist for our purpose: textureRecord->depth=32; textureRecord->nrchannels = 4; // Create default rectangle which describes the dimensions of the image. Will be overwritten // later on. PsychMakeRect(rect, 0, 0, 10, 10); PsychCopyRect(textureRecord->rect, rect); // Other setup stuff: textureRecord->textureMemorySizeBytes= 0; textureRecord->textureMemory=NULL; // Assign parent window and copy its inheritable properties: PsychAssignParentWindow(textureRecord, windowRecord); // Set textureNumber to zero, which means "Not cached, do not recycle" // Todo: Texture recycling like in PsychMovieSupport for higher efficiency! textureRecord->textureNumber = 0; } // Power-of-two texture requested? if (specialmode & 0x01) { // Yes. Spec it: textureRecord->texturetarget = GL_TEXTURE_2D; } } else { // Just want to return summed_intensity and timestamp, not real texture... textureRecord = NULL; } // Default to no calculation of summed image intensity: tsummed = NULL; if ((PsychGetNumOutputArgs() > 3) && !(specialmode & 0x2)) { // Return sum of pixel intensities for all channels of this image: Need to // assign the output pointer for this to happen: tsummed = &summed_intensity; } // Try to fetch an image from the capture object and return it as texture: targetmatrixptr = NULL; // Shall we return a Matlab matrix? if ((PsychGetNumOutputArgs() > 3) && (specialmode & 0x2)) { // We shall return a matrix with raw image data. Allocate a uint8 matrix // of sufficient size: PsychAllocOutUnsignedByteMatArg(4, TRUE, rawCaptureBuffer.depth, rawCaptureBuffer.w, rawCaptureBuffer.h, &targetmatrixptr); tsummed = NULL; } // Shall we return data into preallocated memory buffer? if (specialmode & 0x4) { // Copy in memory address (which itself is encoded in a double value): PsychCopyInDoubleArg(6, TRUE, &targetmemptr); targetmatrixptr = (psych_uint8*) PsychDoubleToPtr(targetmemptr); } if (targetmatrixptr == NULL) { // Standard fetch of a texture and its timestamp: rc = PsychGetTextureFromCapture(windowRecord, capturehandle, 0, 0.0, textureRecord, &presentation_timestamp, tsummed, NULL); } else { // Fetch of a memory raw image buffer + timestamp + possibly a texture: rawCaptureBuffer.data = (void*) targetmatrixptr; rc = PsychGetTextureFromCapture(windowRecord, capturehandle, 0, 0.0, textureRecord, &presentation_timestamp, tsummed, &rawCaptureBuffer); } if (tsummed) { // Return sum of pixel intensities for all channels of this image: PsychCopyOutDoubleArg(4, FALSE, summed_intensity); } // Real texture requested? if (textureRecord) { // Texture ready for consumption. // Assign GLSL filter-/lookup-shaders if needed: usefloatformat is always == 0 as // our current capture engine implementations only return 8 bpc fixed textures. // The 'userRequest' flag is set if specialmode flag is set to 8. PsychAssignHighPrecisionTextureShaders(textureRecord, windowRecord, 0, (specialmode & 8) ? 1 : 0); // specialmode setting 16? Disable auto-mipmap generation: if (specialmode & 16) textureRecord->specialflags |= kPsychDontAutoGenMipMaps; // Mark it valid and return handle to userspace: PsychSetWindowRecordValid(textureRecord); PsychCopyOutDoubleArg(1, TRUE, textureRecord->windowIndex); } else { PsychCopyOutDoubleArg(1, TRUE, 0); } // Return presentation timestamp for this image: PsychCopyOutDoubleArg(2, FALSE, presentation_timestamp); // Return count of pending frames in buffers or of dropped frames: PsychCopyOutDoubleArg(3, FALSE, (double) rc); // Ready! return(PsychError_none); }
PsychError SCREENOpenOffscreenWindow(void) { int screenNumber, depth, targetScreenNumber; PsychRectType rect; PsychColorType color; PsychWindowRecordType *exampleWindowRecord, *windowRecord, *targetWindow; psych_bool wasColorSupplied; char* texturePointer; size_t xSize, ySize, nbytes; psych_bool bigendian; GLubyte *rpb; int ix; GLenum fboInternalFormat; psych_bool needzbuffer; psych_bool overridedepth = FALSE; int usefloatformat = 0; int specialFlags = 0; int multiSample = 0; // Detect endianity (byte-order) of machine: ix=255; rpb=(GLubyte*) &ix; bigendian = ( *rpb == 255 ) ? FALSE : TRUE; ix = 0; rpb = NULL; //all sub functions should have these two lines PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; //cap the number of inputs PsychErrorExit(PsychCapNumInputArgs(6)); //The maximum number of inputs PsychErrorExit(PsychCapNumOutputArgs(2)); //The maximum number of outputs //1-User supplies a window ptr 2-User supplies a screen number 3-User supplies rect and pixel size if(PsychIsWindowIndexArg(1)){ PsychAllocInWindowRecordArg(1, TRUE, &exampleWindowRecord); // Assign normalized copy of example windows rect -- Top-Left corner is always (0,0) PsychNormalizeRect(exampleWindowRecord->clientrect, rect); // We copy depth only from exampleWindow if it is a offscreen window (=texture). Copying from // onscreen windows doesn't make sense, e.g. depth=16 for onscreen means RGBA8 window, but it // would map onto a LUMINANCE+ALPHA texture for the offscreen window! We always use 32 bit RGBA8 // in such a case. depth=(PsychIsOffscreenWindow(exampleWindowRecord)) ? exampleWindowRecord->depth : 32; // unless it is a FBO backed onscreen window in imaging mode: Then we can use the depth from it. if (exampleWindowRecord->imagingMode & kPsychNeedFastBackingStore || exampleWindowRecord->imagingMode & kPsychNeedFastOffscreenWindows) depth = exampleWindowRecord->depth; targetScreenNumber=exampleWindowRecord->screenNumber; targetWindow=exampleWindowRecord; } else if(PsychIsScreenNumberArg(1)){ PsychCopyInScreenNumberArg(1, TRUE, &screenNumber); PsychGetScreenRect(screenNumber, rect); depth=32; // Always use RGBA8 in this case! See above... targetScreenNumber=screenNumber; targetWindow=NULL; } else if(PsychIsUnaffiliatedScreenNumberArg(1)){ //that means -1 or maybe also NaN if we add that option. // Default to a depth of 32 bpp: depth = 32; targetWindow = NULL; // Get first open onscreen window as target window: PsychFindScreenWindowFromScreenNumber(kPsychUnaffiliatedWindow, &targetWindow); if (targetWindow == NULL) PsychErrorExitMsg(PsychError_user, "Could not find any open onscreen window to act as parent for this offscreen window. Open an onscreen window first!"); targetScreenNumber = targetWindow->screenNumber; PsychGetScreenRect(targetScreenNumber, rect); } else { targetScreenNumber = 0; // Make compiler happy. PsychErrorExit(PsychError_invalidNumdex); } if (targetWindow==NULL) { // Get target window of screen: PsychFindScreenWindowFromScreenNumber(targetScreenNumber, &targetWindow); if (targetWindow == NULL) PsychErrorExitMsg(PsychError_user, "Could not find any open onscreen window to act as parent for this offscreen window. Open an onscreen window first!"); targetScreenNumber = targetWindow->screenNumber; } //Depth and rect argument supplied as arguments override those inherited from reference screen or window. //Note that PsychCopyIn* prefix means that value will not be overwritten if the arguments are not present. PsychCopyInRectArg(3,FALSE, rect); if (IsPsychRectEmpty(rect)) PsychErrorExitMsg(PsychError_user, "Invalid rect value provided: Empty rects are not allowed."); // Copy in optional depth: This gets overriden in many ways if imaging pipeline is on: if (PsychCopyInIntegerArg(4,FALSE, &depth)) overridedepth = TRUE; // If any of the no longer supported values 0, 1, 2 or 4 is provided, we // silently switch to 32 bits per pixel, which is the safest and fastest setting: if (depth==0 || depth==1 || depth==2 || depth==4) depth=32; // Final sanity check: if (!(targetWindow->imagingMode & kPsychNeedFastOffscreenWindows) && !(targetWindow->imagingMode & kPsychNeedFastBackingStore) && (depth==64 || depth==128)) { PsychErrorExitMsg(PsychError_user, "Invalid depth value provided. Must be 8 bpp, 16 bpp, 24 bpp or 32 bpp, unless you enable the imaging pipeline, which provides you with more options!"); } if (depth!=8 && depth!=16 && depth!=24 && depth!=32 && depth!=64 && depth!=128) { PsychErrorExitMsg(PsychError_user, "Invalid depth value provided. Must be 8 bpp, 16 bpp, 24 bpp, 32 bpp, or if imagingmode is enabled also 64 bpp or 128 bpp!"); } // If the imaging pipeline is enabled for the associated onscreen window and fast backing store, aka FBO's // is requested, then we only accept depths of at least 32 bit, i.e. RGBA windows. We override any lower // precision spec. This is because some common hardware only supports rendering to RGBA textures, not to // RGB, LA or Luminance textures. if ((targetWindow->imagingMode & kPsychNeedFastBackingStore || targetWindow->imagingMode & kPsychNeedFastOffscreenWindows) && (depth < 32)) depth = 32; // Find the color for the window background. wasColorSupplied=PsychCopyInColorArg(kPsychUseDefaultArgPosition, FALSE, &color); // If none provided, use a proper white-value for this window: if(!wasColorSupplied) PsychLoadColorStruct(&color, kPsychIndexColor, PsychGetWhiteValueFromWindow(targetWindow)); // Get the optional specialmode flag: PsychCopyInIntegerArg(5, FALSE, &specialFlags); // OpenGL-ES only supports GL_TEXTURE_2D targets, so enforce these via flags setting 1: if (PsychIsGLES(targetWindow)) specialFlags |= 1; // This command converts whatever color we got into RGBA format: PsychCoerceColorMode(&color); // printf("R=%i G=%i B=%i A=%i I=%i", color.value.rgba.r, color.value.rgba.g,color.value.rgba.b,color.value.rgba.a,color.value.index); // First allocate the offscreen window record to store stuff into. If we exit with an error PsychErrorExit() should // call PsychPurgeInvalidWindows which will clean up the window record. PsychCreateWindowRecord(&windowRecord); // This also fills the window index field. // This offscreen window is implemented as a Psychtoolbox texture: windowRecord->windowType=kPsychTexture; // We need to assign the screen number of the onscreen-window, so PsychCreateTexture() // can query the size of the screen/onscreen-window... windowRecord->screenNumber = targetScreenNumber; // Assign the computed depth: windowRecord->depth=depth; // Default number of channels: windowRecord->nrchannels=depth / 8; // Assign the computed rect, but normalize it to start with top-left at (0,0): PsychNormalizeRect(rect, windowRecord->rect); // Client rect of an offscreen window is always == rect of it: PsychCopyRect(windowRecord->clientrect, windowRecord->rect); // Until here no OpenGL commands executed. Now we need a valid context: Set targetWindow // as drawing target. This will perform neccessary context-switch and all backbuffer // backup/restore/whatever operations to make sure we can do what we want without // possibly screwing any offscreen windows and bindings: if (PsychIsOnscreenWindow(targetWindow) || PsychIsOffscreenWindow(targetWindow)) { // This is a possible on-/offscreen drawingtarget: PsychSetDrawingTarget(targetWindow); } else { // This must be a proxy-window object: Can't transition to it! // But we can safe-reset the current drawingtarget... PsychSetDrawingTarget((PsychWindowRecordType*) 0x1); // ...and then switch to the OpenGL context of the 'targetWindow' proxy object: PsychSetGLContext(targetWindow); // Ok, framebuffer and bindings are safe and disabled, context is set. We // should be safe to continue with the proxy... } // From here on we have a defined context and state. We can detach the drawing target whenever // we want, as everything is backed up somewhere for later reinit. // Create offscreen window either new style as FBO, or old style as texture: if ((targetWindow->imagingMode & kPsychNeedFastBackingStore) || (targetWindow->imagingMode & kPsychNeedFastOffscreenWindows)) { // Imaging mode for this window enabled: Use new way of creating the offscreen window: // We safely unbind any FBO bindings and drawingtargets: PsychSetDrawingTarget((PsychWindowRecordType*) 0x1); // Overriden for imagingmode: There we always have 4 channels... windowRecord->nrchannels=4; // Start off with standard 8 bpc fixed point: fboInternalFormat = GL_RGBA8; windowRecord->depth=32; usefloatformat = 0; // Need 16 bpc fixed point precision? if (targetWindow->imagingMode & kPsychNeed16BPCFixed) { fboInternalFormat = (targetWindow->gfxcaps & kPsychGfxCapSNTex16) ? GL_RGBA16_SNORM : GL_RGBA16; windowRecord->depth=64; usefloatformat = 0; } // Need 16 bpc floating point precision? if (targetWindow->imagingMode & kPsychNeed16BPCFloat) { fboInternalFormat = GL_RGBA_FLOAT16_APPLE; windowRecord->depth=64; usefloatformat = 1; } // Need 32 bpc floating point precision? if (targetWindow->imagingMode & kPsychNeed32BPCFloat) { fboInternalFormat = GL_RGBA_FLOAT32_APPLE; windowRecord->depth=128; usefloatformat = 2; } // Override depth value provided? if (overridedepth) { // Manual depth specified: Override with that depth: switch(depth) { case 32: fboInternalFormat = GL_RGBA8; windowRecord->depth=32; usefloatformat = 0; break; case 64: fboInternalFormat = GL_RGBA_FLOAT16_APPLE; windowRecord->depth=64; usefloatformat = 1; // Need fallback for lack of float 16 support? if (!(targetWindow->gfxcaps & kPsychGfxCapFPTex16) && !PsychIsGLES(targetWindow)) { // Yes. Try 16 bit signed normalized texture instead: if (PsychPrefStateGet_Verbosity() > 4) printf("PTB-INFO:OpenOffscreenWindow: Code requested 16 bpc float precision, but this is unsupported. Trying to use 15 bit snorm precision instead.\n"); fboInternalFormat = GL_RGBA16_SNORM; windowRecord->depth=64; usefloatformat = 0; if (!(targetWindow->gfxcaps & kPsychGfxCapSNTex16)) { printf("PTB-ERROR:OpenOffscreenWindow: Code requested 16 bpc float precision, but this is unsupported by this graphics card.\n"); printf("PTB-ERROR:OpenOffscreenWindow: Tried to use 16 bit snorm format instead, but failed as this is unsupported as well.\n"); } } break; case 128: fboInternalFormat = GL_RGBA_FLOAT32_APPLE; windowRecord->depth=128; usefloatformat = 2; break; default: fboInternalFormat = GL_RGBA8; windowRecord->depth=32; usefloatformat = 0; } } // Floating point framebuffer on OpenGL-ES requested? if (PsychIsGLES(targetWindow) && (usefloatformat > 0)) { // Yes. We only support 32 bpc float framebuffers with alpha-blending. On less supportive hardware we fail: if (!(targetWindow->gfxcaps & kPsychGfxCapFPTex32) || !(targetWindow->gfxcaps & kPsychGfxCapFPFBO32)) { PsychErrorExitMsg(PsychError_user, "Sorry, the requested offscreen window color resolution of 32 bpc floating point is not supported by your graphics card. Game over."); } // Supported. Upgrade requested format to 32 bpc float, whatever it was before: fboInternalFormat = GL_RGBA_FLOAT32_APPLE; windowRecord->depth=128; usefloatformat = 2; } // Do we need additional depth buffer attachments? needzbuffer = (PsychPrefStateGet_3DGfx()>0) ? TRUE : FALSE; // Copy in optional multiSample argument: It defaults to zero, aka multisampling disabled. PsychCopyInIntegerArg(6, FALSE, &multiSample); if (multiSample < 0) PsychErrorExitMsg(PsychError_user, "Invalid negative multiSample level provided!"); // Multisampled anti-aliasing requested? if (multiSample > 0) { // Yep. Supported by GPU? if (!(targetWindow->gfxcaps & kPsychGfxCapFBOMultisample)) { // No. We fall back to non-multisampled mode: multiSample = 0; // Tell user if warnings enabled: if (PsychPrefStateGet_Verbosity() > 1) { printf("PTB-WARNING: You requested stimulus anti-aliasing via multisampling by setting the multiSample parameter of Screen('OpenOffscreenWindow', ...) to a non-zero value.\n"); printf("PTB-WARNING: You also requested use of the imaging pipeline. Unfortunately, your combination of operating system, graphics hardware and driver does not\n"); printf("PTB-WARNING: support simultaneous use of the imaging pipeline and multisampled anti-aliasing.\n"); printf("PTB-WARNING: Will therefore continue without anti-aliasing...\n\n"); printf("PTB-WARNING: A driver upgrade may resolve this issue. Users of MacOS-X need at least OS/X 10.5.2 Leopard for support on recent ATI hardware.\n\n"); } } } // Allocate framebuffer object for this Offscreen window: if (!PsychCreateFBO(&(windowRecord->fboTable[0]), fboInternalFormat, needzbuffer, (int) PsychGetWidthFromRect(rect), (int) PsychGetHeightFromRect(rect), multiSample, specialFlags)) { // Failed! PsychErrorExitMsg(PsychError_user, "Creation of Offscreen window in imagingmode failed for some reason :("); } // Assign this FBO as drawBuffer for mono channel of our Offscreen window: windowRecord->drawBufferFBO[0] = 0; windowRecord->fboCount = 1; // Assign it as texture as well: windowRecord->textureNumber = windowRecord->fboTable[0]->coltexid; windowRecord->textureMemorySizeBytes = 0; windowRecord->textureMemory = NULL; windowRecord->texturetarget = (specialFlags & 0x1) ? GL_TEXTURE_2D : GL_TEXTURE_RECTANGLE_EXT; windowRecord->surfaceSizeBytes = (size_t) (PsychGetWidthFromRect(rect) * PsychGetHeightFromRect(rect) * (windowRecord->depth / 8)); // Set bpc for FBO backed offscreen window: windowRecord->bpc = (int) (windowRecord->depth / 4); // Initial setup done, continues below after some shared code... } else { // Traditional texture creation code: // Special case for alpha-channel: DBL_MAX signals maximum alpha // value requested. In our own code we need to manually map this to // the maximum uint8 alpha value of 255: if (color.value.rgba.a == DBL_MAX) color.value.rgba.a = 255; // Allocate the texture memory: // We only allocate the amount really needed for given format, aka numMatrixPlanes - Bytes per pixel. xSize = (size_t) PsychGetWidthFromRect(rect); ySize = (size_t) PsychGetHeightFromRect(rect); windowRecord->textureMemorySizeBytes = ((size_t) (depth/8)) * xSize * ySize; windowRecord->textureMemory = malloc(windowRecord->textureMemorySizeBytes); texturePointer=(char*) windowRecord->textureMemory; // printf("depth=%i xsize=%i ysize=%i mem=%i ptr=%p", depth, xSize, ySize, windowRecord->textureMemorySizeBytes, texturePointer); // Fill with requested background color: nbytes=0; switch (depth) { case 8: // Pure LUMINANCE texture: memset((void*) texturePointer, (int) color.value.rgba.r, windowRecord->textureMemorySizeBytes); break; case 16: // LUMINANCE + ALPHA while (nbytes < windowRecord->textureMemorySizeBytes) { *(texturePointer++) = (psych_uint8) color.value.rgba.r; *(texturePointer++) = (psych_uint8) color.value.rgba.a; nbytes+=2; } break; case 24: // RGB: while (nbytes < windowRecord->textureMemorySizeBytes) { *(texturePointer++) = (psych_uint8) color.value.rgba.r; *(texturePointer++) = (psych_uint8) color.value.rgba.g; *(texturePointer++) = (psych_uint8) color.value.rgba.b; nbytes+=3; } break; case 32: // RGBA if (bigendian) { // Code for big-endian machines, e.g., PowerPC: while (nbytes < windowRecord->textureMemorySizeBytes) { *(texturePointer++) = (psych_uint8) color.value.rgba.a; *(texturePointer++) = (psych_uint8) color.value.rgba.r; *(texturePointer++) = (psych_uint8) color.value.rgba.g; *(texturePointer++) = (psych_uint8) color.value.rgba.b; nbytes+=4; } } else { // Code for little-endian machines, e.g., IntelPC, IntelMAC, aka Pentium. while (nbytes < windowRecord->textureMemorySizeBytes) { *(texturePointer++) = (psych_uint8) color.value.rgba.b; *(texturePointer++) = (psych_uint8) color.value.rgba.g; *(texturePointer++) = (psych_uint8) color.value.rgba.r; *(texturePointer++) = (psych_uint8) color.value.rgba.a; nbytes+=4; } } break; } } // Shared setup code for FBO vs. non-FBO Offscreen windows: // Assign parent window and copy its inheritable properties: PsychAssignParentWindow(windowRecord, targetWindow); // Texture orientation is type 2 aka upright, non-transposed aka Offscreen window: windowRecord->textureOrientation = 2; if ((windowRecord->imagingMode & kPsychNeedFastBackingStore) || (windowRecord->imagingMode & kPsychNeedFastOffscreenWindows)) { // Last step for FBO backed Offscreen window: Clear it to its background color: PsychSetDrawingTarget(windowRecord); // Set default draw shader: PsychSetShader(windowRecord, -1); // Set background fill color: PsychSetGLColor(&color, windowRecord); // Setup alpha-blending: PsychUpdateAlphaBlendingFactorLazily(windowRecord); // Fullscreen fill of a non-onscreen window: PsychGLRect(windowRecord->rect); // Multisampling requested? If so, we need to enable it: if (multiSample > 0) { glEnable(GL_MULTISAMPLE); while (glGetError() != GL_NO_ERROR); } // Ready. Unbind it. PsychSetDrawingTarget(NULL); } else { // Old-style setup for non-FBO Offscreen windows: // Special texture format? if (specialFlags & 0x1) windowRecord->texturetarget = GL_TEXTURE_2D; // Let's create and bind a new texture object and fill it with our new texture data. PsychCreateTexture(windowRecord); } // Assign GLSL filter-/lookup-shaders if needed: PsychAssignHighPrecisionTextureShaders(windowRecord, targetWindow, usefloatformat, (specialFlags & 2) ? 1 : 0); // specialFlags setting 8? Disable auto-mipmap generation: if (specialFlags & 0x8) windowRecord->specialflags |= kPsychDontAutoGenMipMaps; // A specialFlags setting of 32? Protect texture against deletion via Screen('Close') without providing a explicit handle: if (specialFlags & 32) windowRecord->specialflags |= kPsychDontDeleteOnClose; // Window ready. Mark it valid and return handle to userspace: PsychSetWindowRecordValid(windowRecord); //Return the window index and the rect argument. PsychCopyOutDoubleArg(1, FALSE, windowRecord->windowIndex); PsychCopyOutRectArg(2, FALSE, rect); // Ready. return(PsychError_none); }
PsychError SCREENCopyWindow(void) { PsychRectType sourceRect, targetRect, targetRectInverted; PsychWindowRecordType *sourceWin, *targetWin; GLdouble sourceVertex[2], targetVertex[2]; double t1; double sourceRectWidth, sourceRectHeight; GLuint srcFBO, dstFBO; GLenum glerr; //all sub functions should have these two lines PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; //cap the number of inputs PsychErrorExit(PsychCapNumInputArgs(5)); //The maximum number of inputs PsychErrorExit(PsychCapNumOutputArgs(0)); //The maximum number of outputs //get parameters for the source window: PsychAllocInWindowRecordArg(1, TRUE, &sourceWin); PsychCopyRect(sourceRect, sourceWin->rect); // Special case for stereo: Only half the real window width: PsychMakeRect(&sourceRect, sourceWin->rect[kPsychLeft], sourceWin->rect[kPsychTop], sourceWin->rect[kPsychLeft] + PsychGetWidthFromRect(sourceWin->rect)/((sourceWin->specialflags & kPsychHalfWidthWindow) ? 2 : 1), sourceWin->rect[kPsychTop] + PsychGetHeightFromRect(sourceWin->rect)/((sourceWin->specialflags & kPsychHalfHeightWindow) ? 2 : 1)); PsychCopyInRectArg(3, FALSE, sourceRect); if (IsPsychRectEmpty(sourceRect)) return(PsychError_none); //get paramters for the target window: PsychAllocInWindowRecordArg(2, TRUE, &targetWin); // By default, the targetRect is equal to the sourceRect, but centered in // the target window. PsychCopyRect(targetRect, targetWin->rect); // Special case for stereo: Only half the real window width: PsychMakeRect(&targetRect, targetWin->rect[kPsychLeft], targetWin->rect[kPsychTop], targetWin->rect[kPsychLeft] + PsychGetWidthFromRect(targetWin->rect)/((targetWin->specialflags & kPsychHalfWidthWindow) ? 2 : 1), targetWin->rect[kPsychTop] + PsychGetHeightFromRect(targetWin->rect)/((targetWin->specialflags & kPsychHalfHeightWindow) ? 2 : 1)); PsychCopyInRectArg(4, FALSE, targetRect); if (IsPsychRectEmpty(targetRect)) return(PsychError_none); if (0) { printf("SourceRect: %f %f %f %f ---> TargetRect: %f %f %f %f\n", sourceRect[0], sourceRect[1], sourceRect[2], sourceRect[3], targetRect[0], targetRect[1],targetRect[2],targetRect[3]); } // Validate rectangles: if (!ValidatePsychRect(sourceRect) || sourceRect[kPsychLeft]<sourceWin->rect[kPsychLeft] || sourceRect[kPsychTop]<sourceWin->rect[kPsychTop] || sourceRect[kPsychRight]>sourceWin->rect[kPsychRight] || sourceRect[kPsychBottom]>sourceWin->rect[kPsychBottom]) { PsychErrorExitMsg(PsychError_user, "Invalid source rectangle specified - (Partially) outside of source window."); } if (!ValidatePsychRect(targetRect) || targetRect[kPsychLeft]<targetWin->rect[kPsychLeft] || targetRect[kPsychTop]<targetWin->rect[kPsychTop] || targetRect[kPsychRight]>targetWin->rect[kPsychRight] || targetRect[kPsychBottom]>targetWin->rect[kPsychBottom]) { PsychErrorExitMsg(PsychError_user, "Invalid target rectangle specified - (Partially) outside of target window."); } if (!(PsychPrefStateGet_ConserveVRAM() & kPsychAvoidCPUGPUSync)) PsychTestForGLErrors(); // Does this GL implementation support the EXT_framebuffer_blit extension for fast blitting between // framebuffers? And is the imaging pipeline active? And is the kPsychAvoidFramebufferBlitIfPossible not set? if ((sourceWin->gfxcaps & kPsychGfxCapFBOBlit) && (targetWin->gfxcaps & kPsychGfxCapFBOBlit) && (sourceWin->imagingMode > 0) && (targetWin->imagingMode > 0) && !(PsychPrefStateGet_ConserveVRAM() & kPsychAvoidFramebufferBlitIfPossible)) { // Yes :-) -- This simplifies the CopyWindow implementation to a simple framebuffer blit op, // regardless what the source- or destination is: // Set each windows framebuffer as a drawingtarget once: This is just to make sure all of them // have proper FBO's attached: PsychSetDrawingTarget(sourceWin); PsychSetDrawingTarget(targetWin); // Soft-Reset drawing target - Detach anything bound or setup: PsychSetDrawingTarget(0x1); // Find source framebuffer: if (sourceWin->fboCount == 0) { // No FBO's attached to sourceWin: Must be a system framebuffer, e.g., if imagingMode == kPsychNeedFastOffscreenWindows and // this is the onscreen window system framebuffer. Assign system framebuffer handle: srcFBO = 0; } else { // FBO's attached: Want drawBufferFBO 0 or 1 - 1 for right eye view in stereomode, 0 for // everything else: left eye view, monoscopic buffer, offscreen windows / textures: if ((sourceWin->stereomode > 0) && (sourceWin->stereodrawbuffer == 1)) { // We are in stereo mode and want to access the right-eye channel. Bind FBO-1 srcFBO = sourceWin->fboTable[sourceWin->drawBufferFBO[1]]->fboid; } else { srcFBO = sourceWin->fboTable[sourceWin->drawBufferFBO[0]]->fboid; } } // Find target framebuffer: if (targetWin->fboCount == 0) { // No FBO's attached to targetWin: Must be a system framebuffer, e.g., if imagingMode == kPsychNeedFastOffscreenWindows and // this is the onscreen window system framebuffer. Assign system framebuffer handle: dstFBO = 0; } else { // FBO's attached: Want drawBufferFBO 0 or 1 - 1 for right eye view in stereomode, 0 for // everything else: left eye view, monoscopic buffer, offscreen windows / textures: if ((targetWin->stereomode > 0) && (targetWin->stereodrawbuffer == 1)) { // We are in stereo mode and want to access the right-eye channel. Bind FBO-1 dstFBO = targetWin->fboTable[targetWin->drawBufferFBO[1]]->fboid; } else { dstFBO = targetWin->fboTable[targetWin->drawBufferFBO[0]]->fboid; } } // Bind read- / write- framebuffers: glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, srcFBO); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, dstFBO); // Perform blit-operation: If blitting from a multisampled FBO into a non-multisampled one, this will also perform the // multisample resolve operation: glBlitFramebufferEXT(sourceRect[kPsychLeft], PsychGetHeightFromRect(sourceWin->rect) - sourceRect[kPsychBottom], sourceRect[kPsychRight], PsychGetHeightFromRect(sourceWin->rect) - sourceRect[kPsychTop], targetRect[kPsychLeft], PsychGetHeightFromRect(targetWin->rect) - targetRect[kPsychBottom], targetRect[kPsychRight], PsychGetHeightFromRect(targetWin->rect) - targetRect[kPsychTop], GL_COLOR_BUFFER_BIT, GL_NEAREST); if (PsychPrefStateGet_Verbosity() > 5) { printf("FBB-SRC: X0 = %f Y0 = %f X1 = %f Y1 = %f \n", sourceRect[kPsychLeft], PsychGetHeightFromRect(sourceWin->rect) - sourceRect[kPsychBottom], sourceRect[kPsychRight], PsychGetHeightFromRect(sourceWin->rect) - sourceRect[kPsychTop]); printf("FBB-DST: X0 = %f Y0 = %f X1 = %f Y1 = %f \n", targetRect[kPsychLeft], PsychGetHeightFromRect(targetWin->rect) - targetRect[kPsychBottom], targetRect[kPsychRight], PsychGetHeightFromRect(targetWin->rect) - targetRect[kPsychTop]); } if (!(PsychPrefStateGet_ConserveVRAM() & kPsychAvoidCPUGPUSync)) { if ((glerr = glGetError())!=GL_NO_ERROR) { // Reset framebuffer binding to something safe - The system framebuffer: glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); if((glerr == GL_INVALID_OPERATION) && (PsychGetWidthFromRect(sourceRect) != PsychGetWidthFromRect(targetRect) || PsychGetHeightFromRect(sourceRect) != PsychGetHeightFromRect(targetRect))) { // Non-matching sizes. Make sure we do not operate on multisampled stuff PsychErrorExitMsg(PsychError_user, "CopyWindow failed: Most likely cause: You tried to copy a multi-sampled window into a non-multisampled window, but there is a size mismatch of sourceRect and targetRect. Matching size is required for such copies."); } else { if (glerr == GL_INVALID_OPERATION) { PsychErrorExitMsg(PsychError_user, "CopyWindow failed: Most likely cause: You tried to copy from a multi-sampled window into another multisampled window, but there is a mismatch between the multiSample levels of both. Identical multiSample values are required for such copies."); } else { printf("CopyWindow failed for unresolved reason: OpenGL says after call to glBlitFramebufferEXT(): %s\n", gluErrorString(glerr)); PsychErrorExitMsg(PsychError_user, "CopyWindow failed for unresolved reason."); } } } } // Reset framebuffer binding to something safe - The system framebuffer: glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0); // Just to make sure we catch invalid values: if (!(PsychPrefStateGet_ConserveVRAM() & kPsychAvoidCPUGPUSync)) PsychTestForGLErrors(); // Done. return(PsychError_none); } // We have four possible combinations for copy ops: // Onscreen -> Onscreen // Onscreen -> Texture // Texture -> Texture // Texture -> Onscreen // Texture -> something copy? (Offscreen to Offscreen or Offscreen to Onscreen) // This should work for both, copies from a texture/offscreen window to a different texture/offscreen window/onscreen window, // and for copies of a subregion of a texture/offscreen window into a non-overlapping subregion of the texture/offscreen window // itself: if (sourceWin->windowType == kPsychTexture) { // Bind targetWin (texture or onscreen windows framebuffer) as // drawing target and just blit texture into it. Binding is done implicitely if ((sourceWin == targetWin) && (targetWin->imagingMode > 0)) { // Copy of a subregion of an offscreen window into itself while imaging pipe active, ie. FBO storage: This is actually the same // as on onscreen -> onscreen copy, just with the targetWin FBO bound. // Set target windows framebuffer as drawing target: PsychSetDrawingTarget(targetWin); // Disable alpha-blending: glDisable(GL_BLEND); // Disable any shading during copy-op: PsychSetShader(targetWin, 0); // Start position for pixel write is: glRasterPos2f(targetRect[kPsychLeft], targetRect[kPsychBottom]); // Zoom factor if rectangle sizes don't match: glPixelZoom(PsychGetWidthFromRect(targetRect) / PsychGetWidthFromRect(sourceRect), PsychGetHeightFromRect(targetRect) / PsychGetHeightFromRect(sourceRect)); // Perform pixel copy operation: glCopyPixels(sourceRect[kPsychLeft], PsychGetHeightFromRect(sourceWin->rect) - sourceRect[kPsychBottom], (int) PsychGetWidthFromRect(sourceRect), (int) PsychGetHeightFromRect(sourceRect), GL_COLOR); // That's it. glPixelZoom(1,1); // Flush drawing commands and wait for render-completion in single-buffer mode: PsychFlushGL(targetWin); } else { // Sourcewin != Targetwin and/or imaging pipe (FBO storage) not used. We blit the // backing texture into itself, aka into its representation inside the system // backbuffer. The blit routine will setup proper bindings: // Disable alpha-blending: glDisable(GL_BLEND); // We use filterMode == 1 aka Bilinear filtering, so we get nice texture copies // if size of sourceRect and targetRect don't match and some scaling is needed. // We maybe could map the copyMode argument into some filterMode settings, but // i don't know the spec of copyMode, so ... PsychBlitTextureToDisplay(sourceWin, targetWin, sourceRect, targetRect, 0, 1, 1); // That's it. // Flush drawing commands and wait for render-completion in single-buffer mode: PsychFlushGL(targetWin); } } // Onscreen to texture copy? if (PsychIsOnscreenWindow(sourceWin) && PsychIsOffscreenWindow(targetWin)) { // With the current implemenation we can't zoom if sizes of sourceRect and targetRect don't // match: Only one-to-one copy possible... if(PsychGetWidthFromRect(sourceRect) != PsychGetWidthFromRect(targetRect) || PsychGetHeightFromRect(sourceRect) != PsychGetHeightFromRect(targetRect)) { // Non-matching sizes. We can't perform requested scaled copy :( PsychErrorExitMsg(PsychError_user, "Size mismatch of sourceRect and targetRect. Matching size is required for Onscreen to Offscreen copies. Sorry."); } // Update selected textures content: // Looks weird but we need the framebuffer of sourceWin: PsychSetDrawingTarget(sourceWin); // Disable alpha-blending: glDisable(GL_BLEND); // Disable any shading during copy-op: PsychSetShader(sourceWin, 0); // Texture objects are shared across contexts, so doesn't matter if targetWin's texture actually // belongs to the bound context of sourceWin: glBindTexture(PsychGetTextureTarget(targetWin), targetWin->textureNumber); // Copy into texture: glCopyTexSubImage2D(PsychGetTextureTarget(targetWin), 0, targetRect[kPsychLeft], PsychGetHeightFromRect(targetWin->rect) - targetRect[kPsychBottom], sourceRect[kPsychLeft], PsychGetHeightFromRect(sourceWin->rect) - sourceRect[kPsychBottom], (int) PsychGetWidthFromRect(sourceRect), (int) PsychGetHeightFromRect(sourceRect)); // Unbind texture object: glBindTexture(PsychGetTextureTarget(targetWin), 0); // That's it. glPixelZoom(1,1); } // Onscreen to Onscreen copy? if (PsychIsOnscreenWindow(sourceWin) && PsychIsOnscreenWindow(targetWin)) { // Currently only works for copies of subregion -> subregion inside same onscreen window, // not across different onscreen windows! TODO: Only possible with EXT_framebuffer_blit if (sourceWin != targetWin) PsychErrorExitMsg(PsychError_user, "Sorry, the current implementation only supports copies within the same onscreen window, not accross onscreen windows."); // Set target windows framebuffer as drawing target: PsychSetDrawingTarget(targetWin); // Disable alpha-blending: glDisable(GL_BLEND); // Disable any shading during copy-op: PsychSetShader(targetWin, 0); // Start position for pixel write is: glRasterPos2f(targetRect[kPsychLeft], targetRect[kPsychBottom]); // Zoom factor if rectangle sizes don't match: glPixelZoom(PsychGetWidthFromRect(targetRect) / PsychGetWidthFromRect(sourceRect), PsychGetHeightFromRect(targetRect) / PsychGetHeightFromRect(sourceRect)); // Perform pixel copy operation: glCopyPixels(sourceRect[kPsychLeft], PsychGetHeightFromRect(sourceWin->rect) - sourceRect[kPsychBottom], (int) PsychGetWidthFromRect(sourceRect), (int) PsychGetHeightFromRect(sourceRect), GL_COLOR); // That's it. glPixelZoom(1,1); // Flush drawing commands and wait for render-completion in single-buffer mode: PsychFlushGL(targetWin); } // Just to make sure we catch invalid values: if (!(PsychPrefStateGet_ConserveVRAM() & kPsychAvoidCPUGPUSync)) PsychTestForGLErrors(); // Done. return(PsychError_none); }