User Tools

Site Tools


monitor:custom_resolutions

Custom Resolutions

Introduction

To drive the high-bandwidth AOMs (which could have a bandwidth anywhere between 80MHz and 3GHz), we use graphics cards as signal generators and desktop windows as the sample array for the signal generators. To generate a large output bandwidth, it is necessary to have a high sampling frequency, which is accomplished by setting the graphics card clock to an above-average rate, and a large sample size, which is accomplished by creating a larger-than-average desktop in the chosen operating system. The desktop frame rate is determined by these two factors.

The critical aspects of the custom resolutions are the pixel clock rate, the active pixel resolution, and the total resolution. There are few, if any, constraints on the blanking and sync pulse periods/polarity. (under review, Fri 22 Jan 2016)

If these terms are unfamiliar, please research the topics in the Readings & Keywords section below

The new resolution settings used, as of Fri 29 Jan 2016 are as follows:

Detailed Timing Descriptor Format:

Pixel Clk400 MHz
H Active Pxl3872V Active Pxl3293
H Blank112V Blank35
H Sync Offset16V Sync Offset1
H Sync Width96V Sync Width1
H Image Size355 mmV Image Size247 mm
H Border0V Border0
H Sync Polarity+V Sync Polarity+

It used the following parameter values:

f_FRAMERATE 30.169 fps DoF, f_GPU /( HTotal * VTotal )
f_HSYNC 99.840 100.402 kHz f_GPU / HTotal
f_GPU 400 MHz DoF
f_POLY 1,560 1,568.8 Hz DoF, maximum-ish consistently lockable
f_DivHSYNC f_POLY f_HSYNC / CompRowsPerHoloLine
CompRowsPerHoloLine 64 DoF
NHoloLines 52 DoF
HTotal 4006 3984 HActive + HBlankMin
HActive 3894 3872 maximum supported by EDID and computer
HBlankMin 112 experimentally determined
VTotal 3328 CompRowsPerHoloLine * NHoloLines
VActive 3293 VTotal - VBlankMin
VBlankMin 35 experimentally determined
PixelsPerHoloLine 254976 HTotal * NHoloLines
Pixels per frame 13258752 PixelsPerHoloLine * NHoloLines
Divider Knobs /8,/4 (“7”,“3”)
Sync CCA Rev 29 Jul 2015

Struck-out values are idealistic, but did not produce an HTotal which was a multiple of 16. (:!: Verify.) Used values next to struck-out ones were backwards-adjusted so that HTotal was a multiple of 16. This limitation may be due to cache block sizes.

The old resolution settings used, as of 23 June 2015 are as follows:

Detailed Timing Descriptor Format:

Pixel Clk400 MHz
H Active Pxl3552V Active Pxl2476
H Blank1576V Blank124
H Sync Offset32V Sync Offset56
H Sync Width1023*V Sync Width12
H Image Size355 mmV Image Size247 mm
H Border0V Border0
H Sync Polarity+V Sync Polarity+

(formatted after Phoenix EDID Designer v1.3)

Modeline Format:

DispSync StartSync EndTotal
Horizontal:355235845096*5128
Vertical:2476253225442600

*explanation in EDID section below

Choice of Values

Variable Description
f_FRAMERATE Rate at which the entire screen is redrawn. Alias for f_VSYNC.
f_HSYNC HSync pulse frequency.
f_GPU GPU pixel clock frequency. 400 MHz max for NVidia graphics cards.
f_POLY Rate at which the laser beam is swept. Multiple of polygon mirror speed (rev per sec) and facet count. Ranges between 0.96 to 1.71 kHz for the KS-49 / LS7170 scanner assembly we're currently using.
f_DivHSYNC f_HSYNC divided by CompRowsPerHoloLines.
CompRowsPerHoloLine The number of computer pixel rows per row on the holomonitor output. Hardware counters must be able to create this ratio.
NHoloLines The number of rows in the holomonitor output; aka, holomonitor vertical resolution.
HTotal The total number of 'pixels' between HSync pulses.
HActive The number of addressable pixels in a computer row.
HBlankMin The minimum number of horizontally-blanking pixels the hardware can support. Equals 112 from trial/error.
VTotal The total number of 'rows' per frame.
VActive The number of addressable rows in a computer frame.
VBlankMin The minimum number of vertically-blanking rows the hardware can support. Equals 35 from trial/error.

