/* Accept a pointer to a Psychtoolbox rect specifier and fill it with the rect information supplied at the specified argument position in module call. The behavior depends on the value of required: 1. Required = TRUE A. If argument is not present exit with an error PsychError_invalidColorArg. B. If argument is present and valid then load it into *rect and return true. 2. Required = FALSE A. If argument is not present then don't touch *color and return false. B. If argument is present and valid then load it into *rect and return true. */ psych_bool PsychCopyInRectArg(int position, psych_bool required, PsychRectType rect) { int m,n,p,argSize; psych_bool isArg; double *rectArgMat=NULL; if(position == kPsychUseDefaultArgPosition) position = kPsychDefaultRectArgPosition; isArg = PsychIsArgPresent(PsychArgIn, position); if(!isArg){ if(required) PsychErrorExitMsg(PsychError_user, "Required rect argument missing."); //1A else return(FALSE); //2A } PsychAllocInDoubleMatArg(position, TRUE, &m, &n, &p, &rectArgMat); if(p!=1) PsychErrorExitMsg(PsychError_invalidRectArg, ">2 D array passed as rect"); argSize = m*n; if(argSize!=4) PsychErrorExitMsg(PsychError_invalidRectArg, "rect argument not 4 elements in size"); memcpy(rect,rectArgMat,sizeof(PsychRectType)); if(!ValidatePsychRect(rect)){ PsychErrorExitMsg(PsychError_invalidRectArg, "impossible rect values given"); return(FALSE); } return(TRUE); //1B, 2B }
PsychError SCREENConstrainCursor(void) { static char useString[] = "Screen('ConstrainCursor', windowIndex, addConstraint [, rect]);"; // 1 2 3 static char synopsisString[] = "Confine mouse cursor position to a specified area inside onscreen window 'windowIndex'.\n\n" "If you set 'addConstraint' to 1, then a region constraint is added: 'rect' specifies the " "rectangle (in window local coordinates) to which the mouse cursor should be confined. If " "you omit 'rect', then the cursor is confined to the region of the window, ie. can't leave " "the window. On MS-Windows you can only define one single rectangular region at all, regardless " "of the number of onscreen windows. On Linux/X11 you can define up to a total of (currently) 1024 " "confinement regions, e.g., for multiple separate windows, or multiple regions per window.\n" "Additionally on Linux/X11 you can define empty 'rect's which define a horizontal or vertical line. " "This adds a horizontal or vertical border line which can not be crossed by the mouse cursor, so you " "could, e.g., build a complex maze, in which the cursor has to navigate. Please note that this " "ability will not be present on a future version of Psychtoolbox for Linux with the Wayland display " "server. While the Wayland implementation will provide the ability to define multiple regions, its " "semantic will very likely be different, so if you use this special Linux/X11 only feature, your code " "will not only be non-portable to MS-Windows, but also to future Linux versions which use Wayland instead " "of the X11 graphics system!\n\n" "If you set 'addConstraint' to 0 and specify a 'rect', then the specified 'rect' confinement region " "for the given onscreen window is removed on Linux/X11. If you omit 'rect' on Linux, then all confinement " "regions for the given onscreen window are removed. On MS-Windows the single globally available confinement " "region is removed if it was set for the given onscreen window, regardless if you specify 'rect' or not, " "as there is no ambiguity or choice with only one global rect anyway.\n\n" "Closing an onscreen window with active cursor constraints will automatically remove all associated " "cursor confinement regions. This is true for proper close via Screen('Close', windowIndex), Screen('Closeall') or sca, " "or during a controlled error abort with proper error handling. On Linux, quitting/killing or crashing Octave/Matlab " "will also release pointer confinement. On MS-Windows, pressing ALT+TAB will release the confinement.\n\n" "The 'ConstrainCursor' function is not currently supported or supportable on Apple macOS due to macOS " "operating system limitations. See 'help SetMouse' sections referring to the 'detachFromMouse' parameter for " "a hint on how you may be able to work around this macOS limitation for some applications.\n\n"; static char seeAlsoString[] = "HideCursorHelper"; PsychWindowRecordType *windowRecord; int addConstraint; PsychRectType rect; // All subfunctions should have these two lines. PsychPushHelp(useString, synopsisString, seeAlsoString); if (PsychIsGiveHelp()) { PsychGiveHelp(); return(PsychError_none); }; PsychErrorExit(PsychCapNumInputArgs(3)); // The maximum number of inputs PsychErrorExit(PsychCapNumOutputArgs(0)); //The maximum number of outputs // Get windowRecord: PsychAllocInWindowRecordArg(kPsychUseDefaultArgPosition, TRUE, &windowRecord); if (!PsychIsOnscreenWindow(windowRecord)) PsychErrorExitMsg(PsychError_user, "Specified window is not an onscreen window, as required."); // Get flag: PsychCopyInIntegerArg(2, kPsychArgRequired, &addConstraint); if (addConstraint) { // If optional rect is omitted, use full window rect: if (!PsychCopyInRectArg(3, kPsychArgOptional, rect)) PsychCopyRect(rect, windowRecord->rect); if (!ValidatePsychRect(rect) || rect[kPsychLeft] < 0 || rect[kPsychTop] < 0 || rect[kPsychRight] > PsychGetWidthFromRect(windowRecord->rect) || rect[kPsychBottom] > PsychGetHeightFromRect(windowRecord->rect)) { PsychErrorExitMsg(PsychError_user, "Invalid 'rect' provided. Invalid, or reaches outside the onscreen windows borders."); } // Add a new cursor constraint for this window, defined by rect: if (!PsychOSConstrainPointer(windowRecord, TRUE, rect)) PsychErrorExitMsg(PsychError_user, "Failed to add cursor constraint for onscreen window."); } else { if (PsychCopyInRectArg(3, kPsychArgOptional, rect)) { // Remove cursor constraint for this window, as defined by rect: if (!PsychOSConstrainPointer(windowRecord, FALSE, rect)) PsychErrorExitMsg(PsychError_user, "Failed to remove cursor constraint for onscreen window."); } else { // Remove all cursor constraints for this window: if (!PsychOSConstrainPointer(windowRecord, FALSE, NULL)) PsychErrorExitMsg(PsychError_user, "Failed to remove all cursor constraints for onscreen window."); } } 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); }