/* * PsychQTGetTextureFromMovie() -- Create an OpenGL texture map from a specific videoframe from given movie object. * * win = Window pointer of onscreen window for which a OpenGL texture should be created. * moviehandle = Handle to the movie object. * checkForImage = true == Just check if new image available, false == really retrieve the image, blocking if necessary. * timeindex = When not in playback mode, this allows specification of a requested frame by presentation time. * If set to -1, or if in realtime playback mode, this parameter is ignored and the next video frame is returned. * out_texture = Pointer to the Psychtoolbox texture-record where the new texture should be stored. * presentation_timestamp = A ptr to a double variable, where the presentation timestamp of the returned frame should be stored. * * Returns true (1) on success, false (0) if no new image available, -1 if no new image available and there won't be any in future. */ int PsychQTGetTextureFromMovie(PsychWindowRecordType *win, int moviehandle, int checkForImage, double timeindex, PsychWindowRecordType *out_texture, double *presentation_timestamp) { static TimeValue myNextTimeCached = -2; static TimeValue nextFramesTimeCached = -2; TimeValue myCurrTime; TimeValue myNextTime; TimeValue nextFramesTime=0; short myFlags; OSType myTypes[1]; OSErr error = noErr; Movie theMovie; CVOpenGLTextureRef newImage = NULL; QTVisualContextRef theMoviecontext; unsigned int failcount=0; float lowerLeft[2]; float lowerRight[2]; float upperRight[2]; float upperLeft[2]; GLuint texid; Rect rect; float rate; double targetdelta, realdelta, frames; PsychRectType outRect; if (!PsychIsOnscreenWindow(win)) { PsychErrorExitMsg(PsychError_user, "Need onscreen window ptr!!!"); } // Activate OpenGL context of target window: PsychSetGLContext(win); // Explicitely disable Apple's Client storage extensions. For now they are not really useful to us. glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE); if (moviehandle < 0 || moviehandle >= PSYCH_MAX_MOVIES) { PsychErrorExitMsg(PsychError_user, "Invalid moviehandle provided."); } if ((timeindex!=-1) && (timeindex < 0 || timeindex >= 10000.0)) { PsychErrorExitMsg(PsychError_user, "Invalid timeindex provided."); } if (NULL == out_texture && !checkForImage) { PsychErrorExitMsg(PsychError_internal, "NULL-Ptr instead of out_texture ptr passed!!!"); } // Fetch references to objects we need: theMovie = movieRecordBANK[moviehandle].theMovie; theMoviecontext = movieRecordBANK[moviehandle].QTMovieContext; if (theMovie == NULL) { PsychErrorExitMsg(PsychError_user, "Invalid moviehandle provided. No movie associated with this handle."); } // Check if end of movie is reached. Rewind, if so... if (IsMovieDone(theMovie) && movieRecordBANK[moviehandle].loopflag > 0) { if (GetMovieRate(theMovie)>0) { GoToBeginningOfMovie(theMovie); } else { GoToEndOfMovie(theMovie); } } // Is movie actively playing (automatic async playback, possibly with synced sound)? // If so, then we ignore the 'timeindex' parameter, because the automatic playback // process determines which frames should be delivered to PTB when. This function will // simply wait or poll for arrival/presence of a new frame that hasn't been fetched // in previous calls. if (0 == GetMovieRate(theMovie)) { // Movie playback inactive. We are in "manual" mode: No automatic async playback, // no synced audio output. The user just wants to manually fetch movie frames into // textures for manual playback in a standard Matlab-loop. // First pass - checking for new image? if (checkForImage) { // Image for specific point in time requested? if (timeindex >= 0) { // Yes. We try to retrieve the next possible image for requested timeindex. myCurrTime = (TimeValue) ((timeindex * (double) GetMovieTimeScale(theMovie)) + 0.5f); } else { // No. We just retrieve the next frame, given the current movie time. myCurrTime = GetMovieTime(theMovie, NULL); } // Retrieve timeindex of the closest image sample after myCurrTime: myFlags = nextTimeStep + nextTimeEdgeOK; // We want the next frame in the movie's media. myTypes[0] = VisualMediaCharacteristic; // We want video samples. GetMovieNextInterestingTime(theMovie, myFlags, 1, myTypes, myCurrTime, FloatToFixed(1), &myNextTime, &nextFramesTime); error = GetMoviesError(); if (error != noErr) { PsychErrorExitMsg(PsychError_internal, "Failed to fetch texture from movie for given timeindex!"); } // Found useful event? if (myNextTime == -1) { if (PsychPrefStateGet_Verbosity() > 3) printf("PTB-WARNING: Bogus timevalue in movie track for movie %i. Trying to keep going.\n", moviehandle); // No. Just push timestamp to current time plus a little bit in the hope // this will get us unstuck: myNextTime = myCurrTime + (TimeValue) 1; nextFramesTime = (TimeValue) 0; } if (myNextTime != myNextTimeCached) { // Set movies current time to myNextTime, so the next frame will be fetched from there: SetMovieTimeValue(theMovie, myNextTime); // nextFramesTime is the timeindex to which we need to advance for retrieval of next frame: (see code below) nextFramesTime=myNextTime + nextFramesTime; if (PsychPrefStateGet_Verbosity() > 5) printf("PTB-DEBUG: Current timevalue in movie track for movie %i is %lf secs.\n", moviehandle, (double) myNextTime / (double) GetMovieTimeScale(theMovie)); if (PsychPrefStateGet_Verbosity() > 5) printf("PTB-DEBUG: Next timevalue in movie track for movie %i is %lf secs.\n", moviehandle, (double) nextFramesTime / (double) GetMovieTimeScale(theMovie)); // Cache values for 2nd pass: myNextTimeCached = myNextTime; nextFramesTimeCached = nextFramesTime; } else { // Somehow got stuck? Do nothing... if (PsychPrefStateGet_Verbosity() > 5) printf("PTB-DEBUG: Seem to be a bit stuck at timevalue [for movie %i] of %lf secs. Nudging a bit forward...\n", moviehandle, (double) myNextTime / (double) GetMovieTimeScale(theMovie)); // Nudge the timeindex a bit forware in the hope that this helps: SetMovieTimeValue(theMovie, GetMovieTime(theMovie, NULL) + 1); } } else { // This is the 2nd pass: Image fetching. Use cached values from first pass: // Caching in a static works because we're always called immediately for 2nd // pass after successfull return from 1st pass, and we're not multi-threaded, // i.e., don't need to be reentrant or thread-safe here: myNextTime = myNextTimeCached; nextFramesTime = nextFramesTimeCached; myNextTimeCached = -2; } } else { // myNextTime unavailable if in autoplayback-mode: myNextTime=-1; } // Presentation timestamp requested? if (presentation_timestamp) { // Already available? if (myNextTime==-1) { // Retrieve the exact presentation timestamp of the retrieved frame (in movietime): myFlags = nextTimeStep + nextTimeEdgeOK; // We want the next frame in the movie's media. myTypes[0] = VisualMediaCharacteristic; // We want video samples. // We search backward for the closest available image for the current time. Either we get the current time // if we happen to fetch a frame exactly when it becomes ready, or we get a bit earlier timestamp, which is // the optimal presentation timestamp for this frame: GetMovieNextInterestingTime(theMovie, myFlags, 1, myTypes, GetMovieTime(theMovie, NULL), FloatToFixed(-1), &myNextTime, NULL); } // Convert pts (in Quicktime ticks) to pts in seconds since start of movie and return it: *presentation_timestamp = (double) myNextTime / (double) GetMovieTimeScale(theMovie); } // Allow quicktime visual context task to do its internal bookkeeping and cleanup work: if (theMoviecontext) QTVisualContextTask(theMoviecontext); // Perform decompress-operation: if (checkForImage) MoviesTask(theMovie, 0); // Should we just check for new image? If so, just return availability status: if (checkForImage) { if (PSYCH_USE_QT_GWORLDS) { // We use GWorlds. In this case we either suceed immediately due to the // synchronous nature of GWorld rendering, or we fail completely at end // of non-looping movie: if (IsMovieDone(theMovie) && movieRecordBANK[moviehandle].loopflag == 0) { // No new frame available and there won't be any in the future, because this is a non-looping // movie that has reached its end. return(-1); } // Is this the special case of a movie without video, but only sound? In that case, // we always return a 'false' because there ain't no image to return. if (movieRecordBANK[moviehandle].QTMovieGWorld == NULL) return(false); // Success! return(true); } // Code which uses QTVisualContextTasks... if (QTVisualContextIsNewImageAvailable(theMoviecontext, NULL)) { // New frame ready! return(true); } else if (IsMovieDone(theMovie) && movieRecordBANK[moviehandle].loopflag == 0) { // No new frame available and there won't be any in the future, because this is a non-looping // movie that has reached its end. return(-1); } else { // No new frame available yet: return(false); } } if (!PSYCH_USE_QT_GWORLDS) { // Blocking wait-code for non-GWorld mode: // Try up to 1000 iterations for arrival of requested image data in wait-mode: failcount=0; while ((failcount < 1000) && !QTVisualContextIsNewImageAvailable(theMoviecontext, NULL)) { PsychWaitIntervalSeconds(0.005); MoviesTask(theMovie, 0); failcount++; } // No new frame available and there won't be any in the future, because this is a non-looping // movie that has reached its end. if ((failcount>=1000) && IsMovieDone(theMovie) && (movieRecordBANK[moviehandle].loopflag == 0)) { return(-1); } // Fetch new OpenGL texture with the new movie image frame: error = QTVisualContextCopyImageForTime(theMoviecontext, kCFAllocatorDefault, NULL, &newImage); if ((error!=noErr) || newImage == NULL) { PsychErrorExitMsg(PsychError_internal, "OpenGL<->Quicktime texture fetch failed!!!"); } // Disable client storage, if it was enabled: glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE); // Build a standard PTB texture record: CVOpenGLTextureGetCleanTexCoords (newImage, lowerLeft, lowerRight, upperRight, upperLeft); texid = CVOpenGLTextureGetName(newImage); // Assign texture rectangle: PsychMakeRect(outRect, upperLeft[0], upperLeft[1], lowerRight[0], lowerRight[1]); // Set texture orientation as if it were an inverted Offscreen window: Upside-down. out_texture->textureOrientation = (CVOpenGLTextureIsFlipped(newImage)) ? 3 : 4; // Assign OpenGL texture id: out_texture->textureNumber = texid; // Store special texture object as part of the PTB texture record: out_texture->targetSpecific.QuickTimeGLTexture = newImage; } else { // Synchronous texture fetch code for GWorld rendering mode: // At this point, the GWorld should contain the source image for creating a // standard OpenGL texture: // Disable client storage, if it was enabled: glPixelStorei(GL_UNPACK_CLIENT_STORAGE_APPLE, GL_FALSE); // Build a standard PTB texture record: // Assign texture rectangle: GetMovieBox(theMovie, &rect); // Hack: Need to extend rect by 4 pixels, because GWorlds are 4 pixels-aligned via // image row padding: rect.right = rect.right + 4; PsychMakeRect(out_texture->rect, rect.left, rect.top, rect.right, rect.bottom); // Set NULL - special texture object as part of the PTB texture record: out_texture->targetSpecific.QuickTimeGLTexture = NULL; // Set texture orientation as if it were an inverted Offscreen window: Upside-down. out_texture->textureOrientation = 3; // Setup a pointer to our GWorld as texture data pointer: out_texture->textureMemorySizeBytes = 0; // Quicktime textures are aligned on 4 Byte boundaries: out_texture->textureByteAligned = 4; // Lock GWorld: if(!LockPixels(GetGWorldPixMap(movieRecordBANK[moviehandle].QTMovieGWorld))) { // Locking surface failed! We abort. PsychErrorExitMsg(PsychError_internal, "PsychQTGetTextureFromMovie(): Locking GWorld pixmap surface failed!!!"); } // This will retrieve an OpenGL compatible pointer to the GWorlds pixel data and assign it to our texmemptr: out_texture->textureMemory = (GLuint*) GetPixBaseAddr(GetGWorldPixMap(movieRecordBANK[moviehandle].QTMovieGWorld)); // Let PsychCreateTexture() do the rest of the job of creating, setting up and // filling an OpenGL texture with GWorlds content: PsychCreateTexture(out_texture); // Undo hack from above after texture creation: Now we need the real width of the // texture for proper texture coordinate assignments in drawing code et al. rect.right = rect.right - 4; PsychMakeRect(outRect, rect.left, rect.top, rect.right, rect.bottom); // Unlock GWorld surface. We do a glFinish() before, for safety reasons... //glFinish(); UnlockPixels(GetGWorldPixMap(movieRecordBANK[moviehandle].QTMovieGWorld)); // Ready to use the texture... We're done. } // Normalize texture rectangle and assign it: PsychNormalizeRect(outRect, out_texture->rect); rate = FixedToFloat(GetMovieRate(theMovie)); // Detection of dropped frames: This is a heuristic. We'll see how well it works out... if (rate && presentation_timestamp) { // Try to check for dropped frames in playback mode: // Expected delta between successive presentation timestamps: targetdelta = 1.0f / (movieRecordBANK[moviehandle].fps * rate); // Compute real delta, given rate and playback direction: if (rate>0) { realdelta = *presentation_timestamp - movieRecordBANK[moviehandle].last_pts; if (realdelta<0) realdelta = 0; } else { realdelta = -1.0 * (*presentation_timestamp - movieRecordBANK[moviehandle].last_pts); if (realdelta<0) realdelta = 0; } frames = realdelta / targetdelta; // Dropped frames? if (frames > 1 && movieRecordBANK[moviehandle].last_pts>=0) { movieRecordBANK[moviehandle].nr_droppedframes += (int) (frames - 1 + 0.5); } movieRecordBANK[moviehandle].last_pts = *presentation_timestamp; } // Manually advance movie time, if in fetch mode: if (0 == GetMovieRate(theMovie)) { // We are in manual fetch mode: Need to manually advance movie time to next // media sample: if (nextFramesTime == myNextTime) { // Invalid value? Try to hack something that gets us unstuck: myNextTime = GetMovieTime(theMovie, NULL); nextFramesTime = myNextTime + (TimeValue) 1; } SetMovieTimeValue(theMovie, nextFramesTime); } // Check if end of movie is reached. Rewind, if so... if (IsMovieDone(theMovie) && movieRecordBANK[moviehandle].loopflag > 0) { if (GetMovieRate(theMovie)>0) { GoToBeginningOfMovie(theMovie); } else { GoToEndOfMovie(theMovie); } } return(TRUE); }
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 SCREENTextBounds(void) { //for debugging TextEncodingBase textEncodingBase; TextEncodingVariant textEncodingVariant; TextEncodingFormat textEncodingFormat; /////// PsychWindowRecordType *winRec; char *textCString; Str255 textPString; UniChar *textUniString; OSStatus callError; PsychRectType resultPsychRect, resultPsychNormRect; ATSUTextLayout textLayout; //layout is a pointer to an opaque struct. int stringLengthChars; int uniCharBufferLengthElements, uniCharBufferLengthChars, uniCharBufferLengthBytes, yPositionIsBaseline; double textHeightToBaseline; ByteCount uniCharStringLengthBytes; TextToUnicodeInfo textToUnicodeInfo; TextEncoding textEncoding; ATSUStyle atsuStyle; Boolean foundFont; //for ATSU style attributes PsychFontStructPtrType psychFontRecord; //all subfunctions should have these two lines. PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; //check for correct the number of arguments before getting involved PsychErrorExit(PsychCapNumInputArgs(5)); PsychErrorExit(PsychRequireNumInputArgs(2)); PsychErrorExit(PsychCapNumOutputArgs(2)); //get the window pointer and the text string and check that the window record has a font set PsychAllocInWindowRecordArg(1, kPsychArgRequired, &winRec); foundFont=PsychGetFontRecordFromFontNumber(winRec->textAttributes.textFontNumber, &psychFontRecord); if(!foundFont) PsychErrorExitMsg(PsychError_user, "Attempt to determine the bounds of text with no font or invalid font number"); //it would be better to both prevent the user from setting invalid font numbers and init to the OS 9 default font. //read in the string and get its length and convert it to a unicode string. PsychAllocInCharArg(2, kPsychArgRequired, &textCString); stringLengthChars=strlen(textCString); if(stringLengthChars < 1) PsychErrorExitMsg(PsychError_user, "You asked me to compute the bounding box of an empty text string?!? Sorry, that's a no no..."); if(stringLengthChars > 255) PsychErrorExitMsg(PsychError_unimplemented, "Cut corners and TextBounds will not accept a string longer than 255 characters"); CopyCStringToPascal(textCString, textPString); uniCharBufferLengthChars= stringLengthChars * CHAR_TO_UNICODE_LENGTH_FACTOR; uniCharBufferLengthElements= uniCharBufferLengthChars + 1; uniCharBufferLengthBytes= sizeof(UniChar) * uniCharBufferLengthElements; textUniString=(UniChar*)malloc(uniCharBufferLengthBytes); PsychCopyInDoubleArg(3, kPsychArgOptional, &(winRec->textAttributes.textPositionX)); PsychCopyInDoubleArg(4, kPsychArgOptional, &(winRec->textAttributes.textPositionY)); //Using a TextEncoding type describe the encoding of the text to be converteed. textEncoding=CreateTextEncoding(kTextEncodingMacRoman, kMacRomanDefaultVariant, kTextEncodingDefaultFormat); //Take apart the encoding we just made to check it: textEncodingBase=GetTextEncodingBase(textEncoding); textEncodingVariant=GetTextEncodingVariant(textEncoding); textEncodingFormat=GetTextEncodingFormat(textEncoding); //Create a structure holding conversion information from the text encoding type we just created. callError=CreateTextToUnicodeInfoByEncoding(textEncoding,&textToUnicodeInfo); //Convert the text to a unicode string callError=ConvertFromPStringToUnicode(textToUnicodeInfo, textPString, (ByteCount)uniCharBufferLengthBytes, &uniCharStringLengthBytes, textUniString); //create the text layout object callError=ATSUCreateTextLayout(&textLayout); //associate our unicode text string with the text layout object callError=ATSUSetTextPointerLocation(textLayout, textUniString, kATSUFromTextBeginning, kATSUToTextEnd, (UniCharCount)stringLengthChars); //create an ATSU style object callError=ATSUCreateStyle(&atsuStyle); callError=ATSUClearStyle(atsuStyle); //Not that we have a style object we have to set style charactersitics. These include but are more general than Font Manager styles. //ATSU Style objects have three sets of characteristics: attributes, variations, and features. //attributes are things we need to set to match OS 9 behavior, such as the font ID, size, boldness, and italicization. //features are esoteric settings which we don't need for reproducing OS 9 behavior. Whatever clearstyle sets should be fine. //font variations are axes of variation through the space of font characteristics. The font definition includes available axes of variation. Something else we can ignore for now. PsychSetATSUStyleAttributesFromPsychWindowRecord(atsuStyle, winRec); //don't bother to set the variations of the style. //don't bother to set the features of the style. //associate the style with our layout object. This call assigns a style to every character of the string to be displayed. callError=ATSUSetRunStyle(textLayout, atsuStyle, (UniCharArrayOffset)0, (UniCharCount)stringLengthChars); // Define the meaning of the y position of the specified drawing cursor. // We get the global setting from the Screen preference, but allow to override // it on a per-invocation basis via the optional 7th argument to 'DrawText': yPositionIsBaseline = PsychPrefStateGet_TextYPositionIsBaseline(); PsychCopyInIntegerArg(5, kPsychArgOptional, &yPositionIsBaseline); if (yPositionIsBaseline) { // Y position of drawing cursor defines distance between top of text and // baseline of text, i.e. the textheight excluding descenders of letters: // Need to compute offset via ATSU: ATSUTextMeasurement mleft, mright, mtop, mbottom; callError=ATSUGetUnjustifiedBounds(textLayout, kATSUFromTextBeginning, kATSUToTextEnd, &mleft, &mright, &mbottom, &mtop); if (callError) { PsychErrorExitMsg(PsychError_internal, "Failed to compute unjustified text height to baseline in call to ATSUGetUnjustifiedBounds().\n"); } // Only take height including ascenders into account, not the descenders. // MK: Honestly, i have no clue why this is the correct calculation (or if it is // the correct calculation), but visually it seems to provide the correct results // and i'm not a typographic expert and don't intend to become one... textHeightToBaseline = fabs(Fix2X(mbottom)); } else { // Y position of drawing cursor defines top of text, therefore no offset (==0) needed: textHeightToBaseline = 0; } //Get the bounds for our text so that and create a texture of sufficient size to containt it. ATSTrapezoid trapezoid; ItemCount oActualNumberOfBounds = 0; callError=ATSUGetGlyphBounds(textLayout, 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 0, NULL, &oActualNumberOfBounds); if (callError || oActualNumberOfBounds!=1) { PsychErrorExitMsg(PsychError_internal, "Failed to compute bounding box in call 1 to ATSUGetGlyphBounds() (nrbounds!=1)\n"); } callError=ATSUGetGlyphBounds(textLayout, 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 1, &trapezoid, &oActualNumberOfBounds); if (callError || oActualNumberOfBounds!=1) { PsychErrorExitMsg(PsychError_internal, "Failed to retrieve bounding box in call 2 to ATSUGetGlyphBounds() (nrbounds!=1)\n"); } resultPsychRect[kPsychLeft]=(Fix2X(trapezoid.upperLeft.x) < Fix2X(trapezoid.lowerLeft.x)) ? Fix2X(trapezoid.upperLeft.x) : Fix2X(trapezoid.lowerLeft.x); resultPsychRect[kPsychRight]=(Fix2X(trapezoid.upperRight.x) > Fix2X(trapezoid.lowerRight.x)) ? Fix2X(trapezoid.upperRight.x) : Fix2X(trapezoid.lowerRight.x); resultPsychRect[kPsychTop]=(Fix2X(trapezoid.upperLeft.y) < Fix2X(trapezoid.upperRight.y)) ? Fix2X(trapezoid.upperLeft.y) : Fix2X(trapezoid.upperRight.y); resultPsychRect[kPsychBottom]=(Fix2X(trapezoid.lowerLeft.y) > Fix2X(trapezoid.lowerRight.y)) ? Fix2X(trapezoid.lowerLeft.y) : Fix2X(trapezoid.lowerRight.y); PsychNormalizeRect(resultPsychRect, resultPsychNormRect); resultPsychRect[kPsychLeft]=resultPsychNormRect[kPsychLeft] + winRec->textAttributes.textPositionX; resultPsychRect[kPsychRight]=resultPsychNormRect[kPsychRight] + winRec->textAttributes.textPositionX; resultPsychRect[kPsychTop]=resultPsychNormRect[kPsychTop] + winRec->textAttributes.textPositionY - textHeightToBaseline; resultPsychRect[kPsychBottom]=resultPsychNormRect[kPsychBottom] + winRec->textAttributes.textPositionY - textHeightToBaseline; PsychCopyOutRectArg(1, FALSE, resultPsychNormRect); PsychCopyOutRectArg(2, FALSE, resultPsychRect); //release resources free((void*)textUniString); callError=ATSUDisposeStyle(atsuStyle); return(PsychError_none); }
PsychError SCREENTextBounds(void) { //for debugging TextEncodingBase textEncodingBase; TextEncodingVariant textEncodingVariant; TextEncodingFormat textEncodingFormat; /////// PsychWindowRecordType *winRec; char *textCString; Str255 textPString; UniChar *textUniString; OSStatus callError; PsychRectType resultPsychRect, resultPsychNormRect; ATSUTextLayout textLayout; //layout is a pointer to an opaque struct. int stringLengthChars; int uniCharBufferLengthElements, uniCharBufferLengthChars, uniCharBufferLengthBytes; ByteCount uniCharStringLengthBytes; TextToUnicodeInfo textToUnicodeInfo; TextEncoding textEncoding; ATSUStyle atsuStyle; Boolean foundFont; //for ATSU style attributes PsychFontStructPtrType psychFontRecord; //all subfunctions should have these two lines. PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; //check for correct the number of arguments before getting involved PsychErrorExit(PsychCapNumInputArgs(2)); PsychErrorExit(PsychRequireNumInputArgs(2)); PsychErrorExit(PsychCapNumOutputArgs(2)); //get the window pointer and the text string and check that the window record has a font set PsychAllocInWindowRecordArg(1, kPsychArgRequired, &winRec); foundFont=PsychGetFontRecordFromFontNumber(winRec->textAttributes.textFontNumber, &psychFontRecord); if(!foundFont) PsychErrorExitMsg(PsychError_user, "Attempt to determine the bounds of text with no font or invalid font number"); //it would be better to both prevent the user from setting invalid font numbers and init to the OS 9 default font. //read in the string and get its length and convert it to a unicode string. PsychAllocInCharArg(2, kPsychArgRequired, &textCString); stringLengthChars=strlen(textCString); if(stringLengthChars > 255) PsychErrorExitMsg(PsychError_unimplemented, "Cut corners and TextBounds will not accept a string longer than 255 characters"); CopyCStringToPascal(textCString, textPString); uniCharBufferLengthChars= stringLengthChars * CHAR_TO_UNICODE_LENGTH_FACTOR; uniCharBufferLengthElements= uniCharBufferLengthChars + 1; uniCharBufferLengthBytes= sizeof(UniChar) * uniCharBufferLengthElements; textUniString=(UniChar*)malloc(uniCharBufferLengthBytes); //Using a TextEncoding type describe the encoding of the text to be converteed. textEncoding=CreateTextEncoding(kTextEncodingMacRoman, kMacRomanDefaultVariant, kTextEncodingDefaultFormat); //Take apart the encoding we just made to check it: textEncodingBase=GetTextEncodingBase(textEncoding); textEncodingVariant=GetTextEncodingVariant(textEncoding); textEncodingFormat=GetTextEncodingFormat(textEncoding); //Create a structure holding conversion information from the text encoding type we just created. callError=CreateTextToUnicodeInfoByEncoding(textEncoding,&textToUnicodeInfo); //Convert the text to a unicode string callError=ConvertFromPStringToUnicode(textToUnicodeInfo, textPString, (ByteCount)uniCharBufferLengthBytes, &uniCharStringLengthBytes, textUniString); //create the text layout object callError=ATSUCreateTextLayout(&textLayout); //associate our unicode text string with the text layout object callError=ATSUSetTextPointerLocation(textLayout, textUniString, kATSUFromTextBeginning, kATSUToTextEnd, (UniCharCount)stringLengthChars); //create an ATSU style object callError=ATSUCreateStyle(&atsuStyle); callError=ATSUClearStyle(atsuStyle); //Not that we have a style object we have to set style charactersitics. These include but are more general than Font Manager styles. //ATSU Style objects have three sets of characteristics: attributes, variations, and features. //attributes are things we need to set to match OS 9 behavior, such as the font ID, size, boldness, and italicization. //features are esoteric settings which we don't need for reproducing OS 9 behavior. Whatever clearstyle sets should be fine. //font variations are axes of variation through the space of font characteristics. The font definition includes available axes of variation. Something else we can ignore for now. PsychSetATSUStyleAttributesFromPsychWindowRecord(atsuStyle, winRec); //don't bother to set the variations of the style. //don't bother to set the features of the style. //associate the style with our layout object. This call assigns a style to every character of the string to be displayed. callError=ATSUSetRunStyle(textLayout, atsuStyle, (UniCharArrayOffset)0, (UniCharCount)stringLengthChars); //Get the bounds for our text so that and create a texture of sufficient size to containt it. ATSTrapezoid trapezoid; ItemCount oActualNumberOfBounds = 0; callError=ATSUGetGlyphBounds(textLayout, 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 0, NULL, &oActualNumberOfBounds); if (callError || oActualNumberOfBounds!=1) { PsychErrorExitMsg(PsychError_internal, "Failed to compute bounding box in call 1 to ATSUGetGlyphBounds() (nrbounds!=1)\n"); } callError=ATSUGetGlyphBounds(textLayout, 0, 0, kATSUFromTextBeginning, kATSUToTextEnd, kATSUseDeviceOrigins, 1, &trapezoid, &oActualNumberOfBounds); if (callError || oActualNumberOfBounds!=1) { PsychErrorExitMsg(PsychError_internal, "Failed to retrieve bounding box in call 2 to ATSUGetGlyphBounds() (nrbounds!=1)\n"); } resultPsychRect[kPsychLeft]=(Fix2X(trapezoid.upperLeft.x) < Fix2X(trapezoid.lowerLeft.x)) ? Fix2X(trapezoid.upperLeft.x) : Fix2X(trapezoid.lowerLeft.x); resultPsychRect[kPsychRight]=(Fix2X(trapezoid.upperRight.x) > Fix2X(trapezoid.lowerRight.x)) ? Fix2X(trapezoid.upperRight.x) : Fix2X(trapezoid.lowerRight.x); resultPsychRect[kPsychTop]=(Fix2X(trapezoid.upperLeft.y) < Fix2X(trapezoid.upperRight.y)) ? Fix2X(trapezoid.upperLeft.y) : Fix2X(trapezoid.upperRight.y); resultPsychRect[kPsychBottom]=(Fix2X(trapezoid.lowerLeft.y) > Fix2X(trapezoid.lowerRight.y)) ? Fix2X(trapezoid.lowerLeft.y) : Fix2X(trapezoid.lowerRight.y); PsychNormalizeRect(resultPsychRect, resultPsychNormRect); PsychCopyOutRectArg(1, FALSE, resultPsychNormRect); PsychCopyOutRectArg(2, FALSE, resultPsychRect); //release resources free((void*)textUniString); callError=ATSUDisposeStyle(atsuStyle); return(PsychError_none); }
PsychError SCREENTextBounds(void) { PsychWindowRecordType *winRec; char *textString; int stringl, i; PsychRectType resultPsychRect, resultPsychNormRect; float accumWidth, maxHeight; // All subfunctions should have these two lines. PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; PsychErrorExit(PsychCapNumInputArgs(5)); PsychErrorExit(PsychRequireNumInputArgs(2)); PsychErrorExit(PsychCapNumOutputArgs(2)); //Get the window structure for the onscreen window. PsychAllocInWindowRecordArg(1, TRUE, &winRec); //Get the text string (it is required) PsychAllocInCharArg(2, kPsychArgRequired, &textString); PsychCopyInDoubleArg(3, kPsychArgOptional, &(winRec->textAttributes.textPositionX)); PsychCopyInDoubleArg(4, kPsychArgOptional, &(winRec->textAttributes.textPositionY)); // Enable GL context of this window - we might need it: PsychSetGLContext(winRec); // Does the font (== it's display list) need to be build or rebuild, because // font name, size or settings have changed? // This routine will check it and perform all necessary ops if so... PsychOSRebuildFont(winRec); // Top-Left bounds of text are current (x,y) position of text drawing cursor: resultPsychRect[kPsychLeft] = winRec->textAttributes.textPositionX; resultPsychRect[kPsychTop] = winRec->textAttributes.textPositionY; // Compute text x and y increments: stringl=strlen(textString); accumWidth=0; maxHeight=0; for (i=0; i<stringl; i++) { accumWidth+=winRec->textAttributes.glyphWidth[textString[i]]; maxHeight=(fabs(winRec->textAttributes.glyphHeight[textString[i]]) > maxHeight) ? fabs(winRec->textAttributes.glyphHeight[textString[i]]) : maxHeight; } accumWidth*=(PSYCH_SYSTEM == PSYCH_WINDOWS) ? winRec->textAttributes.textSize : 1.0; maxHeight*=(PSYCH_SYSTEM == PSYCH_WINDOWS) ? winRec->textAttributes.textSize : 1.0; resultPsychRect[kPsychRight] = winRec->textAttributes.textPositionX + accumWidth; // MK: This should work according to spec, but f%$!*g Windows only returns zero values for // for glyphHeight, so maxHeight is always zero :( // resultPsychRect[kPsychBottom] = winRec->textAttributes.textPositionY + maxHeight; // As fallback, we use this: It gives correct Bottom-Bound for character strings with characters that // don't contain descenders. The extra height of characters with descenders is not taken into account. resultPsychRect[kPsychBottom] = winRec->textAttributes.textPositionY + winRec->textAttributes.textSize; // Compute normalized version which just encodes text bounding box, not text position box: PsychNormalizeRect(resultPsychRect, resultPsychNormRect); // Return optional values: PsychCopyOutRectArg(1, FALSE, resultPsychNormRect); PsychCopyOutRectArg(2, FALSE, resultPsychRect); // Done. return(PsychError_none); }