In choosing the Fri 29 Jan 2016 values currently used, several degrees of freedom and constraint were discovered, along with having to back-track and choose different values:

Constraints
(1) Min/Max poly freq
(2) Integer lines per frame
(3) Poly voltage buffer range needed for locking
(4) EDID EEPROM limitations (H/V Active < 4096)
(5) Graphics card max clock freq
(6) Integer values/ratios
(7) Galvo Slew Rate
(8) HActive needs to be a multiple of 32
Freedoms
[1] Poly freq
[2] Framerate
[3] # HoloLines
[4] Comp rows per hololine

(1) [1] We initially began with choosing to keep f_poly the same since we know we can lock at this speed. The faster we spin it (up to 1.7kHz), the less ability we'll have to 'kick' it up to speed to stay in lock.

The locking behavior of the 29 Jul 2015 Synchronization CCA requires that f_DivHSYNC == f_POLY.

FIXME (Fri 26 Feb 2016): finish explaining this…

Implementation

There are several techniques to implement the needed custom resolutions, each with varying degrees of difficulty and success depending upon the operating system and hardware used. There are two categories of implementation: software and hardware.

Software Implementation

The software implementation usually involves a balancing-act between the operating system and graphics card drivers. Linux may have less support than Windows as high-end graphics cards manufacturers typically manufacture and develop primarily for the Windows market. Previous efforts to create custom resolutions in Linux involved editing the X11 file xorg.conf, reinstalling and configuring drivers, restarting the computer several times after each significant change, searching X11 user forums for solutions, and reverting changes that disabled the primary desktop. Previous efforts to create custom resolutions in Windows involved trying to coerce the proprietary display manager to accept custom resolutions for the HoloMonitor attached, which resulted in failed persistence. There may be other promising venues to implement these resolutions via software that we are currently unaware of.

Hardware Implementation

The hardware implementation has given us the greatest degree of control and persistence. We currently use the same technique used by many commercial monitor manufacturers to describe to a computer the resolutions we want. In most modern commercial monitors, there exists a 256-byte I2C EEPROM chip that contains bit-packed data, called the EDID, that describes the monitor. We use a small program, Phoenix EDID Designer v1.3 (Fig. 1), to generate the data (128 bytes long) describing the resolutions we want. We reprogram the HoloMonitor's Arduino (.ino file provided below) to flash that data onto the I2C EEPROM chip of the Mixer/Amplifier CCA. We don't use the last 128 bytes of the EEPROM. (If needed, set these to zeros.) The 128-bytes of data we program onto the chip (starting at address 0x00) is provided below. That chip is later connected to the I2C bus of the DVI/VGA ports of the graphics card through the Mixer/Amplifier CCA. Upon detecting a new “monitor” being attached, the graphics card reads the EEPROM chip to get the “monitor's” description and resolution.

Although using a physical chip with EDID on it has given us the most success in applying our resolutions, we have encountered an issue where the operating system fails to receive the EDID. This occurred in Windows with NVidia hardware, where requesting Windows to Detect new displays (through the Screen Resolution control panel) resulted in the HoloMonitor being displayed with a lower resolution, with the desktop sometimes not being extendable onto the HoloMonitor. The workaround was to first plug the EDID-programmed MixAmp CCA into a VGA-VGA cable using VGA-DVI passive adapters, then plugging the cable into the graphics card. The HoloMonitor would then appear with the correct resolution available in Windows, and one could extend the Windows desktop onto the HoloMonitor (Fig. 3). The theory explaining this problem is that the [Detect] button in the Screen Resolution control panel doesn't issue a request to/through the graphics card to read the EDID of currently attached monitors, and that Windows instead looks up preexisting or replacement resolutions previously saved in the registry. When the VGA cable is plugged into the graphics card (with the HoloMonitor already attached), it is that point (it is theorized) when the EDID is actually read from the HoloMonitor I2C EEPROM chip. A straight-through DVI-I cable was unavailable at the time of testing (DVI-D cables will not work with the HoloMonitor; read descriptions).

