PsychError SCREENLoadCLUT(void) { int i, screenNumber, numEntries, inM, inN, inP, start, bits; float *outRedTable, *outGreenTable, *outBlueTable, *inRedTable, *inGreenTable, *inBlueTable; double *inTable, *outTable, maxval; psych_bool isclutprovided; start = 0; bits = 8; //all subfunctions should have these two lines PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; PsychErrorExit(PsychCapNumOutputArgs(1)); PsychErrorExit(PsychCapNumInputArgs(4)); // Read in the screen number: PsychCopyInScreenNumberArg(1, TRUE, &screenNumber); // Read in optional start index: PsychCopyInIntegerArg(3, FALSE, &start); if (start<0 || start>255) { PsychErrorExitMsg(PsychError_user, "Argument startEntry must be between zero and 255."); } // Read in optional bits argument: PsychCopyInIntegerArg(4, FALSE, &bits); if (bits<1 || bits>16) { PsychErrorExitMsg(PsychError_user, "Argument 'bits' must be between 1 and 16."); } // Compute allowable maxval: maxval=(double) ((1 << bits) - 1); // First read the existing gamma table so we can return it. PsychReadNormalizedGammaTable(screenNumber, &numEntries, &outRedTable, &outGreenTable, &outBlueTable); // Load and sanity check the input matrix, and convert from float to doubles: isclutprovided = PsychAllocInDoubleMatArg(2, FALSE, &inM, &inN, &inP, &inTable); if (isclutprovided) { if((inM > 256 - start) || (inM < 1) || (inN != 3) || (inP != 1)) PsychErrorExitMsg(PsychError_user, "The provided CLUT table must have a size between 1 and (256 - startEntry) rows and 3 columns."); inRedTable=PsychMallocTemp(sizeof(float) * 256); inGreenTable=PsychMallocTemp(sizeof(float) * 256); inBlueTable=PsychMallocTemp(sizeof(float) * 256); // Copy the table into the new inTable array: for(i=0; i<numEntries; i++) { inRedTable[i] = outRedTable[i]; inGreenTable[i] = outGreenTable[i]; inBlueTable[i] = outBlueTable[i]; } } // Allocate output array: PsychAllocOutDoubleMatArg(1, FALSE, numEntries, 3, 0, &outTable); // Copy read table into output array, scale it by maxval to map range 0.0-1.0 to 0-maxval: for(i=0;i<numEntries;i++){ outTable[PsychIndexElementFrom3DArray(numEntries, 3, 0, i, 0, 0)]=(double) outRedTable[i] * maxval; outTable[PsychIndexElementFrom3DArray(numEntries, 3, 0, i, 1, 0)]=(double) outGreenTable[i] * maxval; outTable[PsychIndexElementFrom3DArray(numEntries, 3, 0, i, 2, 0)]=(double) outBlueTable[i] * maxval; } if (isclutprovided) { // Now we can overwrite entries 'start' to start+inM of inTable with the user provided table values. We // need to scale the users values down from 0-maxval to 0.0-1.0: for(i=start; (i<256) && (i-start < inM); i++){ inRedTable[i] = (float) (inTable[PsychIndexElementFrom3DArray(inM, 3, 0, i-start, 0, 0)] / maxval); inGreenTable[i] = (float) (inTable[PsychIndexElementFrom3DArray(inM, 3, 0, i-start, 1, 0)] / maxval); inBlueTable[i] = (float) (inTable[PsychIndexElementFrom3DArray(inM, 3, 0, i-start, 2, 0)] / maxval); // Range check: if(inRedTable[i]>1 || inRedTable[i]< 0 || inGreenTable[i] > 1 || inGreenTable[i] < 0 || inBlueTable[i] >1 || inBlueTable[i] < 0) { printf("PTB-ERROR: At least one of the CLUT values in row %i is outside the valid range of 0 to %i!\n", i-start+1, ((1 << bits) - 1)); PsychErrorExitMsg(PsychError_user, "Tried to set a CLUT with invalid entries."); } } // Now set the new gamma table PsychLoadNormalizedGammaTable(screenNumber, numEntries, inRedTable, inGreenTable, inBlueTable); } return(PsychError_none); }
PsychError SCREENReadNormalizedGammaTable(void) { int i, screenNumber, numEntries, reallutsize, physicalDisplay, outputId; float *redTable, *greenTable, *blueTable; double *gammaTable; //all subfunctions should have these two lines PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; PsychErrorExit(PsychCapNumOutputArgs(3)); PsychErrorExit(PsychCapNumInputArgs(2)); // Get optional physicalDisplay argument - It defaults to zero: physicalDisplay = -1; PsychCopyInIntegerArg(2, FALSE, &physicalDisplay); // Read in the screen number: // On OS/X we also accept screen indices for physical displays (as opposed to active dispays). // This only makes a difference in mirror-mode, where there is only 1 active display, but that // corresponds to two physical displays which can have different gamma setting requirements: if ((PSYCH_SYSTEM == PSYCH_OSX) && (physicalDisplay > 0)) { PsychCopyInIntegerArg(1, TRUE, &screenNumber); if (screenNumber < 1) PsychErrorExitMsg(PsychError_user, "A 'screenNumber' that is smaller than one provided, although 'physicalDisplay' flag set. This is not allowed!"); // Invert screenNumber as a sign its a physical display, not an active display: screenNumber = -1 * screenNumber; } else { PsychCopyInScreenNumberArg(1, TRUE, &screenNumber); } if ((PSYCH_SYSTEM == PSYCH_LINUX) && (physicalDisplay > -1)) { // Affect one specific display output for given screen: outputId = physicalDisplay; } else { // Other OS'es, and Linux with default setting: Affect all outputs // for a screen. outputId = -1; } // Retrieve gamma table: PsychReadNormalizedGammaTable(screenNumber, outputId, &numEntries, &redTable, &greenTable, &blueTable); // Copy it out to runtime: PsychAllocOutDoubleMatArg(1, FALSE, numEntries, 3, 0, &gammaTable); for(i=0;i<numEntries;i++){ gammaTable[PsychIndexElementFrom3DArray(numEntries, 3, 0, i, 0, 0)]=(double)redTable[i]; gammaTable[PsychIndexElementFrom3DArray(numEntries, 3, 0, i, 1, 0)]=(double)greenTable[i]; gammaTable[PsychIndexElementFrom3DArray(numEntries, 3, 0, i, 2, 0)]=(double)blueTable[i]; } // Copy out optional DAC resolution value: PsychCopyOutDoubleArg(2, FALSE, (double) PsychGetDacBitsFromDisplay(screenNumber)); // We default to the assumption that the real size of the hardware LUT is identical to // the size of the returned LUT: reallutsize = numEntries; #if PSYCH_SYSTEM == PSYCH_OSX // On OS-X we query the real LUT size from the OS and return that value: CGDirectDisplayID displayID; CFMutableDictionaryRef properties; CFNumberRef cfGammaLength; SInt32 lutslotcount; io_service_t displayService; kern_return_t kr; CFMutableArrayRef framebufferTimings0 = 0; CFDataRef framebufferTimings1 = 0; IODetailedTimingInformationV2 *framebufferTiming = NULL; // Retrieve display handle for screen: PsychGetCGDisplayIDFromScreenNumber(&displayID, screenNumber); if (PsychPrefStateGet_Verbosity()>5) printf("PTB-DEBUG: Screen %i has framebuffer address %p.\n", screenNumber, CGDisplayBaseAddress(displayID)); // Retrieve low-level IOKit service port for this display: displayService = CGDisplayIOServicePort(displayID); // Obtain the properties from that service kr = IORegistryEntryCreateCFProperties(displayService, &properties, NULL, 0); if((kr == kIOReturnSuccess) && ((cfGammaLength = (CFNumberRef) CFDictionaryGetValue(properties, CFSTR(kIOFBGammaCountKey)))!=NULL)) { CFNumberGetValue(cfGammaLength, kCFNumberSInt32Type, &lutslotcount); CFRelease(properties); reallutsize = (int) lutslotcount; } else { // Failed! if (PsychPrefStateGet_Verbosity()>1) printf("PTB-WARNING: Failed to query real size of video LUT for screen %i! Will return safe default of %i slots.\n", screenNumber, reallutsize); } if (PsychPrefStateGet_Verbosity()>9) { CFDictionaryRef currentMode; CFNumberRef n; int modeId; currentMode = CGDisplayCurrentMode(displayID); n=CFDictionaryGetValue(currentMode, kCGDisplayMode); CFNumberGetValue(n, kCFNumberIntType, &modeId); printf("Current mode has id %i\n\n", modeId); kr = IORegistryEntryCreateCFProperties(displayService, &properties, NULL, 0); if((kr == kIOReturnSuccess) && ((framebufferTimings0 = (CFMutableArrayRef) CFDictionaryGetValue(properties, CFSTR(kIOFBDetailedTimingsKey) ) )!=NULL)) { for (i=0; i<CFArrayGetCount(framebufferTimings0); i++) { if ((framebufferTimings1 = CFArrayGetValueAtIndex(framebufferTimings0, i)) != NULL) { if ((framebufferTiming = (IODetailedTimingInformationV2*) CFDataGetBytePtr(framebufferTimings1)) != NULL) { printf("[%i] : VActive = %li, VBL = %li, VSYNC = %li, VSYNCWIDTH = %li , VBORDERBOT = %li, VTOTAL = %li \n", i, framebufferTiming->verticalActive, framebufferTiming->verticalBlanking, framebufferTiming->verticalSyncOffset, framebufferTiming->verticalSyncPulseWidth, framebufferTiming->verticalBorderBottom, framebufferTiming->verticalActive + framebufferTiming->verticalBlanking); } } } CFRelease(properties); } else { // Failed! if (PsychPrefStateGet_Verbosity()>1) printf("PTB-WARNING: Failed to query STUFF for screen %i --> %p!\n", screenNumber, properties); } } #endif // Copy out optional real LUT size (number of slots): PsychCopyOutDoubleArg(3, FALSE, (double) reallutsize); return(PsychError_none); }
PsychError SCREENLoadNormalizedGammaTable(void) { int i, screenNumber, numEntries, inM, inN, inP, loadOnNextFlip, physicalDisplay, outputId; float *outRedTable, *outGreenTable, *outBlueTable, *inRedTable, *inGreenTable, *inBlueTable; double *inTable, *outTable; PsychWindowRecordType *windowRecord; //all subfunctions should have these two lines PsychPushHelp(useString, synopsisString, seeAlsoString); if(PsychIsGiveHelp()){PsychGiveHelp();return(PsychError_none);}; PsychErrorExit(PsychCapNumOutputArgs(1)); PsychErrorExit(PsychCapNumInputArgs(4)); // Get optional physicalDisplay argument - It defaults to zero on OS/X, -1 on Linux: physicalDisplay = -1; PsychCopyInIntegerArg(4, FALSE, &physicalDisplay); // Read in the screen number: // On OS/X we also accept screen indices for physical displays (as opposed to active dispays). // This only makes a difference in mirror-mode, where there is only 1 active display, but that // corresponds to two physical displays which can have different gamma setting requirements: if ((PSYCH_SYSTEM == PSYCH_OSX) && (physicalDisplay > 0)) { PsychCopyInIntegerArg(1, TRUE, &screenNumber); if (screenNumber < 1) PsychErrorExitMsg(PsychError_user, "A 'screenNumber' that is smaller than one provided, although 'physicalDisplay' flag set. This is not allowed!"); // Invert screenNumber as a sign its a physical display, not an active display: screenNumber = -1 * screenNumber; } else { PsychCopyInScreenNumberArg(1, TRUE, &screenNumber); } if ((PSYCH_SYSTEM == PSYCH_LINUX) && (physicalDisplay > -1)) { // Affect one specific display output for given screen: outputId = physicalDisplay; } else { // Other OS'es, and Linux with default setting: Affect all outputs // for a screen. outputId = -1; } // Load and sanity check the input matrix: inM = -1; inN = -1; inP = -1; if (!PsychAllocInDoubleMatArg(2, FALSE, &inM, &inN, &inP, &inTable)) { // Special case: Allow passing in an empty gamma table argument. This // triggers auto-load of identity LUT and setup of GPU for identity passthrough: inM = 0; inN = 3; inP = 1; } // Sanity check dimensions: if((inN != 3) || (inP != 1)) PsychErrorExitMsg(PsychError_user, "The gamma table must have 3 columns (Red, Green, Blue)."); // Identity passthrouh setup requested? if (inM == 0) { // Yes. Try to enable it, return its status code: PsychAllocInWindowRecordArg(1, TRUE, &windowRecord); i = PsychSetGPUIdentityPassthrough(windowRecord, screenNumber, TRUE); PsychCopyOutDoubleArg(1, FALSE, (double) i); // Done. return(PsychError_none); } #if PSYCH_SYSTEM != PSYCH_WINDOWS // OS-X and Linux allow tables with other than 256 slots: // OS/X either passes them to hw if in native size, or performs // software interpolation to convert it into native size. We allow any table size with 1 - x slots. // A table size of 1 row will have a special meaning. It interprets the 1 row of the table as gamma formula // min, max, gamma and lets the OS compute a corresponding gamma correction table. // A table size of zero rows will trigger an internal upload of an identity table via byte transfer. // On Linux we need to interpolate ourselves on non-matching table sizes. #else // Windows requires 256 slots: if((inM != 256) && (inM != 0)) { PsychErrorExitMsg(PsychError_user, "The gamma table must have 256 rows."); } #endif // Copy in optional loadOnNextFlip - flag. It defaults to zero. If provided // with a non-zero value, we will defer actual update of the gamma table to // the next bufferswap as initiated via Screen('Flip'). loadOnNextFlip = 0; PsychCopyInIntegerArg(3, FALSE, &loadOnNextFlip); if (loadOnNextFlip>0) { if ((PSYCH_SYSTEM == PSYCH_OSX) && (physicalDisplay > 0)) PsychErrorExitMsg(PsychError_user, "Non-zero 'loadOnNextFlip' flag not allowed if 'physicalDisplays' flag is non-zero!"); if ((PSYCH_SYSTEM == PSYCH_LINUX) && (physicalDisplay > -1)) PsychErrorExitMsg(PsychError_user, "Non-zero 'loadOnNextFlip' flag not allowed if 'physicalDisplays' setting is positive!"); // Allocate tables in associated windowRecord: We will update during next // Flip operation for specified windowRecord. PsychAllocInWindowRecordArg(1, TRUE, &windowRecord); // Sanity checks: if (!PsychIsOnscreenWindow(windowRecord)) PsychErrorExitMsg(PsychError_user, "Target window for gamma table upload is not an onscreen window!"); if (windowRecord->inRedTable && loadOnNextFlip!=2) PsychErrorExitMsg(PsychError_user, "This window has already a new gamma table assigned for upload on next Flip!"); if (windowRecord->inRedTable && windowRecord->inTableSize != inM) { free(windowRecord->inRedTable); windowRecord->inRedTable = NULL; free(windowRecord->inGreenTable); windowRecord->inGreenTable = NULL; free(windowRecord->inBlueTable); windowRecord->inBlueTable = NULL; } if (windowRecord->inRedTable == NULL) { // Allocate persistent memory: inRedTable=malloc(sizeof(float) * inM); inGreenTable=malloc(sizeof(float) * inM); inBlueTable=malloc(sizeof(float) * inM); // Assign the pointers to the windowRecord: windowRecord->inRedTable = inRedTable; windowRecord->inGreenTable = inGreenTable; windowRecord->inBlueTable = inBlueTable; windowRecord->inTableSize = inM; } else { inRedTable = windowRecord->inRedTable; inGreenTable = windowRecord->inGreenTable; inBlueTable = windowRecord->inBlueTable; } windowRecord->loadGammaTableOnNextFlip = (loadOnNextFlip == 1) ? 1 : 0; } else { // Allocate temporary tables: We will update immediately. inRedTable=PsychMallocTemp(sizeof(float) * inM); inGreenTable=PsychMallocTemp(sizeof(float) * inM); inBlueTable=PsychMallocTemp(sizeof(float) * inM); } for(i=0;i<inM;i++){ inRedTable[i]=(float)inTable[PsychIndexElementFrom3DArray(inM, 3, 0, i, 0, 0)]; inGreenTable[i]=(float)inTable[PsychIndexElementFrom3DArray(inM, 3, 0, i, 1, 0)]; inBlueTable[i]=(float)inTable[PsychIndexElementFrom3DArray(inM, 3, 0, i, 2, 0)]; if(inRedTable[i]>1 || inRedTable[i]< 0 || inGreenTable[i] > 1 || inGreenTable[i] < 0 || inBlueTable[i] >1 || inBlueTable[i] < 0) PsychErrorExitMsg(PsychError_user, "Gamma Table Values must be in interval 0 =< x =< 1"); } if (loadOnNextFlip < 2) { //first read the existing gamma table so we can return it. PsychReadNormalizedGammaTable(screenNumber, outputId, &numEntries, &outRedTable, &outGreenTable, &outBlueTable); PsychAllocOutDoubleMatArg(1, FALSE, numEntries, 3, 0, &outTable); for(i=0;i<numEntries;i++){ outTable[PsychIndexElementFrom3DArray(numEntries, 3, 0, i, 0, 0)]=(double)outRedTable[i]; outTable[PsychIndexElementFrom3DArray(numEntries, 3, 0, i, 1, 0)]=(double)outGreenTable[i]; outTable[PsychIndexElementFrom3DArray(numEntries, 3, 0, i, 2, 0)]=(double)outBlueTable[i]; } } //Now set the new gamma table if (loadOnNextFlip == 0) PsychLoadNormalizedGammaTable(screenNumber, outputId, inM, inRedTable, inGreenTable, inBlueTable); return(PsychError_none); }