PsychError SCREENPutImage(void) { PsychRectType windowRect, positionRect; int ix, iy; size_t matrixRedIndex, matrixGreenIndex, matrixBlueIndex, matrixAlphaIndex, matrixGrayIndex; int inputM, inputN, inputP, positionRectWidth, positionRectHeight; size_t pixelIndex = 0; PsychWindowRecordType *windowRecord; unsigned char *inputMatrixByte; double *inputMatrixDouble; GLfloat *pixelData; GLfloat matrixGrayValue, matrixRedValue, matrixGreenValue, matrixBlueValue, matrixAlphaValue; PsychArgFormatType inputMatrixType; GLfloat xZoom = 1, yZoom = -1; // 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(4)); //The maximum number of inputs PsychErrorExit(PsychCapNumOutputArgs(0)); //The maximum number of outputs // Get the image matrix. inputMatrixType = PsychGetArgType(2); switch (inputMatrixType) { case PsychArgType_none: case PsychArgType_default: PsychErrorExitMsg(PsychError_user, "imageArray argument required"); break; case PsychArgType_uint8: PsychAllocInUnsignedByteMatArg(2, TRUE, &inputM, &inputN, &inputP, &inputMatrixByte); break; case PsychArgType_double: PsychAllocInDoubleMatArg(2, TRUE, &inputM, &inputN, &inputP, &inputMatrixDouble); break; default: PsychErrorExitMsg(PsychError_user, "imageArray must be uint8 or double type"); break; } if (inputP != 1 && inputP != 3 && inputP != 4) { PsychErrorExitMsg(PsychError_user, "Third dimension of image matrix must be 1, 3, or 4"); } // Get the window and get the rect and stuff. PsychAllocInWindowRecordArg(kPsychUseDefaultArgPosition, TRUE, &windowRecord); // A no-go on OES: if (PsychIsGLES(windowRecord)) { PsychErrorExitMsg(PsychError_unimplemented, "Sorry, Screen('PutImage') is not supported on OpenGL-ES embedded graphics hardware. Use 'MakeTexture' and 'DrawTexture' instead."); } PsychGetRectFromWindowRecord(windowRect, windowRecord); if (PsychCopyInRectArg(3, FALSE, positionRect)) { if (IsPsychRectEmpty(positionRect)) { return PsychError_none; } positionRectWidth = (int) PsychGetWidthFromRect(positionRect); positionRectHeight = (int) PsychGetHeightFromRect(positionRect); if (positionRectWidth != inputN || positionRectHeight != inputM) { // Calculate the zoom factor. xZoom = (GLfloat) positionRectWidth / (GLfloat) inputN; yZoom = -((GLfloat) positionRectHeight / (GLfloat) inputM); } } else { positionRect[kPsychLeft] = 0; positionRect[kPsychTop] = 0; positionRect[kPsychRight] = inputN; positionRect[kPsychBottom] = inputM; PsychCenterRect(positionRect, windowRect, positionRect); } // Allocate memory to hold the pixel data that we'll later pass to OpenGL. pixelData = (GLfloat*) PsychMallocTemp(sizeof(GLfloat) * (size_t) inputN * (size_t) inputM * 4); // Loop through all rows and columns of the pixel data passed from Matlab, extract it, // and stick it into 'pixelData'. for (iy = 0; iy < inputM; iy++) { for (ix = 0; ix < inputN; ix++) { if (inputP == 1) { // Grayscale // Extract the grayscale value. matrixGrayIndex = PSYCHINDEXELEMENTFROM3DARRAY((size_t) inputM, (size_t) inputN, 1, (size_t) iy, (size_t) ix, 0); if (inputMatrixType == PsychArgType_uint8) { // If the color range is > 255, then force it to 255 for 8-bit values. matrixGrayValue = (GLfloat)inputMatrixByte[matrixGrayIndex]; if (windowRecord->colorRange > 255) { matrixGrayValue /= (GLfloat)255; } else { matrixGrayValue /= (GLfloat)windowRecord->colorRange; } } else { matrixGrayValue = (GLfloat)(inputMatrixDouble[matrixGrayIndex] / windowRecord->colorRange); } // RGB will all be the same for grayscale. We'll go ahead and fix alpha to the max value. pixelData[pixelIndex++] = matrixGrayValue; // R pixelData[pixelIndex++] = matrixGrayValue; // G pixelData[pixelIndex++] = matrixGrayValue; // B pixelData[pixelIndex++] = (GLfloat) 1.0; // A } else if (inputP == 3) { // RGB matrixRedIndex = PSYCHINDEXELEMENTFROM3DARRAY((size_t) inputM, (size_t) inputN, 3, (size_t) iy, (size_t) ix, 0); matrixGreenIndex = PSYCHINDEXELEMENTFROM3DARRAY((size_t) inputM, (size_t) inputN, 3, (size_t) iy, (size_t) ix, 1); matrixBlueIndex = PSYCHINDEXELEMENTFROM3DARRAY((size_t) inputM, (size_t) inputN, 3, (size_t) iy, (size_t) ix, 2); if (inputMatrixType == PsychArgType_uint8) { // If the color range is > 255, then force it to 255 for 8-bit values. matrixRedValue = (GLfloat)inputMatrixByte[matrixRedIndex]; matrixGreenValue = (GLfloat)inputMatrixByte[matrixGreenIndex]; matrixBlueValue = (GLfloat)inputMatrixByte[matrixBlueIndex]; if (windowRecord->colorRange > 255) { matrixRedValue /= (GLfloat)255; matrixGreenValue /= (GLfloat)255; matrixBlueValue /= (GLfloat)255; } else { matrixRedValue /= (GLfloat)windowRecord->colorRange; matrixGreenValue /= (GLfloat)windowRecord->colorRange; matrixBlueValue /= (GLfloat)windowRecord->colorRange; } } else { matrixRedValue = (GLfloat)(inputMatrixDouble[matrixRedIndex] / windowRecord->colorRange); matrixGreenValue = (GLfloat)(inputMatrixDouble[matrixGreenIndex] / windowRecord->colorRange); matrixBlueValue = (GLfloat)(inputMatrixDouble[matrixBlueIndex] / windowRecord->colorRange); } pixelData[pixelIndex++] = matrixRedValue; pixelData[pixelIndex++] = matrixGreenValue; pixelData[pixelIndex++] = matrixBlueValue; pixelData[pixelIndex++] = (GLfloat)1.0; } else if (inputP == 4) { // RGBA matrixRedIndex = PSYCHINDEXELEMENTFROM3DARRAY((size_t) inputM, (size_t) inputN, 4, (size_t) iy, (size_t) ix, 0); matrixGreenIndex = PSYCHINDEXELEMENTFROM3DARRAY((size_t) inputM, (size_t) inputN, 4, (size_t) iy, (size_t) ix, 1); matrixBlueIndex = PSYCHINDEXELEMENTFROM3DARRAY((size_t) inputM, (size_t) inputN, 4, (size_t) iy, (size_t) ix, 2); matrixAlphaIndex = PSYCHINDEXELEMENTFROM3DARRAY((size_t) inputM, (size_t) inputN, 4, (size_t) iy, (size_t) ix, 3); if (inputMatrixType == PsychArgType_uint8) { // If the color range is > 255, then force it to 255 for 8-bit values. matrixRedValue = (GLfloat)inputMatrixByte[matrixRedIndex]; matrixGreenValue = (GLfloat)inputMatrixByte[matrixGreenIndex]; matrixBlueValue = (GLfloat)inputMatrixByte[matrixBlueIndex]; matrixAlphaValue = (GLfloat)inputMatrixByte[matrixAlphaIndex]; if (windowRecord->colorRange > 255) { matrixRedValue /= (GLfloat)255; matrixGreenValue /= (GLfloat)255; matrixBlueValue /= (GLfloat)255; matrixAlphaValue /= (GLfloat)255; } else { matrixRedValue /= (GLfloat)windowRecord->colorRange; matrixGreenValue /= (GLfloat)windowRecord->colorRange; matrixBlueValue /= (GLfloat)windowRecord->colorRange; matrixAlphaValue /= (GLfloat)windowRecord->colorRange; } } else { matrixRedValue = (GLfloat)(inputMatrixDouble[matrixRedIndex] / windowRecord->colorRange); matrixGreenValue = (GLfloat)(inputMatrixDouble[matrixGreenIndex] / (GLfloat)windowRecord->colorRange); matrixBlueValue = (GLfloat)(inputMatrixDouble[matrixBlueIndex] / (GLfloat)windowRecord->colorRange); matrixAlphaValue = (GLfloat)(inputMatrixDouble[matrixAlphaIndex] / (GLfloat)windowRecord->colorRange); } pixelData[pixelIndex++] = matrixRedValue; pixelData[pixelIndex++] = matrixGreenValue; pixelData[pixelIndex++] = matrixBlueValue; pixelData[pixelIndex++] = matrixAlphaValue; } } // for (iy = 0; iy < inputM; iy++) } // for (ix = 0; ix < inputN; ix++) // Enable this windowRecords framebuffer as current drawingtarget: PsychSetDrawingTarget(windowRecord); // Disable draw shader: PsychSetShader(windowRecord, 0); PsychUpdateAlphaBlendingFactorLazily(windowRecord); // Set the raster position so that we can draw starting at this location. glRasterPos2f((GLfloat)(positionRect[kPsychLeft]), (GLfloat)(positionRect[kPsychTop])); // Tell glDrawPixels to unpack the pixel array along GLfloat boundaries. glPixelStorei(GL_UNPACK_ALIGNMENT, (GLint)sizeof(GLfloat)); // Dump the pixels onto the screen. glPixelZoom(xZoom, yZoom); glDrawPixels(inputN, inputM, GL_RGBA, GL_FLOAT, pixelData); glPixelZoom(1,1); PsychFlushGL(windowRecord); // This does nothing if we are multi buffered, otherwise it glFlushes PsychTestForGLErrors(); return PsychError_none; }
PsychError SCREENDrawDots(void) { PsychWindowRecordType *windowRecord, *parentWindowRecord; int m,n,p,mc,nc,idot_type; int i, nrpoints, nrsize; psych_bool isArgThere, usecolorvector, isdoublecolors, isuint8colors; double *xy, *size, *center, *dot_type, *colors; float *sizef; unsigned char *bytecolors; GLfloat pointsizerange[2]; psych_bool lenient = FALSE; psych_bool usePointSizeArray = FALSE; static psych_bool nocando = FALSE; int oldverbosity; // All sub functions should have these two lines PsychPushHelp(useString, synopsisString,seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; // Check for superfluous arguments PsychErrorExit(PsychCapNumInputArgs(7)); //The maximum number of inputs PsychErrorExit(PsychCapNumOutputArgs(4)); //The maximum number of outputs // Get the window record from the window record argument and get info from the window record PsychAllocInWindowRecordArg(1, kPsychArgRequired, &windowRecord); // Get dot_type argument, if any, as it is already needed for a pure point size range query below: isArgThere = PsychIsArgPresent(PsychArgIn, 6); if(!isArgThere){ idot_type = 0; } else { PsychAllocInDoubleMatArg(6, TRUE, &m, &n, &p, &dot_type); if(p != 1 || n != 1 || m != 1 || (dot_type[0] < 0 || dot_type[0] > 4)) PsychErrorExitMsg(PsychError_user, "dot_type must be 0, 1, 2, 3 or 4"); idot_type = (int) dot_type[0]; } // Query for supported point size range? if (PsychGetNumOutputArgs() > 0) { PsychSetDrawingTarget(windowRecord); // Always query and return aliased range: glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, (GLfloat*) &pointsizerange); PsychCopyOutDoubleArg(3, FALSE, (double) pointsizerange[0]); PsychCopyOutDoubleArg(4, FALSE, (double) pointsizerange[1]); // If driver supports smooth points and usercode doesn't specify a dot type (idot_type 0) // or does not request shader + point-sprite based drawing then return smooth point // size range as "smooth point size range" - query and assign it. Otherwise, ie., code // explicitely wants to use a shader (idot_type >= 3) or has to use one, we will use // point-sprites and that means the GL_ALIASED_POINT_SIZE_RANGE limits apply also to // our shader based smooth dots, so return those: if ((windowRecord->gfxcaps & kPsychGfxCapSmoothPrimitives) && (idot_type < 3)) glGetFloatv(GL_POINT_SIZE_RANGE, (GLfloat*) &pointsizerange); // Whatever the final choice for smooth dots is, return its limits: PsychCopyOutDoubleArg(1, FALSE, (double) pointsizerange[0]); PsychCopyOutDoubleArg(2, FALSE, (double) pointsizerange[1]); // If this was only a query then we are done: if (PsychGetNumInputArgs() < 2) return(PsychError_none); } // Query, allocate and copy in all vectors... nrpoints = 2; nrsize = 0; colors = NULL; bytecolors = NULL; PsychPrepareRenderBatch(windowRecord, 2, &nrpoints, &xy, 4, &nc, &mc, &colors, &bytecolors, 3, &nrsize, &size, (GL_FLOAT == PsychGLFloatType(windowRecord))); isdoublecolors = (colors) ? TRUE:FALSE; isuint8colors = (bytecolors) ? TRUE:FALSE; usecolorvector = (nc>1) ? TRUE:FALSE; // Assign sizef as float-type array of sizes, if float mode active, NULL otherwise: sizef = (GL_FLOAT == PsychGLFloatType(windowRecord)) ? (float*) size : NULL; // Get center argument isArgThere = PsychIsArgPresent(PsychArgIn, 5); if(!isArgThere){ center = (double *) PsychMallocTemp(2 * sizeof(double)); center[0] = 0; center[1] = 0; } else { PsychAllocInDoubleMatArg(5, TRUE, &m, &n, &p, ¢er); if(p!=1 || n!=2 || m!=1) PsychErrorExitMsg(PsychError_user, "center must be a 1-by-2 vector"); } // Turn on antialiasing to draw circles? Or idot_type 4 for shader based square dots? if (idot_type) { // Smooth point rendering supported by gfx-driver and hardware? And user does not request our own stuff? if ((idot_type == 3) || (idot_type == 4) || !(windowRecord->gfxcaps & kPsychGfxCapSmoothPrimitives)) { // No. Need to roll our own shader + point sprite solution. if (!windowRecord->smoothPointShader && !nocando) { parentWindowRecord = PsychGetParentWindow(windowRecord); if (!parentWindowRecord->smoothPointShader) { // Build and assign shader to parent window, but allow this to silently fail: oldverbosity = PsychPrefStateGet_Verbosity(); PsychPrefStateSet_Verbosity(0); parentWindowRecord->smoothPointShader = PsychCreateGLSLProgram(PointSmoothFragmentShaderSrc, PointSmoothVertexShaderSrc, NULL); PsychPrefStateSet_Verbosity(oldverbosity); } if (parentWindowRecord->smoothPointShader) { // Got one compiled - assign it for use: windowRecord->smoothPointShader = parentWindowRecord->smoothPointShader; } else { // Failed. Record this failure so we can avoid retrying at next DrawDots invocation: nocando = TRUE; } } if (windowRecord->smoothPointShader) { // Activate point smooth shader, and point sprite operation on texunit 1 for coordinates on set 1: PsychSetShader(windowRecord, windowRecord->smoothPointShader); glActiveTexture(GL_TEXTURE1); glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_TRUE); glActiveTexture(GL_TEXTURE0); glEnable(GL_POINT_SPRITE); // Tell shader from where to get its color information: Unclamped high precision colors from texture coordinate set 0, or regular colors from vertex color attribute? glUniform1i(glGetUniformLocation(windowRecord->smoothPointShader, "useUnclampedFragColor"), (windowRecord->defaultDrawShader) ? 1 : 0); // Tell shader if it should shade smooth round dots, or square dots: glUniform1i(glGetUniformLocation(windowRecord->smoothPointShader, "drawRoundDots"), (idot_type != 4) ? 1 : 0); // Tell shader about current point size in pointSize uniform: glEnable(GL_PROGRAM_POINT_SIZE); usePointSizeArray = TRUE; } else if (idot_type != 4) { // Game over for round dot drawing: PsychErrorExitMsg(PsychError_user, "Point smoothing unsupported on your system and our shader based implementation failed as well in Screen('DrawDots')."); } else { // Type 4 requested but unsupported. Fallback to type 0, which is the same, just slower: idot_type = 0; } // Request square dots, without anti-aliasing: Better compatibility with // shader + point sprite operation, and needed for idot_type 0 fallback. glDisable(GL_POINT_SMOOTH); glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, (GLfloat*) &pointsizerange); } else { // User wants hw anti-aliased round smooth dots (idot_type = 1 or 2) and // hardware + driver support this. Request smooth points from hardware: glEnable(GL_POINT_SMOOTH); glGetFloatv(GL_POINT_SIZE_RANGE, (GLfloat*) &pointsizerange); // A dot type of 2 requests highest quality point smoothing: glHint(GL_POINT_SMOOTH_HINT, (idot_type > 1) ? GL_NICEST : GL_DONT_CARE); } } else { glDisable(GL_POINT_SMOOTH); glGetFloatv(GL_ALIASED_POINT_SIZE_RANGE, (GLfloat*) &pointsizerange); } // Does ES-GPU only support a fixed point diameter of 1 pixel? if ((pointsizerange[1] <= 1) && PsychIsGLES(windowRecord)) { // Yes. Not much point bailing on this, as it should be easily visible // during testing of a studies code on a OpenGL-ES device. lenient = TRUE; } // Accept optional 'lenient' flag, if provided: PsychCopyInFlagArg(7, FALSE, &lenient); // Set size of a single dot: if (!lenient && ((sizef && (sizef[0] > pointsizerange[1] || sizef[0] < pointsizerange[0])) || (!sizef && (size[0] > pointsizerange[1] || size[0] < pointsizerange[0])))) { printf("PTB-ERROR: You requested a point size of %f units, which is not in the range (%f to %f) supported by your graphics hardware.\n", (sizef) ? sizef[0] : size[0], pointsizerange[0], pointsizerange[1]); PsychErrorExitMsg(PsychError_user, "Unsupported point size requested in Screen('DrawDots')."); } // Setup initial common point size for all points: if (!usePointSizeArray) glPointSize((sizef) ? sizef[0] : (float) size[0]); if (usePointSizeArray) glMultiTexCoord1f(GL_TEXTURE2, (sizef) ? sizef[0] : (float) size[0]); // Setup modelview matrix to perform translation by 'center': glMatrixMode(GL_MODELVIEW); // Make a backup copy of the matrix: glPushMatrix(); // Apply a global translation of (center(x,y)) pixels to all following points: glTranslatef((float) center[0], (float) center[1], 0); // Render the array of 2D-Points - Efficient version: // This command sequence allows fast processing of whole arrays // of vertices (or points, in this case). It saves the call overhead // associated with the original implementation below and is potentially // optimized in specific OpenGL implementations. // Pass a pointer to the start of the point-coordinate array: glVertexPointer(2, PSYCHGLFLOAT, 0, &xy[0]); // Enable fast rendering of arrays: glEnableClientState(GL_VERTEX_ARRAY); if (usecolorvector) { PsychSetupVertexColorArrays(windowRecord, TRUE, mc, colors, bytecolors); } // Render all n points, starting at point 0, render them as POINTS: if ((nrsize == 1) || usePointSizeArray) { // Only one common size provided, or efficient shader based // path in use. We can use the fast path of only submitting // one glDrawArrays call to draw all GL_POINTS. For a single // common size, no further setup is needed. if (nrsize > 1) { // Individual size for each dot provided. Setup texture unit 2 // with a 1D texcoord array that stores per point size info in // texture coordinate set 2. But first validate point sizes: for (i = 0; i < nrpoints; i++) { if (!lenient && ((sizef && (sizef[i] > pointsizerange[1] || sizef[i] < pointsizerange[0])) || (!sizef && (size[i] > pointsizerange[1] || size[i] < pointsizerange[0])))) { printf("PTB-ERROR: You requested a point size of %f units, which is not in the range (%f to %f) supported by your graphics hardware.\n", (sizef) ? sizef[i] : size[i], pointsizerange[0], pointsizerange[1]); PsychErrorExitMsg(PsychError_user, "Unsupported point size requested in Screen('DrawDots')."); } } // Sizes are fine, setup texunit 2: glClientActiveTexture(GL_TEXTURE2); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glTexCoordPointer(1, (sizef) ? GL_FLOAT : GL_DOUBLE, 0, (sizef) ? (const GLvoid*) sizef : (const GLvoid*) size); } // Draw all points: glDrawArrays(GL_POINTS, 0, nrpoints); if (nrsize > 1) { // Individual size for each dot provided. Reset texture unit 2: glTexCoordPointer(1, (sizef) ? GL_FLOAT : GL_DOUBLE, 0, (const GLvoid*) NULL); glDisableClientState(GL_TEXTURE_COORD_ARRAY); // Back to default texunit 0: glClientActiveTexture(GL_TEXTURE0); } } else { // Different size for each dot provided and we can't use our shader based implementation: // We have to do One GL - call per dot: for (i=0; i<nrpoints; i++) { if (!lenient && ((sizef && (sizef[i] > pointsizerange[1] || sizef[i] < pointsizerange[0])) || (!sizef && (size[i] > pointsizerange[1] || size[i] < pointsizerange[0])))) { printf("PTB-ERROR: You requested a point size of %f units, which is not in the range (%f to %f) supported by your graphics hardware.\n", (sizef) ? sizef[i] : size[i], pointsizerange[0], pointsizerange[1]); PsychErrorExitMsg(PsychError_user, "Unsupported point size requested in Screen('DrawDots')."); } // Setup point size for this point: if (!usePointSizeArray) glPointSize((sizef) ? sizef[i] : (float) size[i]); // Render point: glDrawArrays(GL_POINTS, i, 1); } } // Disable fast rendering of arrays: glDisableClientState(GL_VERTEX_ARRAY); glVertexPointer(2, PSYCHGLFLOAT, 0, NULL); if (usecolorvector) PsychSetupVertexColorArrays(windowRecord, FALSE, 0, NULL, NULL); // Restore old matrix from backup copy, undoing the global translation: glPopMatrix(); // turn off antialiasing again if (idot_type) { glDisable(GL_POINT_SMOOTH); if (windowRecord->smoothPointShader) { // Deactivate point smooth shader and point sprite operation on texunit 1: PsychSetShader(windowRecord, 0); glActiveTexture(GL_TEXTURE1); glTexEnvi(GL_POINT_SPRITE, GL_COORD_REPLACE, GL_FALSE); glActiveTexture(GL_TEXTURE0); glDisable(GL_POINT_SPRITE); glDisable(GL_PROGRAM_POINT_SIZE); } } // Reset pointsize to 1.0 glPointSize(1); // Mark end of drawing op. This is needed for single buffered drawing: PsychFlushGL(windowRecord); //All psychfunctions require this. 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); }
// This also works as 'AddFrameToMovie', as almost all code is shared with 'GetImage'. // Only difference is where the fetched pixeldata is sent: To the movie encoder or to // a matlab/octave matrix. PsychError SCREENGetImage(void) { PsychRectType windowRect, sampleRect; int nrchannels, invertedY, stride; size_t ix, iy, sampleRectWidth, sampleRectHeight, redReturnIndex, greenReturnIndex, blueReturnIndex, alphaReturnIndex, planeSize; int viewid = 0; psych_uint8 *returnArrayBase, *redPlane; float *dredPlane; double *returnArrayBaseDouble; PsychWindowRecordType *windowRecord; GLboolean isDoubleBuffer, isStereo; char* buffername = NULL; psych_bool floatprecision = FALSE; GLenum whichBuffer = 0; int frameduration = 1; int moviehandle = 0; unsigned int twidth, theight, numChannels, bitdepth; unsigned char* framepixels; psych_bool isOES; // Called as 2nd personality "AddFrameToMovie" ? psych_bool isAddMovieFrame = PsychMatch(PsychGetFunctionName(), "AddFrameToMovie"); // All sub functions should have these two lines if (isAddMovieFrame) { PsychPushHelp(useString2, synopsisString2, seeAlsoString); } else { PsychPushHelp(useString, synopsisString, seeAlsoString); } if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; //cap the numbers of inputs and outputs PsychErrorExit(PsychCapNumInputArgs(5)); //The maximum number of inputs PsychErrorExit(PsychCapNumOutputArgs(1)); //The maximum number of outputs // Get windowRecord for this window: PsychAllocInWindowRecordArg(kPsychUseDefaultArgPosition, TRUE, &windowRecord); // Embedded subset has very limited support for readback formats : isOES = PsychIsGLES(windowRecord); // Make sure we don't execute on an onscreen window with pending async flip, as this would interfere // by touching the system backbuffer -> Impaired timing of the flip thread and undefined readback // of image data due to racing with the ops of the flipperthread on the same drawable. // // Note: It would be possible to allow drawBuffer readback if the drawBuffer is not multi-sampled // or if we can safely multisample-resolve without touching the backbuffer, but checking for all // special cases adds ugly complexity and is not really worth the effort, so we don't allow this. // // If this passes then PsychSetDrawingTarget() below will trigger additional validations to check // if execution of 'GetImage' is allowed under the current conditions for offscreen windows and // textures: if (PsychIsOnscreenWindow(windowRecord) && (windowRecord->flipInfo->asyncstate > 0)) { PsychErrorExitMsg(PsychError_user, "Calling this function on an onscreen window with a pending asynchronous flip is not allowed!"); } // Set window as drawingtarget: Even important if this binding is changed later on! // We need to make sure all needed transitions are done - esp. in non-imaging mode, // so backbuffer is in a useable state: PsychSetDrawingTarget(windowRecord); // Disable shaders: PsychSetShader(windowRecord, 0); // Soft-Reset drawingtarget. This is important to make sure no FBO's are bound, // otherwise the following glGets for GL_DOUBLEBUFFER and GL_STEREO will retrieve // wrong results, leading to totally wrong read buffer assignments down the road!! PsychSetDrawingTarget((PsychWindowRecordType*) 0x1); // Queries only available on desktop OpenGL: if (!isOES) { glGetBooleanv(GL_DOUBLEBUFFER, &isDoubleBuffer); glGetBooleanv(GL_STEREO, &isStereo); } else { // Make something reasonable up: isStereo = FALSE; isDoubleBuffer = TRUE; } // Force "quad-buffered" stereo mode if our own homegrown implementation is active: if (windowRecord->stereomode == kPsychFrameSequentialStereo) isStereo = TRUE; // Assign read buffer: if(PsychIsOnscreenWindow(windowRecord)) { // Onscreen window: We read from the front- or front-left buffer by default. // This works on single-buffered and double buffered contexts in a consistent fashion: // Copy in optional override buffer name: PsychAllocInCharArg(3, FALSE, &buffername); // Override buffer name provided? if (buffername) { // Which one is it? // "frontBuffer" is always a valid choice: if (PsychMatch(buffername, "frontBuffer")) whichBuffer = GL_FRONT; // Allow selection of left- or right front stereo buffer in stereo mode: if (PsychMatch(buffername, "frontLeftBuffer") && isStereo) whichBuffer = GL_FRONT_LEFT; if (PsychMatch(buffername, "frontRightBuffer") && isStereo) whichBuffer = GL_FRONT_RIGHT; // Allow selection of backbuffer in double-buffered mode: if (PsychMatch(buffername, "backBuffer") && isDoubleBuffer) whichBuffer = GL_BACK; // Allow selection of left- or right back stereo buffer in stereo mode: if (PsychMatch(buffername, "backLeftBuffer") && isStereo && isDoubleBuffer) whichBuffer = GL_BACK_LEFT; if (PsychMatch(buffername, "backRightBuffer") && isStereo && isDoubleBuffer) whichBuffer = GL_BACK_RIGHT; // Allow AUX buffer access for debug purposes: if (PsychMatch(buffername, "aux0Buffer")) whichBuffer = GL_AUX0; if (PsychMatch(buffername, "aux1Buffer")) whichBuffer = GL_AUX1; if (PsychMatch(buffername, "aux2Buffer")) whichBuffer = GL_AUX2; if (PsychMatch(buffername, "aux3Buffer")) whichBuffer = GL_AUX3; // If 'drawBuffer' is requested, but imaging pipeline inactive, ie., there is no real 'drawBuffer', then we // map this to the backbuffer, as on a non-imaging configuration, the backbuffer is pretty much exactly the // equivalent of the 'drawBuffer': if (PsychMatch(buffername, "drawBuffer") && !(windowRecord->imagingMode & kPsychNeedFastBackingStore)) whichBuffer = GL_BACK; } else { // Default is frontbuffer: whichBuffer = GL_FRONT; } } else { // Offscreen window or texture: They only have one buffer, which is the // backbuffer in double-buffered mode and the frontbuffer in single buffered mode: whichBuffer=(isDoubleBuffer) ? GL_BACK : GL_FRONT; } // Enable this windowRecords framebuffer as current drawingtarget. This should // also allow us to "GetImage" from Offscreen windows: if ((windowRecord->imagingMode & kPsychNeedFastBackingStore) || (windowRecord->imagingMode & kPsychNeedFastOffscreenWindows)) { // Special case: Imaging pipeline active - We need to activate system framebuffer // so we really read the content of the framebuffer and not of some FBO: if (PsychIsOnscreenWindow(windowRecord)) { // It's an onscreen window: // Homegrown frame-sequential stereo active? Need to remap some stuff: if (windowRecord->stereomode == kPsychFrameSequentialStereo) { // Back/Front buffers map to backleft/frontleft buffers: if (whichBuffer == GL_BACK) whichBuffer = GL_BACK_LEFT; if (whichBuffer == GL_FRONT) whichBuffer = GL_FRONT_LEFT; // Special case: Want to read from stereo front buffer? if ((whichBuffer == GL_FRONT_LEFT) || (whichBuffer == GL_FRONT_RIGHT)) { // These don't really exist in our homegrown implementation. Their equivalents are the // regular system front/backbuffers. Due to the bufferswaps happening every video // refresh cycle and the complex logic on when and how to blit finalizedFBOs into // the system buffers and the asynchronous execution of the parallel flipper thread, // we don't know which buffer (GL_BACK or GL_FRONT) corresponds to the leftFront or // rightFront buffer. Let's be stupid and just return the current front buffer for // FRONT_LEFT and the current back buffer for FRONT_RIGHT, but warn user about the // ambiguity: whichBuffer = (whichBuffer == GL_FRONT_LEFT) ? GL_FRONT : GL_BACK; if (PsychPrefStateGet_Verbosity() > 2) { printf("PTB-WARNING: In Screen('GetImage'): You selected retrieval of one of the stereo front buffers, while our homegrown frame-sequential\n"); printf("PTB-WARNING: In Screen('GetImage'): stereo display mode is active. This will impair presentation timing and may cause flicker. The\n"); printf("PTB-WARNING: In Screen('GetImage'): mapping of 'frontLeftBuffer' and 'frontRightBuffer' to actual stimulus content is very ambiguous\n"); printf("PTB-WARNING: In Screen('GetImage'): in this mode. You may therefore end up with the content of the wrong buffer returned! Check results\n"); printf("PTB-WARNING: In Screen('GetImage'): carefully! Better read from 'backLeftBuffer' or 'backRightBuffer' for well defined results.\n\n"); } } } // Homegrown frame-sequential stereo active and backleft or backright buffer requested? if (((whichBuffer == GL_BACK_LEFT) || (whichBuffer == GL_BACK_RIGHT)) && (windowRecord->stereomode == kPsychFrameSequentialStereo)) { // We can get the equivalent of the backLeft/RightBuffer from the finalizedFBO's in this mode. Get their content: viewid = (whichBuffer == GL_BACK_RIGHT) ? 1 : 0; whichBuffer = GL_COLOR_ATTACHMENT0_EXT; // Bind finalizedFBO as framebuffer to read from: glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, windowRecord->fboTable[windowRecord->finalizedFBO[viewid]]->fboid); // Make sure binding gets released at end of routine: viewid = -1; } // No frame-sequential stereo: Full imaging pipeline active and one of the drawBuffer's requested? else if (buffername && (PsychMatch(buffername, "drawBuffer")) && (windowRecord->imagingMode & kPsychNeedFastBackingStore)) { // Activate drawBufferFBO: PsychSetDrawingTarget(windowRecord); whichBuffer = GL_COLOR_ATTACHMENT0_EXT; // Is the drawBufferFBO multisampled? viewid = (((windowRecord->stereomode > 0) && (windowRecord->stereodrawbuffer == 1)) ? 1 : 0); if (windowRecord->fboTable[windowRecord->drawBufferFBO[viewid]]->multisample > 0) { // It is! We can't read from a multisampled FBO. Need to perform a multisample resolve operation and read // from the resolved unisample buffer instead. This is only safe if the unisample buffer is either a dedicated // FBO, or - in case its the final system backbuffer etc. - if preflip operations haven't been performed yet. // If non dedicated buffer (aka finalizedFBO) and preflip ops have already happened, then the backbuffer contains // final content for an upcoming Screen('Flip') and we can't use (and therefore taint) that buffer. if ((windowRecord->inputBufferFBO[viewid] == windowRecord->finalizedFBO[viewid]) && (windowRecord->backBufferBackupDone)) { // Target for resolve is finalized FBO (probably system backbuffer) and preflip ops have run already. We // can't do the resolve op, as this would screw up the backbuffer with the final stimulus: printf("PTB-ERROR: Tried to 'GetImage' from a multisampled 'drawBuffer', but can't perform anti-aliasing pass due to\n"); printf("PTB-ERROR: lack of a dedicated resolve buffer.\n"); printf("PTB-ERROR: You can get what you wanted by either one of two options:\n"); printf("PTB-ERROR: Either enable a processing stage in the imaging pipeline, even if you don't need it, e.g., by setting\n"); printf("PTB-ERROR: the imagingmode argument in the 'OpenWindow' call to kPsychNeedImageProcessing. This will create a\n"); printf("PTB-ERROR: suitable resolve buffer. Or place the 'GetImage' call before any Screen('DrawingFinished') call, then\n"); printf("PTB-ERROR: i can (ab-)use the system backbuffer as a temporary resolve buffer.\n\n"); PsychErrorExitMsg(PsychError_user, "Tried to 'GetImage' from a multi-sampled 'drawBuffer'. Unsupported operation under given conditions."); } else { // Ok, the inputBufferFBO is a suitable temporary resolve buffer. Perform a multisample resolve blit to it: // A simple glBlitFramebufferEXT() call will do the copy & downsample operation: glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, windowRecord->fboTable[windowRecord->drawBufferFBO[viewid]]->fboid); glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, windowRecord->fboTable[windowRecord->inputBufferFBO[viewid]]->fboid); glBlitFramebufferEXT(0, 0, windowRecord->fboTable[windowRecord->inputBufferFBO[viewid]]->width, windowRecord->fboTable[windowRecord->inputBufferFBO[viewid]]->height, 0, 0, windowRecord->fboTable[windowRecord->inputBufferFBO[viewid]]->width, windowRecord->fboTable[windowRecord->inputBufferFBO[viewid]]->height, GL_COLOR_BUFFER_BIT, GL_NEAREST); // Bind inputBuffer as framebuffer: glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, windowRecord->fboTable[windowRecord->inputBufferFBO[viewid]]->fboid); viewid = -1; } } } else { // No: Activate system framebuffer: PsychSetDrawingTarget(NULL); } } else { // Offscreen window or texture: Select drawing target as usual, // but set color attachment as read buffer: PsychSetDrawingTarget(windowRecord); whichBuffer = GL_COLOR_ATTACHMENT0_EXT; // We do not support multisampled readout: if (windowRecord->fboTable[windowRecord->drawBufferFBO[0]]->multisample > 0) { printf("PTB-ERROR: You tried to Screen('GetImage', ...); from an offscreen window or texture which has multisample anti-aliasing enabled.\n"); printf("PTB-ERROR: This operation is not supported. You must first use Screen('CopyWindow') to create a non-multisampled copy of the\n"); printf("PTB-ERROR: texture or offscreen window, then use 'GetImage' on that copy. The copy will be anti-aliased, so you'll get what you\n"); printf("PTB-ERROR: wanted with a bit more effort. Sorry for the inconvenience, but this is mostly a hardware limitation.\n\n"); PsychErrorExitMsg(PsychError_user, "Tried to 'GetImage' from a multi-sampled texture or offscreen window. Unsupported operation."); } } } else { // Normal case: No FBO based imaging - Select drawing target as usual: PsychSetDrawingTarget(windowRecord); } if (!isOES) { // Select requested read buffer, after some double-check: if (whichBuffer == 0) PsychErrorExitMsg(PsychError_user, "Invalid or unknown 'bufferName' argument provided."); glReadBuffer(whichBuffer); if (PsychPrefStateGet_Verbosity() > 5) printf("PTB-DEBUG: In Screen('GetImage'): GL-Readbuffer whichBuffer = %i\n", whichBuffer); } else { // OES: No way to select readbuffer, it is "hard-coded" by system spec, depending // on framebuffer. For bound FBO, always color attachment zero, for system framebuffer, // always front buffer on single-buffered setup, back buffer on double-buffered setup: if (buffername && PsychIsOnscreenWindow(windowRecord) && (whichBuffer != GL_COLOR_ATTACHMENT0_EXT)) { // Some part of the real system framebuffer of an onscreen window explicitely requested. if ((windowRecord->windowType == kPsychSingleBufferOnscreen) && (whichBuffer != GL_FRONT) && (PsychPrefStateGet_Verbosity() > 1)) { printf("PTB-WARNING: Tried to Screen('GetImage') single-buffered framebuffer '%s', but only 'frontBuffer' supported on OpenGL-ES. Returning that instead.\n", buffername); } if ((windowRecord->windowType == kPsychDoubleBufferOnscreen) && (whichBuffer != GL_BACK) && (PsychPrefStateGet_Verbosity() > 1)) { printf("PTB-WARNING: Tried to Screen('GetImage') double-buffered framebuffer '%s', but only 'backBuffer' supported on OpenGL-ES. Returning that instead.\n", buffername); } } } if (whichBuffer == GL_COLOR_ATTACHMENT0_EXT) { // FBO of texture / offscreen window / onscreen drawBuffer/inputBuffer // has size of clientrect -- potentially larger or smaller than backbuffer: PsychCopyRect(windowRect, windowRecord->clientrect); } else { // Non-FBO backed texture / offscreen window / onscreen window has size // of raw rect (==clientrect for non-onscreen, == backbuffer size for onscreen): PsychCopyRect(windowRect, windowRecord->rect); } // Retrieve optional read rectangle: if(!PsychCopyInRectArg(2, FALSE, sampleRect)) PsychCopyRect(sampleRect, windowRect); if (IsPsychRectEmpty(sampleRect)) return(PsychError_none); // Compute sampling rectangle: if ((PsychGetWidthFromRect(sampleRect) >= INT_MAX) || (PsychGetHeightFromRect(sampleRect) >= INT_MAX)) { PsychErrorExitMsg(PsychError_user, "Too big 'rect' argument provided. Both width and height of the rect must not exceed 2^31 pixels!"); } sampleRectWidth = (size_t) PsychGetWidthFromRect(sampleRect); sampleRectHeight= (size_t) PsychGetHeightFromRect(sampleRect); // Regular image fetch to runtime, or adding to a movie? if (!isAddMovieFrame) { // Regular fetch: // Get optional floatprecision flag: We return data with float-precision if // this flag is set. By default we return uint8 data: PsychCopyInFlagArg(4, FALSE, &floatprecision); // Get the optional number of channels flag: By default we return 3 channels, // the Red, Green, and blue color channel: nrchannels = 3; PsychCopyInIntegerArg(5, FALSE, &nrchannels); if (nrchannels < 1 || nrchannels > 4) PsychErrorExitMsg(PsychError_user, "Number of requested channels 'nrchannels' must be between 1 and 4!"); if (!floatprecision) { // Readback of standard 8bpc uint8 pixels: // No Luminance + Alpha on OES: if (isOES && (nrchannels == 2)) PsychErrorExitMsg(PsychError_user, "Number of requested channels 'nrchannels' == 2 not supported on OpenGL-ES!"); PsychAllocOutUnsignedByteMatArg(1, TRUE, (int) sampleRectHeight, (int) sampleRectWidth, (int) nrchannels, &returnArrayBase); if (isOES) { // We only do RGBA reads on OES, then discard unwanted stuff ourselves: redPlane = (psych_uint8*) PsychMallocTemp((size_t) 4 * sampleRectWidth * sampleRectHeight); } else { redPlane = (psych_uint8*) PsychMallocTemp((size_t) nrchannels * sampleRectWidth * sampleRectHeight); } planeSize = sampleRectWidth * sampleRectHeight; glPixelStorei(GL_PACK_ALIGNMENT,1); invertedY = (int) (windowRect[kPsychBottom] - sampleRect[kPsychBottom]); if (isOES) { glReadPixels((int) sampleRect[kPsychLeft], invertedY, (int) sampleRectWidth, (int) sampleRectHeight, GL_RGBA, GL_UNSIGNED_BYTE, redPlane); stride = 4; } else { stride = nrchannels; if (nrchannels==1) glReadPixels((int) sampleRect[kPsychLeft], invertedY, (int) sampleRectWidth, (int) sampleRectHeight, GL_RED, GL_UNSIGNED_BYTE, redPlane); if (nrchannels==2) glReadPixels((int) sampleRect[kPsychLeft], invertedY, (int) sampleRectWidth, (int) sampleRectHeight, GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, redPlane); if (nrchannels==3) glReadPixels((int) sampleRect[kPsychLeft], invertedY, (int) sampleRectWidth, (int) sampleRectHeight, GL_RGB, GL_UNSIGNED_BYTE, redPlane); if (nrchannels==4) glReadPixels((int) sampleRect[kPsychLeft], invertedY, (int) sampleRectWidth, (int) sampleRectHeight, GL_RGBA, GL_UNSIGNED_BYTE, redPlane); } //in one pass transpose and flip what we read with glReadPixels before returning. //-glReadPixels insists on filling up memory in sequence by reading the screen row-wise whearas Matlab reads up memory into columns. //-the Psychtoolbox screen as setup by gluOrtho puts 0,0 at the top left of the window but glReadPixels always believes that it's at the bottom left. for(ix=0; ix < sampleRectWidth; ix++){ for(iy=0; iy < sampleRectHeight; iy++){ // Compute write-indices for returned data: redReturnIndex=PsychIndexElementFrom3DArray(sampleRectHeight, sampleRectWidth, nrchannels, iy, ix, 0); greenReturnIndex=PsychIndexElementFrom3DArray(sampleRectHeight, sampleRectWidth, nrchannels, iy, ix, 1); blueReturnIndex=PsychIndexElementFrom3DArray(sampleRectHeight, sampleRectWidth, nrchannels, iy, ix, 2); alphaReturnIndex=PsychIndexElementFrom3DArray(sampleRectHeight, sampleRectWidth, nrchannels, iy, ix, 3); // Always return RED/LUMINANCE channel: returnArrayBase[redReturnIndex] = redPlane[(ix + ((sampleRectHeight-1) - iy ) * sampleRectWidth) * (size_t) stride + 0]; // Other channels on demand: if (nrchannels>1) returnArrayBase[greenReturnIndex] = redPlane[(ix + ((sampleRectHeight-1) - iy ) * sampleRectWidth) * (size_t) stride + 1]; if (nrchannels>2) returnArrayBase[blueReturnIndex] = redPlane[(ix + ((sampleRectHeight-1) - iy ) * sampleRectWidth) * (size_t) stride + 2]; if (nrchannels>3) returnArrayBase[alphaReturnIndex] = redPlane[(ix + ((sampleRectHeight-1) - iy ) * sampleRectWidth) * (size_t) stride + 3]; } } } else { // Readback of standard 32bpc float pixels into a double matrix: // No Luminance + Alpha on OES: if (isOES && (nrchannels == 2)) PsychErrorExitMsg(PsychError_user, "Number of requested channels 'nrchannels' == 2 not supported on OpenGL-ES!"); // Only float readback on floating point FBO's with EXT_color_buffer_float support: if (isOES && ((whichBuffer != GL_COLOR_ATTACHMENT0_EXT) || (windowRecord->bpc < 16) || !glewIsSupported("GL_EXT_color_buffer_float"))) { printf("PTB-ERROR: Tried to 'GetImage' pixels in floating point format from a non-floating point surface, or not supported by your hardware.\n"); PsychErrorExitMsg(PsychError_user, "'GetImage' of floating point values from given object not supported on OpenGL-ES!"); } PsychAllocOutDoubleMatArg(1, TRUE, (int) sampleRectHeight, (int) sampleRectWidth, (int) nrchannels, &returnArrayBaseDouble); if (isOES) { dredPlane = (float*) PsychMallocTemp((size_t) 4 * sizeof(float) * sampleRectWidth * sampleRectHeight); stride = 4; } else { dredPlane = (float*) PsychMallocTemp((size_t) nrchannels * sizeof(float) * sampleRectWidth * sampleRectHeight); stride = nrchannels; } planeSize = sampleRectWidth * sampleRectHeight * sizeof(float); glPixelStorei(GL_PACK_ALIGNMENT, 1); invertedY = (int) (windowRect[kPsychBottom]-sampleRect[kPsychBottom]); if (!isOES) { if (nrchannels==1) glReadPixels((int) sampleRect[kPsychLeft], invertedY, (int) sampleRectWidth, (int) sampleRectHeight, GL_RED, GL_FLOAT, dredPlane); if (nrchannels==2) glReadPixels((int) sampleRect[kPsychLeft], invertedY, (int) sampleRectWidth, (int) sampleRectHeight, GL_LUMINANCE_ALPHA, GL_FLOAT, dredPlane); if (nrchannels==3) glReadPixels((int) sampleRect[kPsychLeft], invertedY, (int) sampleRectWidth, (int) sampleRectHeight, GL_RGB, GL_FLOAT, dredPlane); if (nrchannels==4) glReadPixels((int) sampleRect[kPsychLeft], invertedY, (int) sampleRectWidth, (int) sampleRectHeight, GL_RGBA, GL_FLOAT, dredPlane); } else { glReadPixels((int) sampleRect[kPsychLeft], invertedY, (int) sampleRectWidth, (int) sampleRectHeight, GL_RGBA, GL_FLOAT, dredPlane); } //in one pass transpose and flip what we read with glReadPixels before returning. //-glReadPixels insists on filling up memory in sequence by reading the screen row-wise whearas Matlab reads up memory into columns. //-the Psychtoolbox screen as setup by gluOrtho puts 0,0 at the top left of the window but glReadPixels always believes that it's at the bottom left. for(ix=0; ix < sampleRectWidth; ix++){ for(iy=0; iy < sampleRectHeight; iy++){ // Compute write-indices for returned data: redReturnIndex=PsychIndexElementFrom3DArray(sampleRectHeight, sampleRectWidth, nrchannels, iy, ix, 0); greenReturnIndex=PsychIndexElementFrom3DArray(sampleRectHeight, sampleRectWidth, nrchannels, iy, ix, 1); blueReturnIndex=PsychIndexElementFrom3DArray(sampleRectHeight, sampleRectWidth, nrchannels, iy, ix, 2); alphaReturnIndex=PsychIndexElementFrom3DArray(sampleRectHeight, sampleRectWidth, nrchannels, iy, ix, 3); // Always return RED/LUMINANCE channel: returnArrayBaseDouble[redReturnIndex] = dredPlane[(ix + ((sampleRectHeight-1) - iy ) * sampleRectWidth) * (size_t) stride + 0]; // Other channels on demand: if (nrchannels>1) returnArrayBaseDouble[greenReturnIndex] = dredPlane[(ix + ((sampleRectHeight-1) - iy ) * sampleRectWidth) * (size_t) stride + 1]; if (nrchannels>2) returnArrayBaseDouble[blueReturnIndex] = dredPlane[(ix + ((sampleRectHeight-1) - iy ) * sampleRectWidth) * (size_t) stride + 2]; if (nrchannels>3) returnArrayBaseDouble[alphaReturnIndex] = dredPlane[(ix + ((sampleRectHeight-1) - iy ) * sampleRectWidth) * (size_t) stride + 3]; } } } } if (isAddMovieFrame) { // Adding of image to a movie requested: // Get optional moviehandle: moviehandle = 0; PsychCopyInIntegerArg(4, FALSE, &moviehandle); if (moviehandle < 0) PsychErrorExitMsg(PsychError_user, "Provided 'moviehandle' is negative. Must be greater or equal to zero!"); // Get optional frameduration: frameduration = 1; PsychCopyInIntegerArg(5, FALSE, &frameduration); if (frameduration < 1) PsychErrorExitMsg(PsychError_user, "Number of requested framedurations 'frameduration' is negative. Must be greater than zero!"); framepixels = PsychGetVideoFrameForMoviePtr(moviehandle, &twidth, &theight, &numChannels, &bitdepth); if (framepixels) { glPixelStorei(GL_PACK_ALIGNMENT,1); invertedY = (int) (windowRect[kPsychBottom] - sampleRect[kPsychBottom]); if (isOES) { if (bitdepth != 8) PsychErrorExitMsg(PsychError_user, "AddFrameToMovie failed due to wrong bpc value. Only 8 bpc supported on OpenGL-ES."); if (numChannels == 4) { // OES: BGRA supported? if (glewIsSupported("GL_EXT_read_format_bgra")) { // Yep: Readback in a compatible and acceptably fast format: glReadPixels((int) sampleRect[kPsychLeft], invertedY, twidth, theight, GL_BGRA, GL_UNSIGNED_BYTE, framepixels); } else { // Suboptimal readback path. will also cause swapped colors in movie writing: glReadPixels((int) sampleRect[kPsychLeft], invertedY, twidth, theight, GL_RGBA, GL_UNSIGNED_BYTE, framepixels); } } else if (numChannels == 3) { glReadPixels((int) sampleRect[kPsychLeft], invertedY, twidth, theight, GL_RGB, GL_UNSIGNED_BYTE, framepixels); } else PsychErrorExitMsg(PsychError_user, "AddFrameToMovie failed due to wrong number of channels. Only 3 or 4 channels are supported on OpenGL-ES."); } else { // Desktop-GL: Use optimal format and support 16 bpc bitdepth as well. switch (numChannels) { case 4: glReadPixels((int) sampleRect[kPsychLeft], invertedY, twidth, theight, GL_BGRA, (bitdepth <= 8) ? GL_UNSIGNED_INT_8_8_8_8 : GL_UNSIGNED_SHORT, framepixels); break; case 3: glReadPixels((int) sampleRect[kPsychLeft], invertedY, twidth, theight, GL_RGB, (bitdepth <= 8) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT, framepixels); break; case 1: glReadPixels((int) sampleRect[kPsychLeft], invertedY, twidth, theight, GL_RED, (bitdepth <= 8) ? GL_UNSIGNED_BYTE : GL_UNSIGNED_SHORT, framepixels); break; default: PsychErrorExitMsg(PsychError_user, "AddFrameToMovie failed due to wrong number of channels. Only 1, 3 or 4 channels are supported on OpenGL."); break; } } // Add frame to movie, mark it as "upside down", with invalid -1 timestamp and a duration of frameduration ticks: if (PsychAddVideoFrameToMovie(moviehandle, frameduration, TRUE, -1) != 0) { PsychErrorExitMsg(PsychError_user, "AddFrameToMovie failed with error above!"); } } else { PsychErrorExitMsg(PsychError_user, "Invalid 'moviePtr' provided. Doesn't correspond to a movie open for recording!"); } } if (viewid == -1) { // Need to reset framebuffer binding to get rid of the inputBufferFBO which is bound due to // multisample resolve ops, or of other special FBO bindings --> Activate system framebuffer: PsychSetDrawingTarget(NULL); } return(PsychError_none); }
PsychError SCREENMakeTexture(void) { size_t ix, iters; PsychWindowRecordType *textureRecord; PsychWindowRecordType *windowRecord; PsychRectType rect; psych_bool isImageMatrixBytes, isImageMatrixDoubles; int numMatrixPlanes, xSize, ySize; unsigned char *byteMatrix; double *doubleMatrix; GLuint *texturePointer; GLubyte *texturePointer_b; GLfloat *texturePointer_f; double *rp, *gp, *bp, *ap; GLubyte *rpb, *gpb, *bpb, *apb; int usepoweroftwo, usefloatformat, assume_texorientation, textureShader; double optimized_orientation; psych_bool bigendian; psych_bool planar_storage = FALSE; double scaled = 1.0; double offsetd; // Detect endianity (byte-order) of machine: ix=255; rpb=(GLubyte*) &ix; bigendian = ( *rpb == 255 ) ? FALSE : TRUE; ix = 0; rpb = NULL; if(PsychPrefStateGet_DebugMakeTexture()) //MARK #1 StoreNowTime(); //all subfunctions should have these two lines. PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; //Get the window structure for the onscreen window. It holds the onscreein GL context which we will need in the //final step when we copy the texture from system RAM onto the screen. PsychErrorExit(PsychCapNumInputArgs(7)); PsychErrorExit(PsychRequireNumInputArgs(2)); PsychErrorExit(PsychCapNumOutputArgs(1)); //get the window record from the window record argument and get info from the window record PsychAllocInWindowRecordArg(kPsychUseDefaultArgPosition, TRUE, &windowRecord); if((windowRecord->windowType!=kPsychDoubleBufferOnscreen) && (windowRecord->windowType!=kPsychSingleBufferOnscreen)) PsychErrorExitMsg(PsychError_user, "MakeTexture called on something else than a onscreen window"); // Compute offset to add when converting double input matrices into uint8 textures, so that // the uint8 texture data matches how the hardware converts floating point color values to // fixed-point integer color values. When we convert a double float matrix into uint8 textures, // we want to apply the same conversion behavior that the hw applies when drawing "floating point // color" primitives into a fixed point framebuffer. This is important, e.g., for a uint8 texture // created from double 0.5 luminance values to draw the same color value into a 8 bpc framebuffer, // as a framebuffer clear command, drawdots command etc. with normalized color 0.5 does. // See what happens if this goes wrong (as of Psychtoolbox 3.0.14) in forum message #23300. // // The original OpenGL 1.0 spec says hw should convert float -> fixed with round-to-nearest // behaviour (Section 2.12.9). The OpenGL 4.5 spec leaves it up to the GL implementation if it // wants to truncate, round, ceil up or down to the closest integer etc., but recommends // round-to-nearest (Section 2.3.5.2). In practice, some tested AMD and NVidia hw on Linux and macOS // truncates, whereas some tested Intel hw on Linux and macOS rounds to nearest, so we apparently // can't rely on defined behaviour and therefore detect what the given hw does during startup. offsetd = (windowRecord->gfxcaps & kPsychGfxCapFloatToIntRound) ? 0.5 : 0.0; if (windowRecord->applyColorRangeToDoubleInputMakeTexture == 1) { // Apply scaling to uint8 textures which are created from double input matrices, so that // the Screen('ColorRange', ...) affects those values just as it affects other color specs for other // drawing commands: scaled = 255.0 / fabs(windowRecord->colorRange); } else { // Do not apply scaling based on colorRange to double input for uint8 textures: scaled = 1.0; } // Get optional texture orientation flag: assume_texorientation = 0; PsychCopyInIntegerArg(6, FALSE, &assume_texorientation); // Get optional texture shader handle: textureShader = 0; PsychCopyInIntegerArg(7, FALSE, &textureShader); //get the argument and sanity check it. isImageMatrixBytes=PsychAllocInUnsignedByteMatArg(2, kPsychArgAnything, &ySize, &xSize, &numMatrixPlanes, &byteMatrix); isImageMatrixDoubles=PsychAllocInDoubleMatArg(2, kPsychArgAnything, &ySize, &xSize, &numMatrixPlanes, &doubleMatrix); if (!(isImageMatrixBytes || isImageMatrixDoubles)) PsychErrorExitMsg(PsychError_user, "Illegal argument type. Image matrices must be uint8 or double data type."); if (numMatrixPlanes < 1 || numMatrixPlanes > 4) { iters = 0; // Make compiler happy. PsychErrorExitMsg(PsychError_inputMatrixIllegalDimensionSize, "Specified image matrix exceeds maximum depth of 4 layers"); } if (ySize<1 || xSize <1) { iters = 0; // Make compiler happy. PsychErrorExitMsg(PsychError_inputMatrixIllegalDimensionSize, "Specified image matrix must be at least 1 x 1 pixels in size"); } // Is this a special image matrix which is already pre-transposed to fit our optimal format? if (assume_texorientation == 2) { // Yes. Swap xSize and ySize to take this into account: ix = (size_t) xSize; xSize = ySize; ySize = (int) ix; ix = 0; } // Build defining rect for this texture: PsychMakeRect(rect, 0, 0, xSize, ySize); // Copy in texture preferred draw orientation hint. We default to zero degrees, if // not provided. // This parameter is not yet used. It is silently ignorerd for now... optimized_orientation = 0; PsychCopyInDoubleArg(3, FALSE, &optimized_orientation); // Copy in special creation mode flag: It defaults to zero. If set to 1 then we // always create a power-of-two GL_TEXTURE_2D texture. This is useful if one wants // to create and use drifting gratings with no effort - texture wrapping is only available // for GL_TEXTURE_2D, not for non-pot types. It is also useful if the texture is to be // exported to external OpenGL code to simplify tex coords assignments. usepoweroftwo=0; PsychCopyInIntegerArg(4, FALSE, &usepoweroftwo); // Check if size constraints are fullfilled for power-of-two mode: // We relax this constraint if GPU supports non-power-of-two texture extension. if ((usepoweroftwo & 1) && !(windowRecord->gfxcaps & kPsychGfxCapNPOTTex)) { for(ix = 1; ix < (size_t) xSize; ix*=2); if (ix != (size_t) xSize) { PsychErrorExitMsg(PsychError_inputMatrixIllegalDimensionSize, "Power-of-two texture requested but width of imageMatrix is not a power of two!"); } for(ix = 1; ix < (size_t) ySize; ix*=2); if (ix != (size_t) ySize) { PsychErrorExitMsg(PsychError_inputMatrixIllegalDimensionSize, "Power-of-two texture requested but height of imageMatrix is not a power of two!"); } } // Check if creation of a floating point texture is requested? We default to non-floating point, // standard 8 bpc textures if this parameter is not provided. usefloatformat = 0; PsychCopyInIntegerArg(5, FALSE, &usefloatformat); if (usefloatformat<0 || usefloatformat>2) PsychErrorExitMsg(PsychError_user, "Invalid value for 'floatprecision' parameter provided! Valid values are 0 for 8bpc int, 1 for 16bpc float or 2 for 32bpc float."); if (usefloatformat && !isImageMatrixDoubles) { // Floating point texture requested. We only support this if our input is a double matrix, not // for uint8 matrices - converting them to float precision would be just a waste of ressources // without any benefit for precision. PsychErrorExitMsg(PsychError_user, "Creation of a floating point precision texture requested, but uint8 matrix provided! Only double matrices are acceptable for this mode."); } // Float texture on OpenGL-ES requested? if ((usefloatformat > 0) && PsychIsGLES(windowRecord)) { // 32-bpc float textures supported? We can't do 16-bpc so we fail if 32 bpc is unsupported: if (!(windowRecord->gfxcaps & kPsychGfxCapFPTex32)) PsychErrorExitMsg(PsychError_user, "Creation of a floating point precision texture requested, but this is not supported by your hardware!"); // Upgrade float format to 2 aka 32 bpc float, the only thing we can handle: usefloatformat = 2; } //Create a texture record. Really just a window record adapted for textures. PsychCreateWindowRecord(&textureRecord); textureRecord->windowType=kPsychTexture; // MK: We need to assign the screen number of the onscreen-window, so PsychCreateTexture() // can query the size of the screen/onscreen-window... textureRecord->screenNumber=windowRecord->screenNumber; textureRecord->depth=32; PsychCopyRect(textureRecord->rect, rect); // Is texture storage in planar format explicitely requested by usercode? Do the gpu and its size // constraints on textures support planar storage for this image? // Can a proper planar -> interleaved remapping GLSL shader be generated and assigned for this texture? if ((usepoweroftwo == 4) && (numMatrixPlanes > 1) && (windowRecord->gfxcaps & kPsychGfxCapFBO) && !(PsychPrefStateGet_ConserveVRAM() & kPsychDontCacheTextures) && (ySize * numMatrixPlanes <= windowRecord->maxTextureSize) && PsychAssignPlanarTextureShaders(textureRecord, windowRecord, numMatrixPlanes)) { // Yes: Use the planar texture storage fast-path. planar_storage = TRUE; if (PsychPrefStateGet_Verbosity() > 6) printf("PTB-DEBUG: Using planar storage for %i layer texture of size %i x %i texels.\n", numMatrixPlanes, xSize, ySize); } else { planar_storage = FALSE; if (PsychPrefStateGet_Verbosity() > 7) printf("PTB-DEBUG: Using standard storage for %i layer texture of size %i x %i texels.\n", numMatrixPlanes, xSize, ySize); } //Allocate the texture memory and copy the MATLAB matrix into the texture memory. if (usefloatformat || (planar_storage && !isImageMatrixBytes)) { // Allocate a double for each color component and pixel: textureRecord->textureMemorySizeBytes = sizeof(double) * (size_t) numMatrixPlanes * (size_t) xSize * (size_t) ySize; } else { // Allocate one byte per color component and pixel: textureRecord->textureMemorySizeBytes = (size_t) numMatrixPlanes * (size_t) xSize * (size_t) ySize; } // We allocate our own intermediate conversion buffer unless this is // creation of a single-layer luminance8 integer texture from a single // layer uint8 input matrix and client storage is disabled. In that case, we can use a zero-copy path: if ((isImageMatrixBytes && (numMatrixPlanes == 1) && (!usefloatformat) && !(PsychPrefStateGet_ConserveVRAM() & kPsychDontCacheTextures)) || (isImageMatrixBytes && planar_storage)) { // Zero copy path: texturePointer = NULL; // Set usefloatformat = 0 to prevent false compiler warnings about iters // being used uninitialized: usefloatformat = 0; } else { // Allocate memory: if(PsychPrefStateGet_DebugMakeTexture()) StoreNowTime(); textureRecord->textureMemory = malloc(textureRecord->textureMemorySizeBytes); if(PsychPrefStateGet_DebugMakeTexture()) StoreNowTime(); texturePointer = textureRecord->textureMemory; } // Does script explicitely request usage of a GL_TEXTURE_2D texture? if (usepoweroftwo & 1) { // Enforce creation as a GL_TEXTURE_2D texture: textureRecord->texturetarget=GL_TEXTURE_2D; } // Now the conversion routines that convert Matlab/Octave matrices into memory // buffers suitable for OpenGL: if (planar_storage) { // Planar texture storage, backed by a LUMINANCE texture container: // Zero-Copy possible? Only for uint8 input -> uint8 output: if (texturePointer == NULL) { texturePointer = (GLuint*) byteMatrix; textureRecord->textureMemory = texturePointer; // Set size to zero, so PsychCreateTexture() does not free() our // input buffer: textureRecord->textureMemorySizeBytes = 0; // This is always a LUMINANCE8 texture, backing our planar uint8 texture: textureRecord->depth = 8 * numMatrixPlanes; textureRecord->textureexternaltype = GL_UNSIGNED_BYTE; textureRecord->textureexternalformat = GL_LUMINANCE; textureRecord->textureinternalformat = GL_LUMINANCE8; } else { // Some cast operation needed from double input format. // We always cast from double to float, potentially with // normalization and/or checking of value range. textureRecord->textureexternalformat = GL_LUMINANCE; if (usefloatformat) { // Floating point or other high precision format: textureRecord->depth = ((usefloatformat == 1) ? 16 : 32) * numMatrixPlanes; textureRecord->textureexternaltype = GL_FLOAT; textureRecord->textureinternalformat = (usefloatformat == 1) ? GL_LUMINANCE_FLOAT16_APPLE : GL_LUMINANCE_FLOAT32_APPLE; // Override for missing floating point texture support: Try to use 16 bit fixed point signed normalized textures [-1.0 ; 1.0] resolved at 15 bits: if ((usefloatformat == 1) && !(windowRecord->gfxcaps & kPsychGfxCapFPTex16)) textureRecord->textureinternalformat = GL_LUMINANCE16_SNORM; // Perform copy with double -> float cast: iters = (size_t) xSize * (size_t) ySize * (size_t) numMatrixPlanes; texturePointer_f = (GLfloat*) texturePointer; for(ix = 0; ix < iters; ix++) { *(texturePointer_f++) = (GLfloat) *(doubleMatrix++); } iters = (size_t) xSize * (size_t) ySize; } else { // 8 Bit format, but from double input matrix -> cast to uint8: textureRecord->depth = 8 * numMatrixPlanes; textureRecord->textureexternaltype = GL_UNSIGNED_BYTE; textureRecord->textureinternalformat = GL_LUMINANCE8; iters = (size_t) xSize * (size_t) ySize * (size_t) numMatrixPlanes; texturePointer_b = (GLubyte*) texturePointer; for(ix = 0; ix < iters; ix++) { *(texturePointer_b++) = (GLubyte) (offsetd + scaled * *(doubleMatrix++)); } iters = (size_t) xSize * (size_t) ySize; } } } else if (usefloatformat) { // Conversion routines for HDR 16 bpc or 32 bpc textures -- Slow path. // Our input is always double matrices... iters = (size_t) xSize * (size_t) ySize; // Our input buffer is always of GL_FLOAT precision: textureRecord->textureexternaltype = GL_FLOAT; texturePointer_f=(GLfloat*) texturePointer; if (numMatrixPlanes==1) { for(ix=0;ix<iters;ix++){ *(texturePointer_f++)= (GLfloat) *(doubleMatrix++); } textureRecord->depth=(usefloatformat==1) ? 16 : 32; textureRecord->textureinternalformat = (usefloatformat==1) ? GL_LUMINANCE_FLOAT16_APPLE : GL_LUMINANCE_FLOAT32_APPLE; textureRecord->textureexternalformat = GL_LUMINANCE; // Override for missing floating point texture support: Try to use 16 bit fixed point signed normalized textures [-1.0 ; 1.0] resolved at 15 bits: if ((usefloatformat==1) && !(windowRecord->gfxcaps & kPsychGfxCapFPTex16)) textureRecord->textureinternalformat = GL_LUMINANCE16_SNORM; } if (numMatrixPlanes==2) { rp=(double*) ((size_t) doubleMatrix); ap=(double*) ((size_t) rp + (size_t) iters * sizeof(double)); for(ix=0;ix<iters;ix++){ *(texturePointer_f++)= (GLfloat) *(rp++); *(texturePointer_f++)= (GLfloat) *(ap++); } textureRecord->depth=(usefloatformat==1) ? 32 : 64; textureRecord->textureinternalformat = (usefloatformat==1) ? GL_LUMINANCE_ALPHA_FLOAT16_APPLE : GL_LUMINANCE_ALPHA_FLOAT32_APPLE; textureRecord->textureexternalformat = GL_LUMINANCE_ALPHA; // Override for missing floating point texture support: Try to use 16 bit fixed point signed normalized textures [-1.0 ; 1.0] resolved at 15 bits: if ((usefloatformat==1) && !(windowRecord->gfxcaps & kPsychGfxCapFPTex16)) textureRecord->textureinternalformat = GL_LUMINANCE16_ALPHA16_SNORM; } if (numMatrixPlanes==3) { rp=(double*) ((size_t) doubleMatrix); gp=(double*) ((size_t) rp + (size_t) iters * sizeof(double)); bp=(double*) ((size_t) gp + (size_t) iters * sizeof(double)); for(ix=0;ix<iters;ix++){ *(texturePointer_f++)= (GLfloat) *(rp++); *(texturePointer_f++)= (GLfloat) *(gp++); *(texturePointer_f++)= (GLfloat) *(bp++); } textureRecord->depth=(usefloatformat==1) ? 48 : 96; textureRecord->textureinternalformat = (usefloatformat==1) ? GL_RGB_FLOAT16_APPLE : GL_RGB_FLOAT32_APPLE; textureRecord->textureexternalformat = GL_RGB; // Override for missing floating point texture support: Try to use 16 bit fixed point signed normalized textures [-1.0 ; 1.0] resolved at 15 bits: if ((usefloatformat==1) && !(windowRecord->gfxcaps & kPsychGfxCapFPTex16)) textureRecord->textureinternalformat = GL_RGB16_SNORM; } if (numMatrixPlanes==4) { rp=(double*) ((size_t) doubleMatrix); gp=(double*) ((size_t) rp + (size_t) iters * sizeof(double)); bp=(double*) ((size_t) gp + (size_t) iters * sizeof(double)); ap=(double*) ((size_t) bp + (size_t) iters * sizeof(double)); for(ix=0;ix<iters;ix++){ *(texturePointer_f++)= (GLfloat) *(rp++); *(texturePointer_f++)= (GLfloat) *(gp++); *(texturePointer_f++)= (GLfloat) *(bp++); *(texturePointer_f++)= (GLfloat) *(ap++); } textureRecord->depth=(usefloatformat==1) ? 64 : 128; textureRecord->textureinternalformat = (usefloatformat==1) ? GL_RGBA_FLOAT16_APPLE : GL_RGBA_FLOAT32_APPLE; textureRecord->textureexternalformat = GL_RGBA; // Override for missing floating point texture support: Try to use 16 bit fixed point signed normalized textures [-1.0 ; 1.0] resolved at 15 bits: if ((usefloatformat==1) && !(windowRecord->gfxcaps & kPsychGfxCapFPTex16)) textureRecord->textureinternalformat = GL_RGBA16_SNORM; } // End of HDR conversion code... } else { // Standard LDR texture 8 bpc conversion routines -- Fast path. iters = (size_t) xSize * (size_t) ySize; // Improved implementation: Takes 13 ms on a 800x800 texture... if (isImageMatrixDoubles && numMatrixPlanes==1){ texturePointer_b=(GLubyte*) texturePointer; for(ix=0;ix<iters;ix++){ *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(doubleMatrix++)); } textureRecord->depth=8; } // Improved version: Takes 3 ms on a 800x800 texture... // NB: Implementing memcpy manually by a for-loop takes 10 ms! This is a huge difference. // -> That's because memcpy on MacOS-X is implemented with hand-coded, highly tuned Assembler code for PowerPC. // -> It's always wise to use system-routines if available, instead of coding it by yourself! if (isImageMatrixBytes && numMatrixPlanes==1) { if (texturePointer) { // Need to do a copy. Use optimized memcpy(): memcpy((void*) texturePointer, (void*) byteMatrix, iters); //texturePointer_b=(GLubyte*) texturePointer; //for(ix=0;ix<iters;ix++){ // *(texturePointer_b++) = *(byteMatrix++); //} } else { // Zero-Copy path. Just pass a pointer to our input matrix: texturePointer = (GLuint*) byteMatrix; textureRecord->textureMemory = texturePointer; // Set size to zero, so PsychCreateTexture() does not free() our // input buffer: textureRecord->textureMemorySizeBytes = 0; } textureRecord->depth=8; } // New version: Takes 33 ms on a 800x800 texture... if (isImageMatrixDoubles && numMatrixPlanes==2){ texturePointer_b=(GLubyte*) texturePointer; rp=(double*) ((size_t) doubleMatrix); ap=(double*) ((size_t) rp + (size_t) iters * sizeof(double)); for(ix=0;ix<iters;ix++){ *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(rp++)); *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(ap++)); } textureRecord->depth=16; } // New version: Takes 20 ms on a 800x800 texture... if (isImageMatrixBytes && numMatrixPlanes==2){ texturePointer_b=(GLubyte*) texturePointer; rpb=(GLubyte*) ((size_t) byteMatrix); apb=(GLubyte*) ((size_t) rpb + (size_t) iters); for(ix=0;ix<iters;ix++){ *(texturePointer_b++)= *(rpb++); *(texturePointer_b++)= *(apb++); } textureRecord->depth=16; } // Improved version: Takes 43 ms on a 800x800 texture... if (isImageMatrixDoubles && numMatrixPlanes==3){ texturePointer_b=(GLubyte*) texturePointer; rp=(double*) ((size_t) doubleMatrix); gp=(double*) ((size_t) rp + (size_t) iters * sizeof(double)); bp=(double*) ((size_t) gp + (size_t) iters * sizeof(double)); for(ix=0;ix<iters;ix++){ *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(rp++)); *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(gp++)); *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(bp++)); } textureRecord->depth=24; } // Improved version: Takes 25 ms on a 800x800 texture... if (isImageMatrixBytes && numMatrixPlanes==3){ texturePointer_b=(GLubyte*) texturePointer; rpb=(GLubyte*) ((size_t) byteMatrix); gpb=(GLubyte*) ((size_t) rpb + (size_t) iters); bpb=(GLubyte*) ((size_t) gpb + (size_t) iters); for(ix=0;ix<iters;ix++){ *(texturePointer_b++)= *(rpb++); *(texturePointer_b++)= *(gpb++); *(texturePointer_b++)= *(bpb++); } textureRecord->depth=24; } // Improved version: Takes 55 ms on a 800x800 texture... if (isImageMatrixDoubles && numMatrixPlanes==4){ texturePointer_b=(GLubyte*) texturePointer; rp=(double*) ((size_t) doubleMatrix); gp=(double*) ((size_t) rp + (size_t) iters * sizeof(double)); bp=(double*) ((size_t) gp + (size_t) iters * sizeof(double)); ap=(double*) ((size_t) bp + (size_t) iters * sizeof(double)); if (bigendian) { // Code for big-endian machines like PowerPC: for(ix=0;ix<iters;ix++){ *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(ap++)); *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(rp++)); *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(gp++)); *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(bp++)); } } else { // Code for little-endian machines like Intel Pentium: for(ix=0;ix<iters;ix++){ *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(bp++)); *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(gp++)); *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(rp++)); *(texturePointer_b++)= (GLubyte) (offsetd + scaled * *(ap++)); } } textureRecord->depth=32; } // Improved version: Takes 33 ms on a 800x800 texture... if (isImageMatrixBytes && numMatrixPlanes==4){ texturePointer_b=(GLubyte*) texturePointer; rpb=(GLubyte*) ((size_t) byteMatrix); gpb=(GLubyte*) ((size_t) rpb + (size_t) iters); bpb=(GLubyte*) ((size_t) gpb + (size_t) iters); apb=(GLubyte*) ((size_t) bpb + (size_t) iters); if (bigendian) { // Code for big-endian machines like PowerPC: for(ix=0;ix<iters;ix++){ *(texturePointer_b++)= *(apb++); *(texturePointer_b++)= *(rpb++); *(texturePointer_b++)= *(gpb++); *(texturePointer_b++)= *(bpb++); } } else { // Code for little-endian machines like Intel Pentium: for(ix=0;ix<iters;ix++){ *(texturePointer_b++)= *(bpb++); *(texturePointer_b++)= *(gpb++); *(texturePointer_b++)= *(rpb++); *(texturePointer_b++)= *(apb++); } } textureRecord->depth=32; } } // End of 8 bpc texture conversion code (fast-path for LDR textures) // Override for missing floating point texture support? if ((usefloatformat==1) && !(windowRecord->gfxcaps & kPsychGfxCapFPTex16)) { // Override enabled. Instead of a 16bpc float texture with 11 bit linear precision in the // range [-1.0 ; 1.0], we use a 16 bit signed normalized texture with a normalized value // range of [-1.0; 1.0], encoded with 1 bit sign and 15 bit magnitude. These textures have // an effective linear precision of 15 bits - better than 16 bpc float - but they are restricted // to a value range of [-1.0 ; 1.0], as opposed to 16 bpc float textures. Tell user about this // replacement at high verbosity levels: if (PsychPrefStateGet_Verbosity() > 4) printf("PTB-INFO:MakeTexture: Code requested 16 bpc float texture, but this is unsupported. Trying to use 16 bit snorm texture instead.\n"); // Signed normalized textures supported? Otherwise we bail... if (!(windowRecord->gfxcaps & kPsychGfxCapSNTex16)) { printf("PTB-ERROR:MakeTexture: Code requested 16 bpc floating point texture, but this is unsupported by this graphics card.\n"); printf("PTB-ERROR:MakeTexture: Tried to use 16 bit snorm texture instead, but failed as this is unsupported as well.\n"); PsychErrorExitMsg(PsychError_user, "Creation of 15 bit linear precision signed normalized texture failed. Not supported by your graphics hardware!"); } // Check value range of pixels. This will not work for out of [-1; 1] range values. texturePointer_f=(GLfloat*) texturePointer; iters = iters * (size_t) numMatrixPlanes; for (ix=0; ix<iters; ix++, texturePointer_f++) { if(fabs((double) *texturePointer_f) > 1.0) { // Game over! printf("PTB-ERROR:MakeTexture: Code requested 16 bpc floating point texture, but this is unsupported by this graphics card.\n"); printf("PTB-ERROR:MakeTexture: Tried to use 16 bit snorm texture instead, but failed because some pixels are outside the\n"); printf("PTB-ERROR:MakeTexture: representable range -1.0 to 1.0 for this texture type. Change your code or update your graphics hardware.\n"); PsychErrorExitMsg(PsychError_user, "Creation of 15 bit linear precision signed normalized texture failed due to out of [-1 ; +1] range pixel values!"); } } } // This is a special workaround for bugs in FLOAT16 texture creation on Mac OS/X 10.4.x and 10.5.x. // The OpenGL fails to properly flush very small values (< 1e-9) to zero when creating a FLOAT16 // type texture. Instead it seems to initialize with trash data, corrupting the texture. // Therefore, if FLOAT16 texture creation is requested, we loop over the whole input buffer and // set all values with magnitude smaller than 1e-9 to zero. Better safe than sorry... if ((usefloatformat==1) && (windowRecord->gfxcaps & kPsychGfxCapFPTex16)) { texturePointer_f=(GLfloat*) texturePointer; iters = iters * (size_t) numMatrixPlanes; for(ix=0; ix<iters; ix++, texturePointer_f++) if(fabs((double) *texturePointer_f) < 1e-9) { *texturePointer_f = 0.0; } } // On OpenGL-ES, 32 bpc floating point textures are selected via the GL_FLOAT type specifier, and // internal format must be == external format == not defining resolution. External format is already // properly set by common desktop/es HDR setup code, as is type spec, so we just need to make sure that // internal format is consistent with external one: // MK NOPE: Seems i misread the spec and float textures are treated identical to desktop OpenGL, so this // is probably not needed, at least not on NVidia. Leave it here in case this is a NVidia peculiarity and // my old interpretation was actually correct for any other hardware. // if ((usefloatformat == 2) && PsychIsGLES(windowRecord)) textureRecord->textureinternalformat = textureRecord->textureexternalformat; // The memory buffer now contains our texture data in a format ready to submit to OpenGL. // Assign parent window and copy its inheritable properties: PsychAssignParentWindow(textureRecord, windowRecord); // Texture orientation is zero aka transposed aka non-renderswapped. textureRecord->textureOrientation = ((assume_texorientation != 2) && (assume_texorientation != 3)) ? 0 : 2; // This is our best guess about the number of image channels: textureRecord->nrchannels = numMatrixPlanes; if (planar_storage) { // Setup special rect to fake PsychCreateTexture() into creating a luminance // texture numMatrixPlanes times the height (in rows) of the texture, to store the // numMatrixPlanes layers concatenated to each other. if (textureRecord->textureOrientation == 0) { // Normal case: Transposed storage. PsychMakeRect(&(textureRecord->rect[0]), 0, 0, xSize * numMatrixPlanes, ySize); } else { // Special case: Non-transposed or isotropic storage: PsychMakeRect(&(textureRecord->rect[0]), 0, 0, xSize, ySize * numMatrixPlanes); } // Create planar texture: PsychCreateTexture(textureRecord); // Restore rect and clientrect of texture to effective size: PsychMakeRect(&(textureRecord->rect[0]), 0, 0, xSize, ySize); PsychCopyRect(textureRecord->clientrect, textureRecord->rect); textureRecord->specialflags = kPsychPlanarTexture; } else { // Let's create and bind a new texture object and fill it with our new texture data. PsychCreateTexture(textureRecord); // Assign GLSL filter-/lookup-shaders if needed: PsychAssignHighPrecisionTextureShaders(textureRecord, windowRecord, usefloatformat, (usepoweroftwo & 2) ? 1 : 0); } // User specified override shader for this texture provided? This is useful for // basic image processing and procedural texture shading: if (textureShader!=0) { // Assign provided shader as filtershader to this texture: We negate it so // that the texture blitter routines know this is a custom shader, not our // built in filter shader: textureRecord->textureFilterShader = -1 * textureShader; } // Texture ready. Mark it valid and return handle to userspace: PsychSetWindowRecordValid(textureRecord); PsychCopyOutDoubleArg(1, FALSE, textureRecord->windowIndex); // Swapping the texture to upright orientation requested? if (assume_texorientation == 1) { // Transform sourceRecord source texture into a normalized, upright texture if it isn't already in // that format. We require this standard orientation for simplified shader design. PsychSetShader(windowRecord, 0); PsychNormalizeTextureOrientation(textureRecord); } // Shall the texture be finally declared "normally oriented"? // This is either due to explicit renderswapping if assume_textureorientation == 1, // or because it was already pretransposed in Matlab/Octave if assume_textureorientation == 2, // or because user space tells us the texture is isotropic if assume_textureorientation == 3. if (assume_texorientation > 0) { // Yes. Label it as such: textureRecord->textureOrientation = 2; } // specialFlags setting 8? Disable auto-mipmap generation: if (usepoweroftwo & 0x8) textureRecord->specialflags |= kPsychDontAutoGenMipMaps; // A specialFlags setting of 32? Protect texture against deletion via Screen('Close') without providing a explicit handle: if (usepoweroftwo & 32) textureRecord->specialflags |= kPsychDontDeleteOnClose; if (PsychPrefStateGet_DebugMakeTexture()) //MARK #4 StoreNowTime(); return(PsychError_none); }
GLenum PsychGLFloatType(PsychWindowRecordType *windowRecord) { // On OpenGL-ES we only have GL_FLOAT data type, not GL_DOUBLE, so we need // to convert all double input data into float data for the calling routine: return((PsychIsGLES(windowRecord)) ? GL_FLOAT : GL_DOUBLE); }