We reprogram the Arduino in the HoloMonitor to program the EDID onto the EEPROM chip via a VGA breakout board (Fig. 2), then reprogram the Arduino to control the galvanometer as part of its normal task.

Figure 1: Phoenix EDID Designer v1.3

Figure 2: EDID programming with Arduino & VGA breakout board

Figure 3: Successful HoloMonitor detection in Windows

Listing 1: EDID data in “Phoenix EDID Designer” format (as of Fri 29 Jan 2016)

EDID BYTES:
0x   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
    ------------------------------------------------
00 | 00 FF FF FF FF FF FF 00 0B 35 02 00 15 CD 5B 07
10 | 1A 18 01 03 68 3C 32 78 08 8F 30 A3 55 49 98 27
20 | 14 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01
30 | 01 01 01 01 01 01 40 9C 20 70 F0 DD 23 C0 10 60
40 | 11 00 63 F7 10 00 00 1E 00 00 00 FC 00 48 4F 4C
50 | 4F 5F 4D 4F 4E 49 54 4F 52 0A 00 00 00 FE 00 53
60 | 55 50 20 46 4F 4F 4C 53 0A 0A 0A 0A 00 00 00 10
70 | 00 41 52 42 5F 54 45 53 54 0A 0A 0A 0A 0A 00 12

Note: Copy this text and save it as a .dat file, with a blank line at the end, to be able to open it up in Phoenix.

Readings & Keywords

Arduino_EDID_Programmer.ino

#include <Wire.h>

///////////////////////////////////////////////////////////////
//                                                           //
//         BYU Holography EDID Parser/Reader/Writer          //
//                                                           //
///////////////////////////////////////////////////////////////
//                                                           //
//  Author: Andrew Henrie                                     //
//  Date: Tue 30 Jun 2015                                    //
//                                                           //
//  Copy and paste one of the action options provided below  //
//  into the initialization value of the global variable     //
//  "action" to indicate what you'd like to do.              //
//                                                           //
//  Assuming that the Arduino, HoloMonitor Arduino Shield,   //
//  the VGA/DVI I2C adapter, and the MixAmp CCA are all      //
//  connected, perform one of the following:                 //
//                                                           //
//  READ_ONLY                                                //
//   - read the current EDID on the EEPROM chip              //
//                                                           //
//  PARSE_AND_READ                                           //
//   - test-parse a Phoenix EDID Designer .dat file and      //
//     read the current EDID                                 //
//                                                           //
//  PARSE_WRITE_AND_READ                                     //
//   - parse a Phoenix EDID Designer .dat file, write it to  //
//     the EEPROM chip, and read it back for verification    //
//                                                           //
///////////////////////////////////////////////////////////////

#define  READ_ONLY             0
#define  PARSE_AND_READ        1
#define  PARSE_WRITE_AND_READ  2

int action = PARSE_WRITE_AND_READ;

//  If you want to flash your own EDID, you will have to 
//  copy-paste the Phoenix EDID Designer .dat file here 
//  and put double-quotation marks around each line with
//  a semicolon on the end; like so:

byte PhoenixDATstring[] = "EDID BYTES:"
"0x   00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F"
"    ------------------------------------------------"
"00 | 00 FF FF FF FF FF FF 00 0B 35 02 00 15 CD 5B 07"
"10 | 1A 18 01 03 68 3C 32 78 08 8F 30 A3 55 49 98 27"
"20 | 14 50 54 00 00 00 01 01 01 01 01 01 01 01 01 01"
"30 | 01 01 01 01 01 01 40 9C 20 70 F0 DD 23 C0 10 60"
"40 | 11 00 63 F7 10 00 00 1E 00 00 00 FC 00 48 4F 4C"
"50 | 4F 5F 4D 4F 4E 49 54 4F 52 0A 00 00 00 FE 00 53"
"60 | 55 50 20 46 4F 4F 4C 53 0A 0A 0A 0A 00 00 00 10"
"70 | 00 41 52 42 5F 54 45 53 54 0A 0A 0A 0A 0A 00 12"
""; // optimized_52hololines_Fri22Jan2016.dat













