void PsychGLVertex4f(PsychWindowRecordType *windowRecord, float x, float y, float z, float w) { // Classic OpenGL? Use glEnd() and be done: if (PsychIsGLClassic(windowRecord)) { glVertex4f(x,y,z,w); return; } // Make sure we don't overflow our internal static vertex array bufer: if (gl_buffer_index > PSYCH_MAX_IMMSIM_VERTEX_COMPONENTS - 12) { printf("PTB-ERROR: Overflow of internal immediate mode rendering buffer! Limit of %i components reached.\n", PSYCH_MAX_IMMSIM_VERTEX_COMPONENTS); printf("PTB-ERROR: You must reduce the workload, or your stimulus image will be damaged. Likely culprit is Screen('Fill/FrameOval') with too high level of detail!\n\n"); return; } // Store (x,y,z,w) vertex pos first: gl_buffer[gl_buffer_index++] = x; gl_buffer[gl_buffer_index++] = y; gl_buffer[gl_buffer_index++] = z; gl_buffer[gl_buffer_index++] = w; // Then current color (r,g,b,a): gl_buffer[gl_buffer_index++] = (float) currentColor[0]; gl_buffer[gl_buffer_index++] = (float) currentColor[1]; gl_buffer[gl_buffer_index++] = (float) currentColor[2]; gl_buffer[gl_buffer_index++] = (float) currentColor[3]; // Then current texture coordinates (s,t,u,v): gl_buffer[gl_buffer_index++] = currentTexCoord[0]; gl_buffer[gl_buffer_index++] = currentTexCoord[1]; gl_buffer[gl_buffer_index++] = currentTexCoord[2]; gl_buffer[gl_buffer_index++] = currentTexCoord[3]; return; }
/* PsychSetGLColor() Accept a Psych color structure and a depth value and call the appropriate variant of glColor. */ void PsychSetGLColor(PsychColorType *color, PsychWindowRecordType *windowRecord) { int numVals; numVals=PsychConvertColorToDoubleVector(color, windowRecord, (GLdouble*) &(windowRecord->currentColor)); if(numVals < 3 || numVals > 4) PsychErrorExitMsg(PsychError_internal, "Palette mode not yet implemented or illegal color specifier."); // Set the color in GL: if (windowRecord->defaultDrawShader) { // Drawshader color submission: HDRglColor4dv(windowRecord->currentColor); } else { // Fixed function pipe: if (PsychIsGLClassic(windowRecord)) { // OpenGL-1/2: glColor4dv(windowRecord->currentColor); } else { // OpenGL-ES 1.x: glColor4f() is only available function. glColor4f((float) windowRecord->currentColor[0], (float) windowRecord->currentColor[1], (float) windowRecord->currentColor[2], (float) windowRecord->currentColor[3]); PsychGLColor4f(windowRecord, (float) windowRecord->currentColor[0], (float) windowRecord->currentColor[1], (float) windowRecord->currentColor[2], (float) windowRecord->currentColor[3]); } } }
void PsychGLEnd(PsychWindowRecordType *windowRecord) { // Classic OpenGL? Use glEnd() and be done: if (PsychIsGLClassic(windowRecord)) { glEnd(); return; } // OpenGL 3+ or OpenGL-ES. Need to emulate immediate mode stuff: // Work to do? if (gl_buffer_index > 0) { // Yes: Submit draw call: glVertexPointer(4, GL_FLOAT, 4 * 3 * sizeof(float), &gl_buffer[0 * 4]); glColorPointer(4, GL_FLOAT, 4 * 3 * sizeof(float), &gl_buffer[1 * 4]); glTexCoordPointer(4, GL_FLOAT, 4 * 3 * sizeof(float), &gl_buffer[2 * 4]); glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); glDrawArrays(gl_buffer_primitivetype, 0, gl_buffer_index / (4 * 3)); glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); } // Reset primitive for this Begin->End block: gl_buffer_primitivetype = (GLenum) 0xffff; gl_buffer_index = 0; return; }
/* Emit a single pixel in top-left corner of window and wait for its rendering * to complete. Our classic trick to wait for double-buffer swap completion on * systems where we don't have better system-provided timestamping and syncing * methods. This needs different implementations on classic OpenGL vs. non- * immediate mode OpenGL. * * A 'flushOnly' flag of TRUE will only flush, not wait. * */ void PsychWaitPixelSyncToken(PsychWindowRecordType *windowRecord, psych_bool flushOnly) { // Classic desktop OpenGL in use? if (PsychIsGLClassic(windowRecord)) { // Yes. Use our classic fixed-function immediate mode method: glBegin(GL_POINTS); glColor4f(0, 0, 0, 0); glVertex2i(10, 10); glEnd(); } else { // No. Avoid immediate mode functions, they won't work: GLfloat glverts[2] = { 10, 10 }; glVertexPointer(2, GL_FLOAT, 0, glverts); glEnableClientState(GL_VERTEX_ARRAY); glDrawArrays(GL_POINTS, 0, 1); glDisableClientState(GL_VERTEX_ARRAY); } // flushOnly flag - Don't wait for write to happen, just flush it: if (!flushOnly) { // Wait for write completion - Used for sync and timestamping: glFinish(); } else { // Only flush: glFlush(); } if (flushOnly && (PsychPrefStateGet_Verbosity() > 15)) printf("PTB-DEBUG: PixelSyncToken write + glFlush().\n"); }
void PsychGLTexCoord4f(PsychWindowRecordType *windowRecord, float s, float t, float u, float v) { // Classic OpenGL? Use glEnd() and be done: if (PsychIsGLClassic(windowRecord)) { glTexCoord4f(s, t, u, v); return; } currentTexCoord[0] = s; currentTexCoord[1] = t; currentTexCoord[2] = u; currentTexCoord[3] = v; }
void PsychGLRectd(PsychWindowRecordType *windowRecord, double x1, double y1, double x2, double y2) { // Classic OpenGL? Use glRectd() and be done: if (PsychIsGLClassic(windowRecord)) { glRectd(x1, y1, x2, y2); return; } // Want to emulate a GL_QUAD draw: We do it as a triangle strip: GLBEGIN(GL_TRIANGLE_STRIP); GLVERTEX2d(x1, y1); GLVERTEX2d(x2, y1); GLVERTEX2d(x1, y2); GLVERTEX2d(x2, y2); GLEND(); }
void PsychGLBegin(PsychWindowRecordType *windowRecord, GLenum primitive) { // Classic OpenGL? Use glBegin() and be done: if (PsychIsGLClassic(windowRecord)) { glBegin(primitive); return; } // OpenGL 3+ or OpenGL-ES. Need to emulate immediate mode stuff: if (gl_buffer_index > 0) PsychErrorExitMsg(PsychError_internal, "PsychGLBegin() called while already inside PsychGLBegin() block!"); // Assign primitive for this Begin->End block: gl_buffer_primitivetype = primitive; return; }
PsychError SCREENFrameOval(void) { PsychRectType rect; double numSlices, outerRadius, xScale, yScale, xTranslate, yTranslate, rectY, rectX, penWidth, penHeight, penSize, innerRadius; PsychWindowRecordType *windowRecord; psych_bool isArgThere, isclassic; double *xy, *colors; unsigned char *bytecolors; double* penSizes; int numRects, i, nc, mc, nrsize; GLUquadricObj *diskQuadric; //all sub functions should have these two lines PsychPushHelp(useString, synopsisString,seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);} //check for superfluous arguments PsychErrorExit(PsychCapNumInputArgs(6)); //The maximum number of inputs PsychErrorExit(PsychCapNumOutputArgs(0)); //The maximum number of outputs //get the window record from the window record argument and get info from the window record PsychAllocInWindowRecordArg(kPsychUseDefaultArgPosition, TRUE, &windowRecord); // Query, allocate and copy in all vectors... numRects = 4; nrsize = 0; colors = NULL; bytecolors = NULL; mc = nc = 0; // The negative position -3 means: xy coords are expected at position 3, but they are optional. // NULL means - don't want a size's vector. PsychPrepareRenderBatch(windowRecord, -3, &numRects, &xy, 2, &nc, &mc, &colors, &bytecolors, 4, &nrsize, &penSizes, FALSE); isclassic = PsychIsGLClassic(windowRecord); // Only up to one rect provided? if (numRects <= 1) { // Get the oval and draw it: PsychCopyRect(rect, windowRecord->clientrect); isArgThere=PsychCopyInRectArg(kPsychUseDefaultArgPosition, FALSE, rect); if (isArgThere && IsPsychRectEmpty(rect)) return(PsychError_none); numRects = 1; // Get the pen width and height arguments penWidth=1; penHeight=1; PsychCopyInDoubleArg(4, FALSE, &penWidth); PsychCopyInDoubleArg(5, FALSE, &penHeight); penSize = (penWidth > penHeight) ? penWidth : penHeight; } else { // Multiple ovals provided. Set up the first one: PsychCopyRect(rect, &xy[0]); penSize = penSizes[0]; } // Create quadric object: if (isclassic) diskQuadric = gluNewQuadric(); // Draw all ovals (one or multiple): for (i=0; i < numRects;) { // Per oval color provided? If so then set it up. If only one common color // was provided then PsychPrepareRenderBatch() has already set it up. if (nc>1) { // Yes. Set color for this specific item: PsychSetArrayColor(windowRecord, i, mc, colors, bytecolors); } // Per oval penSize provided? If so, set it up. Otherwise keep at default size // common for all ovals, set by code outside loop: if (nrsize > 1) penSize = penSizes[i]; // Compute drawing parameters for ellipse: if (!IsPsychRectEmpty(rect)) { //The glu disk object location and size with a center point and a radius, //whereas FrameOval accepts a bounding rect. Converting from one set of parameters //to the other we should careful what we do for rects size of even number of pixels in length. PsychGetCenterFromRectAbsolute(rect, &xTranslate, &yTranslate); rectY=PsychGetHeightFromRect(rect); rectX=PsychGetWidthFromRect(rect); if(rectX == rectY){ xScale=1; yScale=1; outerRadius=rectX/2; }else if(rectX > rectY){ xScale=1; yScale=rectY/rectX; outerRadius=rectX/2; }else { yScale=1; xScale=rectX/rectY; outerRadius=rectY/2; } numSlices = 3.14159265358979323846 * 2 * outerRadius; innerRadius = outerRadius - penSize; innerRadius = (innerRadius < 0) ? 0 : innerRadius; if (isclassic) { // Draw: Set up position, scale and size via matrix transform: glPushMatrix(); glTranslated(xTranslate, yTranslate, 0); glScaled(xScale, yScale, 1); // Compute disk quadric for given params: This is awfully slow and would // benefit a lot from shader magic on modern GPUs: gluDisk(diskQuadric, innerRadius, outerRadius, (int) numSlices, 1); glPopMatrix(); } else { PsychDrawDisc(windowRecord, (float) xTranslate, (float) yTranslate, (float) innerRadius, (float) outerRadius, (int) numSlices, (float) xScale, (float) yScale, 0, 360); } } // Done with this one. Set up the next one, if any... i++; if (i < numRects) { PsychCopyRect(rect, &xy[i*4]); } // Next oval. } // Release quadric object: if (isclassic) gluDeleteQuadric(diskQuadric); // Mark end of drawing op. This is needed for single buffered drawing: PsychFlushGL(windowRecord); // All Psychfunctions require this. return(PsychError_none); }
PsychError SCREENFillPoly(void) { PsychColorType color; PsychWindowRecordType *windowRecord; double whiteValue; int i, mSize, nSize, pSize; psych_bool isArgThere; double *pointList; double isConvex; int j,k; int flag; double z; combinerCacheSlot = 0; combinerCacheSize = 0; combinerCache = NULL; //all sub functions should have these two lines PsychPushHelp(useString, synopsisString,seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; //check for superfluous arguments PsychErrorExit(PsychCapNumInputArgs(4)); //The maximum number of inputs PsychErrorExit(PsychCapNumOutputArgs(0)); //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 the color argument or use the default, then coerce to the form determened by the window depth. isArgThere=PsychCopyInColorArg(2, FALSE, &color); if(!isArgThere){ whiteValue=PsychGetWhiteValueFromWindow(windowRecord); PsychLoadColorStruct(&color, kPsychIndexColor, whiteValue ); //index mode will coerce to any other. } PsychCoerceColorMode( &color); //get the list of pairs and validate. PsychAllocInDoubleMatArg(3, kPsychArgRequired, &mSize, &nSize, &pSize, &pointList); if(nSize!=2) PsychErrorExitMsg(PsychError_user, "Width of pointList must be 2"); if(mSize<3) PsychErrorExitMsg(PsychError_user, "Polygons must consist of at least 3 points; M dimension of pointList was < 3!"); if(pSize>1) PsychErrorExitMsg(PsychError_user, "pointList must be a 2D matrix, not a 3D matrix!"); isConvex = -1; PsychCopyInDoubleArg(4, kPsychArgOptional, &isConvex); // On non-OpenGL1/2 we always force isConvex to zero, so the GLU tesselator is // always used. This because the tesselator only emits GL_TRIANGLES and GL_TRIANGLE_STRIP // and GL_TRIANGLE_FANS primitives which are supported on all current OpenGL API's, whereas // or "classic" fast-path needs GL_POLYGONS, which are only supported on classic OpenGL1/2: if (!PsychIsGLClassic(windowRecord)) isConvex = 0; // Enable this windowRecords framebuffer as current drawingtarget: PsychSetDrawingTarget(windowRecord); // Set default drawshader: PsychSetShader(windowRecord, -1); PsychUpdateAlphaBlendingFactorLazily(windowRecord); PsychSetGLColor(&color, windowRecord); ///////// Test for convexity //////// // This algorithm checks, if the polygon is definitely convex, or not. // We take the slow-path, if polygon is non-convex or if we can't prove // that it is convex. // // Algorithm adapted from: http://astronomy.swin.edu.au/~pbourke/geometry/clockwise/ // Which was written by Paul Bourke, 1998. // // -> This webpage explains the mathematical principle behind the test and provides // a C-Source file which has been adapted for use here. // if (isConvex == -1) { flag = 0; for (i=0; i < mSize; i++) { j = (i + 1) % mSize; k = (i + 2) % mSize; z = (pointList[j] - pointList[i]) * (pointList[k+mSize] - pointList[j+mSize]); z -= (pointList[j+mSize] - pointList[i+mSize]) * (pointList[k] - pointList[j]); if (z < 0) { flag |= 1; } else if (z > 0) { flag |= 2; } if (flag == 3) { // This is definitely a CONCAVE polygon --> not Convex --> Take slow but safe path. break; } } if (flag!=0 && flag!=3) { // This is a convex polygon --> Take fast path. isConvex = 1; } else { // This is a complex polygon --> can't determine if it is convex or not --> Take slow but safe path. isConvex = 0; } } ////// Switch between fast path and slow path, depending on convexity of polygon: if (isConvex > 0) { // Convex, non-self-intersecting polygon - Take the fast-path: glBegin(GL_POLYGON); for(i=0;i<mSize;i++) glVertex2d((GLdouble)pointList[i], (GLdouble)pointList[i+mSize]); glEnd(); } else { // Possibly concave and/or self-intersecting polygon - At least we couldn't prove it is convex. // Take the slow, but safe, path using GLU-Tesselators to break it up into a couple of convex, simple // polygons: // Create and initialize a new GLU-Tesselator object, if needed: if (NULL == tess) { // Create tesselator: tess = gluNewTess(); if (NULL == tess) PsychErrorExitMsg(PsychError_outofMemory, "Out of memory condition in Screen('FillPoly')! Not enough space."); // Assign our callback-functions: gluTessCallback(tess, GLU_TESS_BEGIN, GLUTESSCBCASTER PsychtcbBegin); gluTessCallback(tess, GLU_TESS_VERTEX, GLUTESSCBCASTER PsychtcbVertex); gluTessCallback(tess, GLU_TESS_END, GLUTESSCBCASTER PsychtcbEnd); gluTessCallback(tess, GLU_TESS_COMBINE, GLUTESSCBCASTER PsychtcbCombine); // Define all tesselated polygons to lie in the x-y plane: gluTessNormal(tess, 0, 0, 1); } // We need to hold the values in a temporary array: if (tempvsize < mSize) { tempvsize = ((mSize / 1000) + 1) * 1000; tempv = (double*) realloc((void*) tempv, sizeof(double) * 3 * tempvsize); if (NULL == tempv) PsychErrorExitMsg(PsychError_outofMemory, "Out of memory condition in Screen('FillPoly')! Not enough space."); } // Now submit our Polygon for tesselation: gluTessBeginPolygon(tess, NULL); gluTessBeginContour(tess); for(i=0; i < mSize; i++) { tempv[i*3]=(GLdouble) pointList[i]; tempv[i*3+1]=(GLdouble) pointList[i+mSize]; tempv[i*3+2]=0; gluTessVertex(tess, (GLdouble*) &(tempv[i*3]), (void*) &(tempv[i*3])); } // Process, finalize and render it by calling our callback-functions: gluTessEndContour(tess); gluTessEndPolygon (tess); // Done with drawing the filled polygon. (Slow-Path) } // Mark end of drawing op. This is needed for single buffered drawing: PsychFlushGL(windowRecord); // printf("CombinerCalls %i out of %i allocated.\n", combinerCacheSlot, combinerCacheSize); return(PsychError_none); }
PsychError SCREENFillOval(void) { PsychRectType rect; double numSlices, radius, xScale, yScale, xTranslate, yTranslate, rectY, rectX; PsychWindowRecordType *windowRecord; psych_bool isArgThere, isclassic; double *xy, *colors; unsigned char *bytecolors; int numRects, i, nc, mc, nrsize; GLUquadricObj *diskQuadric; double perfectUpToMaxDiameter; static double perfectUpToMaxDiameterOld = 0; //all sub functions should have these two lines PsychPushHelp(useString, synopsisString,seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);} //check for superfluous arguments PsychErrorExit(PsychCapNumInputArgs(4)); //The maximum number of inputs PsychErrorExit(PsychCapNumOutputArgs(0)); //The maximum number of outputs //get the window record from the window record argument and get info from the window record PsychAllocInWindowRecordArg(kPsychUseDefaultArgPosition, TRUE, &windowRecord); isclassic = PsychIsGLClassic(windowRecord); perfectUpToMaxDiameter = PsychGetWidthFromRect(windowRecord->clientrect); if (PsychGetHeightFromRect(windowRecord->clientrect) < perfectUpToMaxDiameter) perfectUpToMaxDiameter = PsychGetHeightFromRect(windowRecord->clientrect); PsychCopyInDoubleArg(4, kPsychArgOptional, &perfectUpToMaxDiameter); // Compute number of subdivisions (slices) to provide a perfect oval, i.e., one subdivision for each // distance unit on the circumference of the oval. numSlices = 3.14159265358979323846 * perfectUpToMaxDiameter; if ((perfectUpToMaxDiameter != perfectUpToMaxDiameterOld) || (windowRecord->fillOvalDisplayList == 0)) { perfectUpToMaxDiameterOld = perfectUpToMaxDiameter; // Destroy old display list so it gets rebuilt with the new numSlices setting: if (isclassic && (windowRecord->fillOvalDisplayList != 0)) { glDeleteLists(windowRecord->fillOvalDisplayList, 1); windowRecord->fillOvalDisplayList = 0; } } // Already cached display list for filled ovals for this windowRecord available? if (isclassic && (windowRecord->fillOvalDisplayList == 0)) { // Nope. Create our prototypical filled oval: // Generate a filled disk of that radius and subdivision and store it in a display list: diskQuadric=gluNewQuadric(); windowRecord->fillOvalDisplayList = glGenLists(1); glNewList(windowRecord->fillOvalDisplayList, GL_COMPILE); gluDisk(diskQuadric, 0, 1, (int) numSlices, 1); glEndList(); gluDeleteQuadric(diskQuadric); // Display list ready for use in this and all future drawing calls for this windowRecord. } // Query, allocate and copy in all vectors... numRects = 4; nrsize = 0; colors = NULL; bytecolors = NULL; mc = nc = 0; // The negative position -3 means: xy coords are expected at position 3, but they are optional. // NULL means - don't want a size's vector. PsychPrepareRenderBatch(windowRecord, -3, &numRects, &xy, 2, &nc, &mc, &colors, &bytecolors, 0, &nrsize, NULL, FALSE); // Only up to one rect provided? if (numRects <= 1) { // Get the oval and draw it: PsychCopyRect(rect, windowRecord->clientrect); isArgThere=PsychCopyInRectArg(kPsychUseDefaultArgPosition, FALSE, rect); if (isArgThere && IsPsychRectEmpty(rect)) return(PsychError_none); numRects = 1; } else { // Multiple ovals provided. Set up the first one: PsychCopyRect(rect, &xy[0]); } // Draw all ovals (one or multiple): for (i = 0; i < numRects;) { // Per oval color provided? If so then set it up. If only one common color // was provided then PsychPrepareRenderBatch() has already set it up. if (nc>1) { // Yes. Set color for this specific item: PsychSetArrayColor(windowRecord, i, mc, colors, bytecolors); } // Compute drawing parameters for ellipse: if (!IsPsychRectEmpty(rect)) { //The glu disk object location and size with a center point and a radius, //whereas FillOval accepts a bounding rect. Converting from one set of parameters //to the other we should careful what we do for rects size of even number of pixels in length. PsychGetCenterFromRectAbsolute(rect, &xTranslate, &yTranslate); rectY=PsychGetHeightFromRect(rect); rectX=PsychGetWidthFromRect(rect); if(rectX == rectY){ xScale=1; yScale=1; radius=rectX/2; } else if(rectX > rectY){ xScale=1; yScale=rectY/rectX; radius=rectX/2; } else { yScale=1; xScale=rectX/rectY; radius=rectY/2; } if (isclassic) { // Draw: Set up position, scale and size via matrix transform: glPushMatrix(); glTranslatef((float) xTranslate, (float) yTranslate, (float) 0); glScalef((float) (xScale * radius), (float) (yScale * radius), (float) 1); // Draw cached disk object (stored in display list): glCallList(windowRecord->fillOvalDisplayList); glPopMatrix(); } else { PsychDrawDisc(windowRecord, (float) xTranslate, (float) yTranslate, (float) 0, (float) radius, (int) numSlices, (float) xScale, (float) yScale, 0, 360); } } // Done with this one. Set up the next one, if any... i++; if (i < numRects) PsychCopyRect(rect, &xy[i*4]); // Next oval. } // Mark end of drawing op. This is needed for single buffered drawing: PsychFlushGL(windowRecord); //All psychfunctions require this. return(PsychError_none); }
PsychError SCREENDrawTexture(void) { static char synopsisString[] = "Draw the texture specified via 'texturePointer' into the target window specified via 'windowPointer'. " "In the the OS X Psychtoolbox textures replace offscreen windows for fast drawing of images during animation." "'sourceRect' specifies a rectangular subpart of the texture to be drawn (Defaults to full texture). " "'destinationRect' defines the rectangular subpart of the window where the texture should be drawn. This defaults" "to centered on the screen. " "'rotationAngle' Specifies a rotation angle in degree for rotated drawing of the texture (Defaults to 0 deg. = upright). " "'filterMode' How to compute the pixel color values when the texture is drawn magnified, minified or drawn shifted, e.g., " "if sourceRect and destinationRect do not have the same size or if sourceRect specifies fractional pixel values. 0 = Nearest " "neighbour filtering, 1 = Bilinear filtering - this is the default. Values 2 or 3 select use of OpenGL mip-mapping for improved " "quality: 2 = Bilinear filtering for nearest mipmap level, 3 = Trilinear filtering across mipmap levels, 4 = Nearest neighbour " "filtering for nearest mipmap level, 5 = nearest neighbour filtering with linear interpolation between mipmap levels. Mipmap filtering is " "only supported for GL_TEXTURE_2D textures (see description of 'specialFlags' flag 1 below). A negative filterMode value will " "also use mip-mapping for fast drawing of blurred textures if the GL_TEXTURE_2D format is used: Mip-maps are essentially image " "resolution pyramids, the filterMode value selects a specific layer in that pyramid. A value of -1 draws the highest resolution " "layer, a value of -2 draws a half-resolution layer, a value of -3 draws a quarter resolution layer and so on. Each layer has " "half the resolution of the preceeding layer. This allows for very fast drawing of blurred or low-pass filtered images, e.g., for " "gaze-contingent displays. However, the filter function for downsampling is system dependent and may vary across graphics cards, " "although a box-filter is the most common type. If you need a well defined filter function, use a custom written GLSL shader " "instead, so you have full control over the mathematical properties of the downsampling function. This would incur a " "performance penalty.\n" "'globalAlpha' A global alpha transparency value to apply " "to the whole texture for blending. Range is 0 = fully transparent to 1 = fully opaque, defaults to one. If both, an alpha-channel " "and globalAlpha are provided, then the final alpha is the product of both values. 'modulateColor', if provided, overrides the " "'globalAlpha' value. If 'modulateColor' is specified, the 'globalAlpha' value will be ignored. 'modulateColor' will be a global " "color that gets applied to the texture as a whole, i.e., it modulates each color channel. E.g., modulateColor = [128 255 0] would " "leave the green- and alpha-channel untouched, but it would multiply the blue channel with 0 - set it to zero blue intensity, and " "it would multiply each texel in the red channel by 128/255 - reduce its intensity to 50%. The most interesting application of " "'modulateColor' is drawing of arbitrary complex shapes of selectable color: Simply generate an all-white luminance texture of " "arbitrary shape, possibly with alpha channel, then draw it with 'modulateColor' set to the wanted color and global alpha value.\n" "'textureShader' (optional): If you provide a valid handle of a GLSL shader, this shader will be applied to the texture during " "drawing. If the texture already has a shader assigned (via Screen('MakeTexture') or automatically by PTB for some reason), then " "the shader provided here as 'textureShader' will silently override the shader assigned earlier. Application of shaders this way " "is mostly useful for application of simple single-pass image processing operations to a texture, e.g., a simple blur or a " "deinterlacing operation for a video texture. If you intend to use this texture multiple times or if you need more complex image " "processing, e.g., multi-pass operations, better use the Screen('TransformTexture') command. It allows for complex operations to " "be applied and is more flexible.\n" "'specialFlags' optional argument: Allows to pass a couple of special flags to influence the drawing. The flags can be combined " "by mor() ing them together. A value of kPsychUseTextureMatrixForRotation will use a different mode of operation for drawing of " "rotated textures, where the drawn 'dstRect' texture rectangle is always upright, but texels are retrieved at rotated positions, " "as if the 'srcRect' rectangle would be rotated. If you set a value of kPsychDontDoRotation then the rotation angle will not be " "used to rotate the texture. Instead it will be passed to a bount texture shader (if any), which is free to interpret the " "'rotationAngle' parameters is it wants - e.g., to implement custom texture rotation." "\n\n" "'auxParameters' optional argument: If this is set as a vector with at least 4 components, and a multiple of four components, " "then these values are passed to a shader (if any is bound) as 'auxParameter0....n'. The current implementation supports at " "most 32 values per draw call. This is mostly useful when drawing procedural textures if one needs to pass more additional " "parameters to define the texture than can fit into other parameter fields. See 'help ProceduralShadingAPI' for more info. " "\n\n" "If you want to draw many textures to the same onscreen- or offscreen window, use the function Screen('DrawTextures'). " "It accepts the same arguments as this function, but is optimized to draw many textures in one call."; // If you change useString then also change the corresponding synopsis string in ScreenSynopsis.c static char useString[] = "Screen('DrawTexture', windowPointer, texturePointer [,sourceRect] [,destinationRect] [,rotationAngle] [, filterMode] [, globalAlpha] [, modulateColor] [, textureShader] [, specialFlags] [, auxParameters]);"; // 1 2 3 4 5 6 7 8 9 10 11 PsychWindowRecordType *source, *target; PsychRectType sourceRect, targetRect, tempRect; double rotationAngle = 0; // Default rotation angle is zero deg. = upright. int filterMode = 1; // Default filter mode is bilinear filtering. double globalAlpha = 1.0; // Default global alpha is 1 == no effect. PsychColorType color; int textureShader, backupShader; double* auxParameters; int numAuxParams, m, n, p; psych_bool isclassic; int specialFlags = 0; //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(11)); PsychErrorExit(PsychRequireNumInputArgs(2)); PsychErrorExit(PsychCapNumOutputArgs(0)); //Read in arguments PsychAllocInWindowRecordArg(1, kPsychArgRequired, &target); PsychAllocInWindowRecordArg(2, kPsychArgRequired, &source); if(source->windowType!=kPsychTexture) { PsychErrorExitMsg(PsychError_user, "The first argument supplied was a window pointer, not a texture pointer"); } // Classic OpenGL-1/2? isclassic = PsychIsGLClassic(target); PsychCopyRect(sourceRect,source->clientrect); PsychCopyInRectArg(3, kPsychArgOptional, sourceRect); if (IsPsychRectEmpty(sourceRect)) return(PsychError_none); PsychCopyRect(tempRect, target->clientrect); PsychCenterRectInRect(sourceRect, tempRect, targetRect); PsychCopyInRectArg(4, kPsychArgOptional, targetRect); if (IsPsychRectEmpty(targetRect)) return(PsychError_none); PsychCopyInDoubleArg(5, kPsychArgOptional, &rotationAngle); PsychCopyInIntegerArg(6, kPsychArgOptional, &filterMode); if (filterMode > 5) { PsychErrorExitMsg(PsychError_user, "filterMode needs to be negative for a specific blur level, or at most 5 for other modes."); } // Copy in optional 'globalAlpha': We don't put restrictions on its valid range // anymore - That made sense for pure fixed function LDR rendering, but no longer // for HDR rendering or procedural shading. PsychCopyInDoubleArg(7, kPsychArgOptional, &globalAlpha); PsychSetDrawingTarget(target); PsychUpdateAlphaBlendingFactorLazily(target); if(PsychCopyInColorArg(8, kPsychArgOptional, &color)) { // set globalAlpha to DBL_MAX to signal that PsychBlitTexture() shouldn't // use this parameter and not set any modulate color, cause we do it. globalAlpha = DBL_MAX; // Setup global vertex color as modulate color for texture drawing: PsychCoerceColorMode(&color); // This call stores unclamped color in target->currentColor, as needed // if color is to be processed by some bound shader (procedural or filtershader) // inside PsychBlitTextureToDisplay(): PsychConvertColorToDoubleVector(&color, target, (GLdouble*) &(target->currentColor)); // Submit the same color to fixed function pipe attribute as well, in case no // shader is bound, or shader pulls from standard color attribute (we can't know yet): if (isclassic) { glColor4dv(target->currentColor); } else { PsychGLColor4f(target, (float) target->currentColor[0], (float) target->currentColor[1], (float) target->currentColor[2], (float) target->currentColor[3]); } } // Assign optional override texture shader, if any provided: textureShader = -1; PsychCopyInIntegerArg(9, kPsychArgOptional, &textureShader); // Assign any other optional special flags: PsychCopyInIntegerArg(10, kPsychArgOptional, &specialFlags); // Set rotation mode flag for texture matrix rotation if secialFlags is set accordingly: if (specialFlags & kPsychUseTextureMatrixForRotation) source->specialflags|=kPsychUseTextureMatrixForRotation; // Set rotation mode flag for no fixed function pipeline rotation if secialFlags is set accordingly: if (specialFlags & kPsychDontDoRotation) source->specialflags|=kPsychDontDoRotation; // Optional auxParameters: auxParameters = NULL; m=n=p=0; if (PsychAllocInDoubleMatArg(11, kPsychArgOptional, &m, &n, &p, &auxParameters)) { if ((p!=1) || (m * n < 4) || (((m*n) % 4)!=0)) PsychErrorExitMsg(PsychError_user, "The 11th argument must be a vector of 'auxParameter' values with a multiple of 4 components."); } numAuxParams = m*n; target->auxShaderParamsCount = numAuxParams; // Pass auxParameters for current primitive in the auxShaderParams field. if (numAuxParams > 0) { target->auxShaderParams = auxParameters; } else { target->auxShaderParams = NULL; } if (textureShader > -1) { backupShader = source->textureFilterShader; source->textureFilterShader = -1 * textureShader; PsychBlitTextureToDisplay(source, target, sourceRect, targetRect, rotationAngle, filterMode, globalAlpha); source->textureFilterShader = backupShader; } else { PsychBlitTextureToDisplay(source, target, sourceRect, targetRect, rotationAngle, filterMode, globalAlpha); } // Reset rotation mode flag: source->specialflags &= ~(kPsychUseTextureMatrixForRotation | kPsychDontDoRotation); target->auxShaderParams = NULL; target->auxShaderParamsCount = 0; // Mark end of drawing op. This is needed for single buffered drawing: PsychFlushGL(target); return(PsychError_none); }
// Batch-drawing version of DrawTexture: PsychError SCREENDrawTextures(void) { // If you change useString then also change the corresponding synopsis string in ScreenSynopsis.c 1 2 3 4 5 6 7 8 static char useString[] = "Screen('DrawTextures', windowPointer, texturePointer(s) [, sourceRect(s)] [, destinationRect(s)] [, rotationAngle(s)] [, filterMode(s)] [, globalAlpha(s)] [, modulateColor(s)] [, textureShader] [, specialFlags] [, auxParameters]);"; // 1 2 3 4 5 6 7 8 9 10 11 static char synopsisString[] = "Draw many textures at once, either one texture to many locations or many textures.\n" "This function accepts the same parameters as Screen('DrawTexture'), but it is optimized for drawing many textures. " "You can leave out each argument, a default setting will be used in that case, provide it once to apply it to all " "drawn items, or provide a vector or matrix with a individual setting for each drawn item. If you provide multiple " "settings per argument, then the number must match between all arguments.\n\n" "Examples:\n" "a) One texture drawn to different locations at different orientations: Provide one texture handle for the texturePointer, " "a 4 row by n columns matrix for 'destinationRect' to provide target rectangles for n locations, provide a n component " "vector of 'rotationAngles' for the n different orientations of the n drawn texture patches.\n" "b) n textures drawn to n different locations: Same as a) but provide a n component vector of 'texturePointers' one for " "each texture to be drawn to one of n locations at n angles.\n"; PsychWindowRecordType *source, *target; PsychRectType sourceRect, targetRect, tempRect; PsychColorType color; double *dstRects, *srcRects, *colors, *penSizes, *globalAlphas, *filterModes, *rotationAngles; unsigned char *bytecolors; int numTexs, numdstRects, numsrcRects, i, nc, mc, nrsize, m, n, p, numAngles, numFilterModes, numAlphas, numRef; double* texids; double rotationAngle, globalAlpha, filterMode; double* auxParameters; int numAuxParams, numAuxComponents; psych_bool isclassic; int textureShader, backupShader; int specialFlags = 0; //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 onscreen GL context which we will need in the //final step when we copy the texture from system RAM onto the screen. PsychErrorExit(PsychCapNumInputArgs(11)); PsychErrorExit(PsychRequireNumInputArgs(2)); PsychErrorExit(PsychCapNumOutputArgs(0)); // The target window is a fixed parameter: PsychAllocInWindowRecordArg(1, kPsychArgRequired, &target); // Classic OpenGL-1/2? isclassic = PsychIsGLClassic(target); // First get all source texture handles: PsychAllocInDoubleMatArg(2, kPsychArgRequired, &m, &n, &p, &texids); if ((p!=1) || (m>1 && n!=1) || (n>1 && m!=1)) PsychErrorExitMsg(PsychError_user, "The second argument must be either a row- or columnvector of valid texture handles."); // This is the number of texture handles: numTexs = m * n; // Only one texture? if (numTexs == 1) { // Yes. Allocate it in the conventional way: PsychAllocInWindowRecordArg(2, kPsychArgRequired, &source); if(source->windowType!=kPsychTexture) { PsychErrorExitMsg(PsychError_user, "The second argument supplied was not a texture handle!"); } } // Query, allocate and copy in all vectors... numdstRects = 4; nrsize = 0; colors = NULL; bytecolors = NULL; penSizes = NULL; // The negative position -4 means: dstRects coords are expected at position 4, but they are optional. // NULL means - don't want a size's vector. PsychPrepareRenderBatch(target, -4, &numdstRects, &dstRects, 8, &nc, &mc, &colors, &bytecolors, 5, &nrsize, &penSizes, FALSE); // At this point, target is set up as target window, i.e. its GL-Context is active, it is set as drawing target, // alpha blending is set up according to Screen('BlendFunction'), and the drawing color is set if it is a singular one. if (nc <= 1) { // Only one - or no - color provided. One or none? if(PsychCopyInColorArg(8, kPsychArgOptional, &color)) { // One global modulate color provided: // Setup global vertex color as modulate color for texture drawing: PsychCoerceColorMode(&color); PsychSetGLColor(&color, target); } else { // No modulateColor provided: Don't use this parameter: nc = 0; } } // Try to get source rects: m=n=p=0; if (PsychAllocInDoubleMatArg(3, kPsychArgOptional, &m, &n, &p, &srcRects)) { if ((p!=1) || (m!=1 && m!=4)) PsychErrorExitMsg(PsychError_user, "The third argument must be either empty, or a single srcRect 4 component row vector, or a 4 row by n column matrix with srcRects for all objects to draw, not a 3D matrix!"); // Ok, its a one row or four row matrix: if (m==4) { // Potentially multiple source rects provided: numsrcRects = n; } else { // Its a one row vector: This is either a single srcRect for all textures, or something invalid: if (n!=4) PsychErrorExitMsg(PsychError_user, "The third argument must be either empty, or a single srcRect 4 component row vector, or a 4 row by n column matrix with srcRects for all objects to draw!"); // Single srcRect provided: numsrcRects = 1; } } else { // No srcRects provided: numsrcRects = 0; } // Optional rotation angles: m=n=p=0; if (PsychAllocInDoubleMatArg(5, kPsychArgOptional, &m, &n, &p, &rotationAngles)) { if ((p!=1) || (m>1 && n!=1) || (n>1 && m!=1)) PsychErrorExitMsg(PsychError_user, "The fifth argument must be either a row- or columnvector of rotation angles."); } numAngles = m * n; // Default to 0 degree rotation -- upright drawing: rotationAngle = (numAngles == 1) ? rotationAngles[0] : 0.0; // Optional filter modes: m=n=p=0; if (PsychAllocInDoubleMatArg(6, kPsychArgOptional, &m, &n, &p, &filterModes)) { if ((p!=1) || (m>1 && n!=1) || (n>1 && m!=1)) PsychErrorExitMsg(PsychError_user, "The sixth argument must be either a row- or columnvector of filterModes."); } numFilterModes = m * n; // Default to bilinear filtering: filterMode = (numFilterModes == 1) ? filterModes[0] : 1; // Optional globalAlphas: m=n=p=0; if (PsychAllocInDoubleMatArg(7, kPsychArgOptional, &m, &n, &p, &globalAlphas)) { if ((p!=1) || (m>1 && n!=1) || (n>1 && m!=1)) PsychErrorExitMsg(PsychError_user, "The seventh argument must be either a row- or columnvector of globalAlpha values."); } numAlphas = m * n; globalAlpha = (numAlphas == 1) ? globalAlphas[0] : 1.0; // Optional auxParameters: auxParameters = NULL; m=n=p=0; if (PsychAllocInDoubleMatArg(11, kPsychArgOptional, &m, &n, &p, &auxParameters)) { if ((p!=1) || (m < 4) || ((m % 4) !=0)|| (n < 1)) PsychErrorExitMsg(PsychError_user, "The 11th argument must be a column vector or matrix of 'auxParameter' values with at least 4 components and component count a multiple of four."); } numAuxParams = n; numAuxComponents = m; // Check for consistency: Each parameter must be either not present, present once, // or present as many times as all other multi-parameters: numRef = (numsrcRects > numdstRects) ? numsrcRects : numdstRects; numRef = (numRef > numTexs) ? numRef : numTexs; numRef = (numRef > nc) ? numRef : nc; numRef = (numRef > numAlphas) ? numRef : numAlphas; numRef = (numRef > numFilterModes) ? numRef : numFilterModes; numRef = (numRef > numAngles) ? numRef : numAngles; numRef = (numRef > numAuxParams) ? numRef : numAuxParams; if (numTexs > 1 && numTexs != numRef) { printf("PTB-ERROR: Number of provided texture handles %i doesn't match number of other primitives %i!\n", numTexs, numRef); PsychErrorExitMsg(PsychError_user, "Inconsistent number of arguments provided to Screen('DrawTextures')."); } if (numsrcRects > 1 && numsrcRects != numRef) { printf("PTB-ERROR: Number of provided source rectangles %i doesn't match number of other primitives %i!\n", numsrcRects, numRef); PsychErrorExitMsg(PsychError_user, "Inconsistent number of arguments provided to Screen('DrawTextures')."); } if (numdstRects > 1 && numdstRects != numRef) { printf("PTB-ERROR: Number of provided destination rectangles %i doesn't match number of other primitives %i!\n", numdstRects, numRef); PsychErrorExitMsg(PsychError_user, "Inconsistent number of arguments provided to Screen('DrawTextures')."); } if (numAngles > 1 && numAngles != numRef) { printf("PTB-ERROR: Number of provided rotation angles %i doesn't match number of other primitives %i!\n", numAngles, numRef); PsychErrorExitMsg(PsychError_user, "Inconsistent number of arguments provided to Screen('DrawTextures')."); } if (numAlphas > 1 && numAlphas != numRef) { printf("PTB-ERROR: Number of provided global alpha values %i doesn't match number of other primitives %i!\n", numAlphas, numRef); PsychErrorExitMsg(PsychError_user, "Inconsistent number of arguments provided to Screen('DrawTextures')."); } if (numFilterModes > 1 && numFilterModes != numRef) { printf("PTB-ERROR: Number of provided filtermode values %i doesn't match number of other primitives %i!\n", numFilterModes, numRef); PsychErrorExitMsg(PsychError_user, "Inconsistent number of arguments provided to Screen('DrawTextures')."); } if (nc > 1 && nc != numRef) { printf("PTB-ERROR: Number of provided modulateColors %i doesn't match number of other primitives %i!\n", nc, numRef); PsychErrorExitMsg(PsychError_user, "Inconsistent number of arguments provided to Screen('DrawTextures')."); } if (numAuxParams > 1 && numAuxParams != numRef) { printf("PTB-ERROR: Number of provided 'auxParameter' column vectors %i doesn't match number of other primitives %i!\n", numAuxParams, numRef); PsychErrorExitMsg(PsychError_user, "Inconsistent number of arguments provided to Screen('DrawTextures')."); } // Assign optional override texture shader, if any provided: textureShader = -1; PsychCopyInIntegerArg(9, kPsychArgOptional, &textureShader); // Assign any other optional special flags: PsychCopyInIntegerArg(10, kPsychArgOptional, &specialFlags); // Ok, everything consistent so far. // Texture blitting loop: for (i=0; i < numRef; i++) { // Draw i'th texture: // Check if more than one texture provided. If not then the one single texture has been // setup already above: if (numTexs > 1) { // More than one texture handle provided: Need to allocate i'th one in: if(!IsWindowIndex((PsychWindowIndexType) texids[i])) { printf("PTB-ERROR: %i th entry in texture handle vector is not a valid handle!\n"); PsychErrorExitMsg(PsychError_user, "Invalid texture handle provided to Screen('DrawTextures')."); } // Get it: FindWindowRecord((PsychWindowIndexType) texids[i], &source); if(source->windowType!=kPsychTexture) { printf("PTB-ERROR: %i th entry in texture handle vector is not a valid handle!\n"); PsychErrorExitMsg(PsychError_user, "The second argument supplied was not a texture handle!"); } // Ok, we have our texture record in source: } // Source rectangle provided? if (numsrcRects > 1) { // Get i'th source rectangle: PsychCopyRect(sourceRect, &(srcRects[i*4])); } else if (numsrcRects == 1) { // Single source rect provided - get it: PsychCopyRect(sourceRect, &(srcRects[0])); } else { // No source rect provided: Take rectangle of current texture as srcRect: PsychCopyRect(sourceRect,source->clientrect); } // Skip this texture if sourceRect is an empty rect: if (IsPsychRectEmpty(sourceRect)) continue; // Destination rectangle provided? if (numdstRects > 1) { // Get i'th destination rectangle: PsychCopyRect(targetRect, &(dstRects[i*4])); } else if (numdstRects == 1) { // Single destination rect provided - get it: PsychCopyRect(targetRect, &(dstRects[0])); } else { // No destination rect provided: Center the current sourceRect in the current // target window and use that as destination: PsychCopyRect(tempRect, target->clientrect); PsychCenterRectInRect(sourceRect, tempRect, targetRect); } // Skip this texture if targetRect is an empty rect: if (IsPsychRectEmpty(targetRect)) continue; if (numAngles > 1) rotationAngle = rotationAngles[i]; if (numFilterModes > 1) filterMode = filterModes[i]; if (numAlphas > 1) globalAlpha = globalAlphas[i]; // Disable alpha if modulateColor active: if (nc > 0) globalAlpha = DBL_MAX; // Pass auxParameters for current primitive in the auxShaderParams field. target->auxShaderParamsCount = numAuxComponents; if (numAuxParams > 0) { if (numAuxParams == 1) { target->auxShaderParams = auxParameters; } else { target->auxShaderParams = &(auxParameters[i * numAuxComponents]); } } else { target->auxShaderParams = NULL; } // Multiple modulateColors provided? if (nc > 1) { if (isclassic) { // Yes. Set it up as current vertex color: We submit to internal currentColor for // shader based color processing and via glColorXXX() for fixed pipe processing: if (mc==3) { if (colors) { // RGB double: glColor3dv(&(colors[i*3])); target->currentColor[0]=colors[i*3 + 0]; target->currentColor[1]=colors[i*3 + 1]; target->currentColor[2]=colors[i*3 + 2]; target->currentColor[3]=1.0; } else { // RGB uint8: glColor3ubv(&(bytecolors[i*3])); target->currentColor[0]=((double) bytecolors[i*3 + 0] / 255.0); target->currentColor[1]=((double) bytecolors[i*3 + 1] / 255.0); target->currentColor[2]=((double) bytecolors[i*3 + 2] / 255.0); target->currentColor[3]=1.0; } } else { if (colors) { // RGBA double: glColor4dv(&(colors[i*4])); target->currentColor[0]=colors[i*4 + 0]; target->currentColor[1]=colors[i*4 + 1]; target->currentColor[2]=colors[i*4 + 2]; target->currentColor[3]=colors[i*4 + 3]; } else { // RGBA uint8: glColor4ubv(&(bytecolors[i*4])); target->currentColor[0]=((double) bytecolors[i*4 + 0] / 255.0); target->currentColor[1]=((double) bytecolors[i*4 + 1] / 255.0); target->currentColor[2]=((double) bytecolors[i*4 + 2] / 255.0); target->currentColor[3]=((double) bytecolors[i*4 + 3] / 255.0); } } } else { PsychSetArrayColor(target, i, mc, colors, bytecolors); } } // Ok, everything assigned. Check parameters: if (filterMode > 5) { PsychErrorExitMsg(PsychError_user, "filterMode needs to be negative for a specific blur level, or at most 5 for other modes."); } // Set rotation mode flag for texture matrix rotation if secialFlags is set accordingly: if (specialFlags & kPsychUseTextureMatrixForRotation) source->specialflags|=kPsychUseTextureMatrixForRotation; if (specialFlags & kPsychDontDoRotation) source->specialflags|=kPsychDontDoRotation; // Perform blit operation for i'th texture, either with or without an override texture shader applied: if (textureShader > -1) { backupShader = source->textureFilterShader; source->textureFilterShader = -1 * textureShader; PsychBlitTextureToDisplay(source, target, sourceRect, targetRect, rotationAngle, (int) filterMode, globalAlpha); source->textureFilterShader = backupShader; } else { PsychBlitTextureToDisplay(source, target, sourceRect, targetRect, rotationAngle, (int) filterMode, globalAlpha); } // Reset rotation mode flag: source->specialflags &= ~(kPsychUseTextureMatrixForRotation | kPsychDontDoRotation); // Next one... } target->auxShaderParams = NULL; target->auxShaderParamsCount = 0; // Mark end of drawing op. This is needed for single buffered drawing: PsychFlushGL(target); return(PsychError_none); }
void PsychSetArrayColor(PsychWindowRecordType *windowRecord, int i, int mc, double* colors, unsigned char *bytecolors) { psych_bool isgles = !PsychIsGLClassic(windowRecord); if ((windowRecord->defaultDrawShader) || isgles) { // Draw shader assigned or OpenGL-ES in use. Need to feed color values into high-precision // alternative channel for unclamped, high-precision color handling: if (mc==3) { i=i * 3; if (colors) { // RGB double: currentColor[0]=colors[i++]; currentColor[1]=colors[i++]; currentColor[2]=colors[i++]; currentColor[3]=1.0; } else { // RGB uint8: currentColor[0]=((double) bytecolors[i++] / 255.0); currentColor[1]=((double) bytecolors[i++] / 255.0); currentColor[2]=((double) bytecolors[i++] / 255.0); currentColor[3]=1.0; } } else { i=i * 4; if (colors) { // RGBA double: currentColor[0]=colors[i++]; currentColor[1]=colors[i++]; currentColor[2]=colors[i++]; currentColor[3]=colors[i++]; } else { // RGBA uint8: currentColor[0]=((double) bytecolors[i++] / 255.0); currentColor[1]=((double) bytecolors[i++] / 255.0); currentColor[2]=((double) bytecolors[i++] / 255.0); currentColor[3]=((double) bytecolors[i++] / 255.0); } } if (isgles) { // GLES can only do glColor4f(), nothing else: glColor4f((float) currentColor[0], (float) currentColor[1], (float) currentColor[2], (float) currentColor[3]); } else { HDRglColor4dv(currentColor); } } else { // Standard fixed-function pipeline assigned: Feed into standard glColorXXX() calls: if (mc==3) { if (colors) { // RGB double: glColor3dv(&(colors[i*3])); } else { // RGB uint8: glColor3ubv(&(bytecolors[i*3])); } } else { if (colors) { // RGBA double: glColor4dv(&(colors[i*4])); } else { // RGBA uint8: glColor4ubv(&(bytecolors[i*4])); } } } return; }