unsigned long I2C_delay = 10; // milliseconds to delay after I2C transfers, for making sure state machines have enough processing time
// if there are intermittent / repeated failures and the hardware is likely good, try increasing this value by x10

boolean chkHexFmt(byte c) {
  // check that a character can be hexadecimal
  
  return ('0' <= c && c <= '9') || ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f');
}

byte char2nybble(byte c) {
  // convert a hexadecimal character to a number between 0-15
  
  if ('0' <= c && c <= '9')
    return c - '0';
  else if ('a' <= c && c <= 'f')
    return c - 'a' + 10;
  else if ('A' <= c && c <= 'F')
    return c - 'A' + 10;
  else
    return 0x0e; // a unique error code
}

byte hex2byte(byte charU, byte charL) {
  // convert two hexadecimal characters into a number between 0-255
  
  if ( chkHexFmt(charU) && chkHexFmt(charL) )
  {
    return (char2nybble(charU) << 4) | char2nybble(charL);
  }
  else
  {
    Serial.print("hex parse error: '");
    Serial.write(charU);
    Serial.print("' '");
    Serial.write(charL);
    Serial.println("'");
    return 0xdd; // a unique error code
  }
}

void setup()
{
  Serial.begin(9600);
  Wire.begin(); // join i2c bus (address optional for master)
  Serial.flush();
  Serial.println();
  Serial.println("Alive...");
  Serial.println();




  byte EDID[128];
  if (action == PARSE_AND_READ || action == PARSE_WRITE_AND_READ)
  {
    Serial.println("Parsing Phoneix EDID .dat file...");

    int start_i = 126 - 2 * 3; // location of first byte in .dat file
    int line_delta = 54 - 2; // number of characters to advance each line, minus \r \n as these were removed when the file was copied in above
    for (int line_i = 0; line_i < 8; line_i++) { // go through each of the 8 data lines
      for (int byte_i = 0; byte_i < 16; byte_i++) { // go through each of the 16 bytes
        byte upperNybble = PhoenixDATstring[byte_i * 3     + line_i * line_delta + start_i];
        byte lowerNybble = PhoenixDATstring[byte_i * 3 + 1 + line_i * line_delta + start_i];
        byte c = hex2byte(upperNybble, lowerNybble);
        EDID[line_i * 16 + byte_i] = c;
        int total_i = byte_i + line_i * 16;
        if (total_i < 16) Serial.print('0');
        Serial.print(total_i, HEX);
        Serial.print(" '");
        Serial.write(upperNybble);
        Serial.print("' '");
        Serial.write(lowerNybble);
        Serial.print("' ");
        if (c < 16) Serial.print('0');
        Serial.print(c, HEX);
        Serial.println();
      }
    }
    Serial.println("Parsing done.");
    Serial.println();

    // read back parsed data array
    Serial.println("EDID parsed from file:");
    for (int i = 0; i < 8; i++) {
      for (int j = 0; j < 16; j++) {
        byte c = EDID[i * 16 + j];
        if (c < 16) Serial.print('0');
        Serial.print(c, HEX);
        if (j != 15) Serial.print(' ');
      }
      Serial.println();
    }
    Serial.println();


    if (action == PARSE_WRITE_AND_READ)
    {

      Serial.println("Writing parsed data to EEPROM (I2C addr 0x50)...");

      // write 8 bytes at a time; this is the limit for the Microchip 24AA02/24LC02B chip we're using
      for (int i = 0; i < 16; i++) {
        Wire.beginTransmission(0x50); // I2C address 0x50
        Wire.write(byte(i * 8)); // memory address in EEPROM
        for (int j = 0; j < 8; j++) {
          Wire.write(EDID[i * 8 + j]);
        }
        Wire.endTransmission();
        delay(I2C_delay);
      }

      Serial.println();

    } // end of write case
  } // end of parse case




  


  Serial.println("Reading data...");

  Wire.beginTransmission(0x50);
  Wire.write(byte(0x00)); // set address pointer to 0x00
  Wire.endTransmission();
  delay(I2C_delay);
  

  int mismatch_addr = 0x100;

  for (int i = 0; i < 8; i++) {
    Wire.requestFrom(0x50, 16);

    for (int j = 0; j < 16; j++) {
      uint8_t data = Wire.read();
      if ( EDID[j + i * 16] != data && mismatch_addr == 0x100)
        mismatch_addr = j + i * 16;
      if (data < 0x10) Serial.print('0');
      Serial.print(data, HEX);
      Serial.print(' ');
    }
    delay(I2C_delay);

    Serial.print('\n');
  }
  Serial.println();
  
  


  if (action == PARSE_WRITE_AND_READ)
  {
    if (mismatch_addr != 0x100)
    {
      Serial.print("EDID flash unsuccessful; write/reread discrepancy beginning at address 0x");
      if (mismatch_addr < 16) Serial.print("0");
      Serial.println(mismatch_addr, HEX);
    }
    else
    {
      Serial.println("EDID flash successful!");
    }

    Serial.println();
  }



  Serial.println("Done.");
}

void loop()
{
}

Changes

On Fri 22 Jan 2016, the monitor timings and Synchronization CCA settings were optimized to produce maximal vertical resolution at 30fps with minimal horizontal blanking. The monitor active region is 3872×3293, with 112, 35 pixels H/V blanking respectively. Horizontal blanking now occupies 2.8% of the screen. This is still noticeable, but only barely.

On Wed 20 Jan 2016, the resolutions were changed slightly to slightly reduce blanking. This change changed the horizontal active region from 3552 to 4095 pixels while reducing the blanking region from 1,576 to 1033. The total horizontally-blanked region went from occupying ~30% to ~20% of the screen with this small change. This was the simplest way to get immediate improvement. Further improvement will require some mathematical analysis and constraint identification.

EDID / Resolution Reprogramming

As of Thu 21 Jan 2016, the procedure to change the holomonitor resolution is somewhat intricate and diverse and requires modifications to software, hardware, and firmware. This procedure can be used to program any resolution.

Hardware Configuration

The HSync division needs to be changed to 64, which factors into 8, 4, and 2. The divider knobs need to be changed to 7 and 3 for this (they add 1 to the input number). Order doesn't matter.

hex_switch_position.jpg

Hardware needs to be connected in either of the following configurations to connect the EEPROM chips to the Arduino's I2C data lines:

'Smart' VGA adapter:

edid_reprogramming_hookup_complete.jpg

'Dumb' VGA adapter:

edid_reprogramming_-_dumb_adapter.jpg

If you're using the 'dumb' VGA adapter, just connect as shown above; no further steps are needed.

Hardware: Step 1

You'll need to solder the following two points together on the 'smart' VGA adapter:

vga_smart_adapter_patch_1.jpg

This jump is made to power the EEPROM on the MixAmp board from the Arduino. This step is not necessary if you're reprogramming only the 'smart' VGA breakout board.

Hardware: Step 2

Connect the Arduino shield to the 'smart' VGA board in the following way. Be careful not to hook up the ports in reverse order.

edid_reprogramming_arduino_shield_-_vga_smart_breakout_hookup.jpg

If you're using the 'smart' VGA breakout board in place of the MixAmp board, you can skip the remaining hardware steps, as you'll be programming only the EDID chip on the 'smart' VGA breakout board.

Hardware: Step 3

Connect the computer (via USB), Arduino, 'smart' VGA adapter board, VGA cable, VGA-DVI adapter, and MixAmp board as shown:

edid_reprogramming_with_smart_vga_adapter.jpg

Firmware Configuration

You'll need to flash the EEPROM chips with new data using the Arduino, as shown below.

Firmware: Step 1

Download, unzip, and open THIS Arduino project in the Arduino IDE (programming environment).

Firmware: Step 2

Make sure the Arduino is connected to the computer via a USB cable before proceeding.

Select the following settings in the Arduino IDE: (note that your serial port number may be different, and is probably not “COM1”)

Firmware: Step 3

Upload and run the Arduino code, then immediately open the Serial Monitor (press Ctrl+Shift+M):

Firmware: Step 4

Check the output of the Serial Monitor. If it was successful, proceed; if not, contact a HoloMonitor development group member.

Firmware: Step 5

At this point, the new resolutions have been downloaded onto the EEPROM chip, and we need to reload the normal Arduino software that controls the galvanometer. Download the normal Arduino software HERE, unzip it, open “MonitorInterruptSweep.ino”, and download/run the code as shown above in Firmware Step 3 (without opening the Serial Monitor). The settings configured in Firmware Step 2 should still be the same. If it downloaded successfully then the Arduino shield should operate as normal in the HoloMonitor.

You can now close the Arduino IDE and unplug the USB cable, as the Arduino board will now run every time the HoloMonitor is turned on.

Firmware: Step 6

Confirm the resolution was changed correctly by plugging the MixAmp board (or 'Smart' VGA Breakout board if you're using it in place of the MixAmp board) into a VGA cable, then into a VGA-DVI adapter, then into a DVI port on the computer. Open the desktop resolution settings by opening Windows Explorer (pres Win+E) and entering

Control Panel\All Control Panel Items\Display

into the address bar.

If the newly-connected MixAmp / 'Smart' VGA board appears grayed-out, then click on the grayed-out box:

Then click on “Multiple Displayes” > “Extend these displays” and “Apply”:

If everything has worked up to now, the screen should flicker, and you should see something like this:

Here, the desired resolution was 4095 x 2476. You should see your new resolution appear here.

If it didn't work, contact a member of the HoloMonitor development group. If, after clicking “Apply”, a message appeared saying “Cannot save settings” or similar, then the resolution settings/data may be bad or the graphics card can't support it.

Software: Step 1

The OpenFrameworks software that generates the graphics and signals needed by the HoloMonitor will need some adjustment.

Fortunately, the only essential change that needs to occur is in the “shader.frag” file, which is opened at runtime and doesn't need to be compiled into a new executable, meaning you don't have to open the project's IDE.

In the “shader.frag” file, under “\bin\data\”, change every instance of 3552 (the old horizontal resolution) to 4095 (the new horizontal resolution). A copy of the result (for the “4095×2476 minimization compromize” resolution) is given below:

#version 150
#extension GL_ARB_texture_rectangle : enable
#extension GL_EXT_gpu_shader4 : enable

#define _PI 3.14159265359

#define MODULATOR_WIDTH 4095.0 // filtering criteria for images when reloading
#define MODULATOR_HEIGHT 2476.0 // filtering criteria for images when reloading
#define N_MAX_IMAGES 6


out vec4 outputColor;

in vec4 gl_FragCoord;//layout(pixel_center_integer)
//vec4 - a struct of 4 floats
//dvec4 - a struct of 4 doubles

uniform sampler2DRect tex0;
uniform sampler2DRect tex1;
uniform sampler2DRect tex2;
uniform sampler2DRect tex3;
uniform sampler2DRect tex4;
uniform sampler2DRect tex5;
uniform sampler2DRect tex6;
uniform sampler2DRect tex7;
uniform sampler2DRect tex8;
uniform sampler2DRect tex9;

uniform float vertGratingK[N_MAX_IMAGES];
uniform int flip_vertical[N_MAX_IMAGES];// I want these to be boolean/unsigned ints, but openFrameworks doesn't have the setUniformXX functions to do so
uniform int roll_vertical[N_MAX_IMAGES];
uniform int flip_horizontal[N_MAX_IMAGES];
uniform int invert_colors[N_MAX_IMAGES];
uniform int y_offset[N_MAX_IMAGES];
uniform int image_enabled[N_MAX_IMAGES];
uniform float relative_amplitude[N_MAX_IMAGES];

uniform int is_preprocessed;
uniform float raw_image_padding;
uniform float raw_image_width;
uniform float raw_image_height;

float pixel_i3, x2, y2, x1, y1;

uniform int n_case_images;
uniform float n_active_images;
uniform int global_y_offset;

uniform float gamma;
uniform float contrast;
uniform float brightness;

// NOTE: this is the GLSL language, which is similar but not the same as the C language
// http://www.opengl.org/wiki/Data_Type_%28GLSL%29
// http://www.openframeworks.cc/doxygen/classof_shader.html

void main()
{
    // gl_FragCoord contains the window relative coordinate for the fragment.
    // please note that all r, g, b, a values are between 0 and 1.



    float carrierR;
	float carrierG;
	float carrierB;
    vec2 modulatorXY;
    vec3 modulatorRGB;
    int img_i;
    outputColor = vec4(0.0, 0.0, 0.0, 1.0);


    for(img_i=0; img_i<n_case_images; img_i++)
		if(image_enabled[img_i] == 1)
		{

			modulatorXY = gl_FragCoord.xy;//get the current pixel location


			// y-offset
			modulatorXY.y -= (y_offset[img_i] + global_y_offset);

			while(modulatorXY.y < 0)
				modulatorXY.y += MODULATOR_HEIGHT;


			// rolling
			int y_int = int(modulatorXY.y - 0.5);//the default OpenGL coordinate system is based on half-pixel coordinates
			int roll_height = 50;
			if(roll_vertical[img_i] == 1)
			{
				modulatorXY.y = float( (y_int/roll_height + 1)*roll_height - y_int%roll_height - 1 ) + 0.5;// restore half-pixel coordinate system
			}


			if(modulatorXY.y < MODULATOR_HEIGHT)
			{
				// vertical flip
				if(flip_vertical[img_i] == 0) {// in OpenGL, pixel (0,0) is in the bottom left-hand corner with X+ right and Y+ up
					modulatorXY.y = MODULATOR_HEIGHT - modulatorXY.y;
				}

				// horizontal flip
				if(flip_horizontal[img_i] == 1)
				{
					modulatorXY.x = MODULATOR_WIDTH - modulatorXY.x;
				}

				//if(modulatorXY.y >= MODULATOR_HEIGHT)
				//    modulatorXY.y -= MODULATOR_HEIGHT;

				//if(modulatorXY.y < 0)
				//    modulatorXY.y += MODULATOR_HEIGHT;

				if(is_preprocessed == 0){
					// preprocessing steps:
					// image 1: original image with padding
					// step 1: stretched to 409500x26
					// image 2: the 409500x26 image
					// step 2: resize to 4095x2476
					// image 3: the 4095x2476 image

					pixel_i3 = modulatorXY.x + 4095.0*modulatorXY.y;
					x2 = float(int(pixel_i3) % 409500);
					y2 = pixel_i3 / 409500.0;
					x1 = (x2*(raw_image_padding + raw_image_width))/409500.0;
					y1 = (y2*raw_image_height)/26.0;

					modulatorXY.x = x1;
					modulatorXY.y = y1;

					//pixel_num = int(modulatorXY.y*MODULATOR_WIDTH + modulatorXY.x);
					//if(pixel_num >= 26*409500){
					//	pixel_num = 26*409500 - 1;
					//}
					//modulatorXY.x = float(pixel_num % 409500);
					//modulatorXY.y = float(pixel_num / 409500);
				}

				if( is_preprocessed == 0 && (x1 >= raw_image_width || y1 >= raw_image_height ))
				{
					// if the calculated raw location is invalid, return a dummy value
					modulatorXY.x = 0;
					modulatorXY.y = 0;
				}

				switch(img_i)
				{
					case 0:
						modulatorRGB = texture2DRect(tex0,modulatorXY).rgb;//get the color of the current pixel
						break;
					case 1:
						modulatorRGB = texture2DRect(tex1,modulatorXY).rgb;//get the color of the current pixel
						break;
					case 2:
						modulatorRGB = texture2DRect(tex2,modulatorXY).rgb;//get the color of the current pixel
						break;
					case 3:
						modulatorRGB = texture2DRect(tex3,modulatorXY).rgb;//get the color of the current pixel
						break;
					case 4:
						modulatorRGB = texture2DRect(tex4,modulatorXY).rgb;//get the color of the current pixel
						break;
					case 5:
						modulatorRGB = texture2DRect(tex5,modulatorXY).rgb;//get the color of the current pixel
						break;
					case 6:
						modulatorRGB = texture2DRect(tex6,modulatorXY).rgb;//get the color of the current pixel
						break;
					case 7:
						modulatorRGB = texture2DRect(tex7,modulatorXY).rgb;//get the color of the current pixel
						break;
					case 8:
						modulatorRGB = texture2DRect(tex8,modulatorXY).rgb;//get the color of the current pixel
						break;
					case 9:
						modulatorRGB = texture2DRect(tex9,modulatorXY).rgb;//get the color of the current pixel
						break;
				}

				if( is_preprocessed == 0 && (x1 >= raw_image_width || y1 >= raw_image_height ))
				{
					// if the calculated raw location is invalid, return a dummy value
					modulatorRGB.r = 0.0;//0.5;
					modulatorRGB.g = 0.0;//0.5;
					modulatorRGB.b = 0.0;//0.5;
				}

				if(invert_colors[img_i] == 1)
				{
					modulatorRGB.r = 1.0-modulatorRGB.r;
					modulatorRGB.g = 1.0-modulatorRGB.g;
					modulatorRGB.b = 1.0-modulatorRGB.b;
				}


				
				modulatorRGB.r = max(min((modulatorRGB.r - .5)*contrast + .5 + brightness, 1.0), 0.0);
				modulatorRGB.g = max(min((modulatorRGB.g - .5)*contrast + .5 + brightness, 1.0), 0.0);
				modulatorRGB.b = max(min((modulatorRGB.b - .5)*contrast + .5 + brightness, 1.0), 0.0);

				modulatorRGB.r = pow(modulatorRGB.r , gamma); // fortunately, colors range between 0-1, so we don't have to do any scaling/rescaling here
				modulatorRGB.g = pow(modulatorRGB.g , gamma);
				modulatorRGB.b = pow(modulatorRGB.b , gamma);
				
				
				/*
				modulatorRGB.r = pow(0.5 , -2.000*modulatorRGB.r + 3.000); // fortunately, colors range between 0-1, so we don't have to do any scaling/rescaling here
				modulatorRGB.g = pow(0.5 , -2.000*modulatorRGB.g + 3.000);
				modulatorRGB.b = pow(0.5 , -2.000*modulatorRGB.b + 3.000);
				*/

				
				
				carrier = relative_amplitude[img_i]*(sin(gl_FragCoord.x * vertGratingK[img_i])*0.5+0.5);

				outputColor += vec4(carrierR * modulatorRGB.r, carrierG * modulatorRGB.g, carrierB * modulatorRGB.b, 0.0);
			}
		}

	if(n_active_images != 0)
	{
		outputColor.r /= n_active_images;
		outputColor.g /= n_active_images;
		outputColor.b /= n_active_images;
	}
	else
	{
		outputColor.r = 0;
		outputColor.g = 0;
		outputColor.b = 0;
	}


	/*
	outputColor.r = max(min((outputColor.r - .5)*contrast + .5 + brightness, 1.0), 0.0);
	outputColor.g = max(min((outputColor.g - .5)*contrast + .5 + brightness, 1.0), 0.0);
	outputColor.b = max(min((outputColor.b - .5)*contrast + .5 + brightness, 1.0), 0.0);

	outputColor.r = pow(outputColor.r , gamma); // fortunately, colors range between 0-1, so we don't have to do any scaling/rescaling here
	outputColor.g = pow(outputColor.g , gamma);
	outputColor.b = pow(outputColor.b , gamma);
	*/
}

For the “4095 x 2476” resolution, no other change is needed to main.cpp, ofApp.cpp, or ofApp.h under “\src\”.

This processed of updating the resolutions will be streamlined in the future.

monitor/custom_resolutions.txt · Last modified: 2017/09/02 20:17 by drhe0208