ecere/gfx/fontRendering: Improved rendering of some fonts e.g. MS Sans Serif having...
[sdk] / ecere / src / gfx / drivers / OpenGLDisplayDriver.ec
index febc303..289d981 100644 (file)
@@ -10,7 +10,7 @@ import "Display"
 import "glab"
 import "immediate"
 import "matrixStack"
-import "shading"
+import "defaultShader"
 
 namespace gfx::drivers;
 
@@ -320,7 +320,7 @@ public void GLSetupTexturing(bool enable)
 {
 #if ENABLE_GL_SHADERS
    if(glCaps_shaders)
-      shader_texturing(enable);
+      defaultShader.texturing(enable);
 #endif
 
 #if ENABLE_GL_FFP
@@ -333,7 +333,7 @@ public void GLSetupFog(bool enable)
 {
 #if ENABLE_GL_SHADERS
    if(glCaps_shaders)
-      shader_fog(enable);
+      defaultShader.fog(enable);
 #endif
 
 #if ENABLE_GL_FFP
@@ -349,7 +349,7 @@ public void GLSetupLighting(bool enable)
    lightingEnabled = enable;
 #if ENABLE_GL_SHADERS
    if(glCaps_shaders)
-      shader_lighting(enable);
+      defaultShader.lighting(enable);
 #endif
 
 #if ENABLE_GL_FFP
@@ -361,6 +361,8 @@ public void GLSetupLighting(bool enable)
 
 /*static */GLuint lastBlitTex;
 
+Shader activeShader;
+
 static int displayWidth, displayHeight;
 
 #define GL_CLAMP_TO_EDGE 0x812F
@@ -368,6 +370,17 @@ static int displayWidth, displayHeight;
 static bool useSingleGLContext = false;
 class OGLDisplay : struct
 {
+   GLCapabilities capabilities, originalCapabilities;
+   bool compat;
+   int version;
+
+   ColorAlpha * flippingBuffer;
+   int flipBufH, flipBufW;
+   bool depthWrite;
+   int x, y;
+   uint vao;
+   int maxTMU;
+
 #if defined(__WIN32__)
    HDC hdc;
    HGLRC glrc;
@@ -397,27 +410,12 @@ class OGLDisplay : struct
    Pixmap shapePixmap;
    X11Picture shapePicture;
 #endif
-
-   GLCapabilities capabilities, originalCapabilities;
-   bool compat;
-   int version;
-
-   ColorAlpha * flippingBuffer;
-   int flipBufH, flipBufW;
-   bool depthWrite;
-   int x, y;
-   uint vao;
 };
 
 class OGLSystem : struct
 {
    int maxTextureSize;
    bool loadingFont;
-#if ENABLE_GL_SHADERS
-   int shadingProgram;
-   int vertexShader;
-   int fragmentShader;
-#endif
 #if defined(__WIN32__)
    PIXELFORMATDESCRIPTOR pfd;
    int format;
@@ -455,6 +453,8 @@ class OGLMesh : struct
 {
    GLAB vertices;
    GLAB normals;
+   GLAB tangents;
+   GLAB lightVectors;
    GLAB texCoords;
    GLAB texCoords2;
    GLAB colors;
@@ -665,7 +665,7 @@ class OpenGLDisplayDriver : DisplayDriver
    {
       GLCapabilities capabilities;
 #if !defined(_GLES2)
-      const char * extensions = (canCheckExtensions && oglDisplay.compat) ? (const char *)glGetString(GL_EXTENSIONS) : null;
+      const char * extensions = (canCheckExtensions && (!oglDisplay || oglDisplay.compat)) ? (const char *)glGetString(GL_EXTENSIONS) : null;
 #endif
 #ifdef DIAGNOSTICS
       printf("extensions: %s\n", extensions);
@@ -705,7 +705,7 @@ class OpenGLDisplayDriver : DisplayDriver
          vao = glBindVertexArray != null && !oglDisplay.compat;
 #endif
 #if ENABLE_GL_FBO
-         shaders = glBindFramebuffer != null;
+         frameBuffer = glBindFramebuffer != null;
 #endif
          vertexBuffer = glBindBuffer != null;
          // mapBuffer = glMapBuffer != null;
@@ -987,12 +987,8 @@ class OpenGLDisplayDriver : DisplayDriver
       }
 
 #if ENABLE_GL_SHADERS
-      if(oglSystem.shadingProgram)
-         glDeleteProgram(oglSystem.shadingProgram);
-      if(oglSystem.fragmentShader)
-         glDeleteShader(oglSystem.fragmentShader);
-      if(oglSystem.vertexShader)
-         glDeleteShader(oglSystem.vertexShader);
+      defaultShader.free();
+      activeShader = null;
 #endif
 
       delete oglSystem.shortBDBuffer;
@@ -1022,6 +1018,8 @@ class OpenGLDisplayDriver : DisplayDriver
          XFree(oglSystem.visualInfo);
    #endif
       }
+      if(oglSystem.glContext)
+        glXDestroyContext(xGlobalDisplay, oglSystem.glContext);
 
       if(oglSystem.glxDrawable)
       {
@@ -1043,12 +1041,6 @@ class OpenGLDisplayDriver : DisplayDriver
       if(loadExtensions && ogl_LoadFunctions() == ogl_LOAD_FAILED)
          PrintLn("ogl_LoadFunctions() failed!");
       CheckCapabilities(oglSystem, oglDisplay, canCheckExtensions);
-   #ifdef GL_DEBUGGING
-      if(oglDisplay.capabilities.debug)
-         setupDebugging();
-   #else
-      oglDisplay.capabilities.debug = false;
-   #endif
 #endif
 
       {
@@ -1093,6 +1085,13 @@ class OpenGLDisplayDriver : DisplayDriver
          oglSystem.capabilities = oglDisplay.capabilities;
       }
 
+   #ifdef GL_DEBUGGING
+      if(oglDisplay.capabilities.debug)
+         setupDebugging();
+   #else
+      oglDisplay.capabilities.debug = false;
+   #endif
+
 #if ENABLE_GL_VAO
       if(oglDisplay.capabilities.vao)
       {
@@ -1116,7 +1115,7 @@ class OpenGLDisplayDriver : DisplayDriver
             glDisableClientState(GL_COLOR_ARRAY);
          }
 #endif
-         loadShaders(display.displaySystem, "<:ecere>shaders/fixed.vertex", "<:ecere>shaders/fixed.frag");
+         defaultShader.select();
       }
 #if ENABLE_GL_LEGACY
       else
@@ -1125,7 +1124,11 @@ class OpenGLDisplayDriver : DisplayDriver
          glDisableVertexAttribArray(GLBufferContents::normal);
          glDisableVertexAttribArray(GLBufferContents::texCoord);
          glDisableVertexAttribArray(GLBufferContents::vertex);
+         glDisableVertexAttribArray(GLBufferContents::tangent1);
+         glDisableVertexAttribArray(GLBufferContents::tangent2);
+#if ENABLE_GL_VAO
          glBindVertexArray(0);
+#endif
          glUseProgram(0);
       }
 #endif
@@ -1169,10 +1172,10 @@ class OpenGLDisplayDriver : DisplayDriver
       if(!glCaps_shaders)
       {
          glShadeModel(GL_FLAT);
-         /*
+
          #define GL_LIGHT_MODEL_LOCAL_VIEWER 0x0B51
          GLLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER, GL_TRUE);
-         */
+
 #if !defined(_GLES)
          ;//GLLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR);
 #endif
@@ -1711,6 +1714,9 @@ class OpenGLDisplayDriver : DisplayDriver
 #endif
       GLABBindBuffer(GL_ARRAY_BUFFER, 0);
       GLABBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
+#if ENABLE_GL_SHADERS
+      activeProgram = 0;
+#endif
    }
 
    void EndUpdate(Display display)
@@ -1892,12 +1898,14 @@ class OpenGLDisplayDriver : DisplayDriver
       return result;
    }
 
-   bool MakeDDBitmap(DisplaySystem displaySystem, Bitmap bitmap, bool mipMaps)
+   bool MakeDDBitmap(DisplaySystem displaySystem, Bitmap bitmap, bool mipMaps, uint cubeMapFace)
    {
       bool result = false;
       OGLSystem oglSystem = displaySystem.driverData;
       GLCapabilities capabilities = oglSystem.capabilities;
       Bitmap convBitmap = bitmap;
+      bool oldStyleCubeMap = (cubeMapFace >> 3) != 0;
+      int face = (cubeMapFace & 7) - 1;
       if(bitmap.keepData)
       {
          convBitmap = { };
@@ -1909,7 +1917,8 @@ class OpenGLDisplayDriver : DisplayDriver
       {
          int c, level;
          uint w = bitmap.width, h = bitmap.height;
-         GLuint glBitmap = 0;
+         GLuint glBitmap = cubeMapFace && face > 0 ? (GLuint)(uintptr)bitmap.driverData : 0;
+         int target = cubeMapFace ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
          if(!capabilities.nonPow2Textures)
          {
             w = pow2i(w);
@@ -1927,7 +1936,8 @@ class OpenGLDisplayDriver : DisplayDriver
          // Switch ARGB to RGBA
          //if(bitmap.format != pixelFormatRGBA)
          {
-            for(c=0; c<bitmap.size; c++)
+            int size = convBitmap.stride * convBitmap.height;
+            for(c = 0; c < size; c++)
             {
                // ((ColorRGBA *)bitmap.picture)[c] = ((ColorAlpha *)bitmap.picture)[c];
                // TODO:
@@ -1937,27 +1947,70 @@ class OpenGLDisplayDriver : DisplayDriver
          }
          // convBitmap.pixelFormat = pixelFormat888;
 
+         if(cubeMapFace && oldStyleCubeMap)
+         {
+            if(face == 0 || face == 1 || face == 4 || face == 5)
+            {
+               uint w = convBitmap.width;
+               uint32 * tmp = new uint [convBitmap.width];
+               int x, y;
+               for(y = 0; y < convBitmap.height; y++)
+               {
+                  uint32 * pic = (uint32 *)((byte *)convBitmap.picture + y * w * 4);
+                  for(x = 0; x < w; x++)
+                     tmp[x] = pic[w-1-x];
+                  memcpy(pic, tmp, w*4);
+               }
+               delete tmp;
+            }
+            else if(face == 2 || face == 3)
+            {
+               int y;
+               Bitmap tmp { };
+               tmp.Allocate(null, convBitmap.width, convBitmap.height, 0, convBitmap.pixelFormat, false);
+               for(y = 0; y < convBitmap.height; y++)
+               {
+                  memcpy(tmp.picture + convBitmap.width * 4 * y,
+                     convBitmap.picture + (convBitmap.height-1-y) * convBitmap.width * 4,
+                     convBitmap.width * 4);
+               }
+               memcpy(convBitmap.picture, tmp.picture, convBitmap.sizeBytes);
+               delete tmp;
+            }
+         }
+
          glGetError();
-         glGenTextures(1, &glBitmap);
+         if(!glBitmap)
+            glGenTextures(1, &glBitmap);
          if(glBitmap == 0)
          {
             //int error = glGetError();
             return false;
          }
 
-         glBindTexture(GL_TEXTURE_2D, glBitmap);
+         glBindTexture(target, glBitmap);
          glPixelStorei( GL_UNPACK_ALIGNMENT, 1 );
 
-         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, mipMaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
-         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+         glTexParameteri(target, GL_TEXTURE_MIN_FILTER, mipMaps ? GL_LINEAR_MIPMAP_LINEAR : GL_LINEAR);
+         glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+
+         //glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
 
-         //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
+         //glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP);
+         //glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP);
 
-         //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
-         //glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);
+         glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+         glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+
+#ifndef GL_TEXTURE_WRAP_R
+   #define GL_TEXTURE_WRAP_R 0x8072
+#endif
+
+#if !defined(__EMSCRIPTEN__)
+         if(cubeMapFace)
+            glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
+#endif
 
-         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-         glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
 #ifdef GL_TEXTURE_MAX_ANISOTROPY_EXT
          glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 16.0 );
 #endif
@@ -1980,6 +2033,7 @@ class OpenGLDisplayDriver : DisplayDriver
                if(mipMap.Allocate(null, w, h, w, convBitmap.pixelFormat, false))
                {
                   Surface mipSurface = mipMap.GetSurface(0,0,null);
+                  mipSurface.blend = false;
                   mipSurface.Filter(convBitmap, 0,0,0,0, w, h, convBitmap.width, convBitmap.height);
                   delete mipSurface;
                }
@@ -1998,7 +2052,7 @@ class OpenGLDisplayDriver : DisplayDriver
                //int width = 0;
                glGetError();
                // glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA8, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipMap.picture);
-               glTexImage2D(GL_TEXTURE_2D, level, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipMap.picture);
+               glTexImage2D(cubeMapFace ? GL_TEXTURE_CUBE_MAP_POSITIVE_X + face : GL_TEXTURE_2D, level, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, mipMap.picture);
                //printf("Calling glTexImage2D\n");
                //glGetTexLevelParameteriv(GL_TEXTURE_2D, level, GL_TEXTURE_WIDTH, &width);
                //printf("width = %d (Should be %d, %d)\n", width, w, h);
@@ -2024,8 +2078,8 @@ class OpenGLDisplayDriver : DisplayDriver
             FreeBitmap(displaySystem, bitmap);
          else if(oglSystem.loadingFont)
          {
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
+            glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
+            glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
             oglSystem.loadingFont = false;
          }
       }
@@ -2634,7 +2688,7 @@ class OpenGLDisplayDriver : DisplayDriver
 
    void WriteText(Display display, Surface surface, int x, int y, const char * text, int len, int prevGlyph, int * rPrevGlyph)
    {
-      if(len && text[0])
+      if(len && text[0] && surface.font)
       {
          OGLSurface oglSurface = surface.driverData;
          OGLSystem oglSystem = display.displaySystem.driverData;
@@ -2654,20 +2708,24 @@ class OpenGLDisplayDriver : DisplayDriver
 
          GLSetupTexturing(true);
 
+         x <<= 6;
          if(surface.font.outlineSize)
          {
             ColorAlpha outlineColor = surface.outlineColor;
+            int fx = x;
+
             GLColor4ub(outlineColor.color.r, outlineColor.color.g, outlineColor.color.b, outlineColor.a);
             oglSurface.writingOutline = true;
             lastBlitTex = 0;
-            ((subclass(DisplayDriver))class(LFBDisplayDriver)).WriteText(display, surface, x, y, text, len, prevGlyph, rPrevGlyph);
+            oglSurface.font.ProcessString(surface.displaySystem, (const byte *)text, len, true, surface, display, &fx, y, prevGlyph, rPrevGlyph, null);
             if(lastBlitTex) GLEnd();
             oglSurface.writingOutline = false;
          }
          GLColor4fv(oglSurface.foreground);
 
          lastBlitTex = 0;
-         ((subclass(DisplayDriver))class(LFBDisplayDriver)).WriteText(display, surface, x, y, text, len, prevGlyph, rPrevGlyph);
+         oglSurface.font.ProcessString(surface.displaySystem, (const byte *)text, len, true, surface, display, &x, y, prevGlyph, rPrevGlyph, null);
+
          if(lastBlitTex) GLEnd();
          lastBlitTex = 0;
 
@@ -2738,6 +2796,24 @@ class OpenGLDisplayDriver : DisplayDriver
          }
       }
    }
+#if ENABLE_GL_FFP
+   void ::disableRemainingTMUs(Display display, int lastTMU)
+   {
+      OGLDisplay oglDisplay = display.driverData;
+      int t;
+      for(t = lastTMU; t < oglDisplay.maxTMU; t++)
+      {
+         glActiveTexture(GL_TEXTURE0 + t);
+         glClientActiveTexture(GL_TEXTURE0 + t);
+         glDisable(GL_TEXTURE_2D);
+         glDisable(GL_TEXTURE_CUBE_MAP);
+         GLDisableClientState(TEXCOORDS);
+      }
+      glActiveTexture(GL_TEXTURE0);
+      glClientActiveTexture(GL_TEXTURE0);
+      oglDisplay.maxTMU = lastTMU;
+   }
+#endif
 
 #if !defined(ECERE_NO3D) && !defined(ECERE_VANILLA)
    void SetRenderState(Display display, RenderState state, uint value)
@@ -2771,7 +2847,7 @@ class OpenGLDisplayDriver : DisplayDriver
             float color[4] = { ((Color)value).r/255.0f, ((Color)value).g/255.0f, ((Color)value).b/255.0f, 1.0f };
 #if ENABLE_GL_SHADERS
             if(glCaps_shaders)
-               shader_fogColor(color[0], color[1], color[2]);
+               defaultShader.setFogColor(color[0], color[1], color[2]);
 #endif
 
 #if ENABLE_GL_FFP
@@ -2783,7 +2859,7 @@ class OpenGLDisplayDriver : DisplayDriver
          case fogDensity:
 #if ENABLE_GL_SHADERS
             if(glCaps_shaders)
-               shader_fogDensity((float)(RenderStateFloat { ui = value }.f * nearPlane));
+               defaultShader.setFogDensity((float)(RenderStateFloat { ui = value }.f * nearPlane));
 #endif
 
 #if ENABLE_GL_FFP
@@ -2800,7 +2876,7 @@ class OpenGLDisplayDriver : DisplayDriver
          {
 #if ENABLE_GL_SHADERS
             if(glCaps_shaders)
-               shader_setGlobalAmbient(((Color)value).r / 255.0f, ((Color)value).g / 255.0f, ((Color)value).b / 255.0f, 1.0f);
+               defaultShader.setGlobalAmbient(((Color)value).r / 255.0f, ((Color)value).g / 255.0f, ((Color)value).b / 255.0f, 1.0f);
 #endif
 
 #if ENABLE_GL_FFP
@@ -2832,22 +2908,25 @@ class OpenGLDisplayDriver : DisplayDriver
    {
 #if ENABLE_GL_SHADERS
       if(glCaps_shaders)
-         shader_setLight(display, id, light);
+         defaultShader.setLight(display, id, light);
 #endif
 
 #if ENABLE_GL_FFP
       if(!glCaps_shaders)
       {
-         if(light != null)
+         if(light != null && !light.flags.off)
          {
             Object lightObject = light.lightObject;
             float position[4] = { 0, 0, 0, 0 };
             float color[4] = { 0, 0, 0, 1 };
+            Vector3D l;
 
             glEnable(GL_LIGHT0 + id);
 
             if(!light.multiplier) light.multiplier = 1.0f;
 
+            GLFlushMatrices();
+
             color[0] = light.diffuse.r * light.multiplier;
             color[1] = light.diffuse.g * light.multiplier;
             color[2] = light.diffuse.b * light.multiplier;
@@ -2857,6 +2936,7 @@ class OpenGLDisplayDriver : DisplayDriver
             color[1] = light.ambient.g * light.multiplier;
             color[2] = light.ambient.b * light.multiplier;
             glLightfv(GL_LIGHT0 + id, GL_AMBIENT, color);
+
             color[0] = light.specular.r * light.multiplier;
             color[1] = light.specular.g * light.multiplier;
             color[2] = light.specular.b * light.multiplier;
@@ -2864,84 +2944,13 @@ class OpenGLDisplayDriver : DisplayDriver
 
             if(lightObject)
             {
-               Vector3D positionVector;
-               if(light.flags.spot)
-               {
-                  if(lightObject.flags.root || !lightObject.parent)
-                  {
-                     positionVector = lightObject.transform.position;
-                     positionVector.Subtract(positionVector, display.display3D.camera.cPosition);
-                  }
-                  else
-                  {
-                     positionVector.MultMatrix(lightObject.transform.position, lightObject.parent.matrix);
-                     if(display.display3D.camera)
-                        positionVector.Subtract(positionVector, display.display3D.camera.cPosition);
-                  }
-                  position[3] = 1;
-               }
-               else
-               {
-                  if(!light.direction.x && !light.direction.y && !light.direction.z)
-                  {
-                     Vector3Df vector { 0,0,-1 };
-                     Matrix mat;
-                     mat.RotationQuaternion(light.orientation);
-                     positionVector.MultMatrixf(vector, mat);
-                  }
-                  else
-                  {
-                     positionVector = light.direction;
-                     position[3] = 1;
-                  }
-               }
-
-               position[0] = (float)positionVector.x;
-               position[1] = (float)positionVector.y;
-               position[2] = (float)positionVector.z;
-
-               glLightfv(GL_LIGHT0 + id, GL_POSITION, position);
-
-               /*
-               // Display Light Position
-               glDisable(GL_LIGHTING);
-               glDisable(GL_DEPTH_TEST);
-               glColor3f(1,1,1);
-               glPointSize(10);
-               glBegin(GL_POINTS);
-               glVertex3fv(position);
-               glEnd();
-               glEnable(GL_DEPTH_TEST);
-               glEnable(GL_LIGHTING);
-
-
-               // Display Target
-               if(lightObject.flags.root || !lightObject.parent)
-               {
-                  positionVector = light.target.transform.position;
-                  positionVector.Subtract(positionVector, display.camera.cPosition);
-               }
-               else
-               {
-                  positionVector.MultMatrix(light.target.transform.position,
-                     lightObject.light.target.parent.matrix);
-                  positionVector.Subtract(positionVector, display.camera.cPosition);
-               }
+               // Positional Lights, including Spot Lights (and omni light with flags.spot not set)
+               Matrix * mat = &lightObject.matrix;
+               l = { mat->m[3][0], mat->m[3][1], mat->m[3][2] };
+               if(display.display3D && display.display3D.camera)
+                  l.Subtract(l, display.display3D.camera.cPosition);
 
-               position[0] = positionVector.x;
-               position[1] = positionVector.y;
-               position[2] = positionVector.z;
-
-               glDisable(GL_LIGHTING);
-               glDisable(GL_DEPTH_TEST);
-               glColor3f(1,1,0);
-               glPointSize(10);
-               glBegin(GL_POINTS);
-               glVertex3fv(position);
-               glEnd();
-               glEnable(GL_DEPTH_TEST);
-               glEnable(GL_LIGHTING);
-               */
+               position[0] = (float)l.x, position[1] = (float)l.y, position[2] = (float)l.z, position[3] = 1;
 
                if(light.flags.attenuation)
                {
@@ -2949,34 +2958,104 @@ class OpenGLDisplayDriver : DisplayDriver
                   glLightf(GL_LIGHT0 + id, GL_LINEAR_ATTENUATION, light.Kl);
                   glLightf(GL_LIGHT0 + id, GL_QUADRATIC_ATTENUATION, light.Kq);
                }
+               else
+               {
+                  glLightf(GL_LIGHT0 + id, GL_CONSTANT_ATTENUATION, 1);
+                  glLightf(GL_LIGHT0 + id, GL_LINEAR_ATTENUATION, 0);
+                  glLightf(GL_LIGHT0 + id, GL_QUADRATIC_ATTENUATION, 0);
+               }
 
-               if(light.flags.spot)
+               if((light.flags.spot && light.fallOff < 360) || (lightObject && (light.direction.x || light.direction.y || light.direction.z)))
                {
-                  float exponent = 0;
+                  // Figure out exponent out of the hot spot
                   #define MAXLIGHT  0.9
+                  float exponent = light.flags.spot ? (float)(log(MAXLIGHT) / log(cos(light.hotSpot / 2))) : 1;
+                  Degrees cutOff = light.flags.spot ? light.fallOff/2 : 90;
                   float direction[4] = { (float)light.direction.x, (float)light.direction.y, (float)light.direction.z, 1 };
-                  // Figure out exponent out of the hot spot
-                  exponent = (float)(log(MAXLIGHT) / log(cos((light.hotSpot / 2))));
 
                   glLightfv(GL_LIGHT0 + id, GL_SPOT_DIRECTION, direction);
-                  glLightf(GL_LIGHT0 + id, GL_SPOT_CUTOFF, (float)(light.fallOff / 2));
+                  glLightf(GL_LIGHT0 + id, GL_SPOT_CUTOFF, (float)cutOff);
                   glLightf(GL_LIGHT0 + id, GL_SPOT_EXPONENT, exponent);
                }
+               else
+               {
+                  float d[4] = { 0, 0, 1, 0 };
+                  glLightfv(GL_LIGHT0 + id, GL_SPOT_DIRECTION, d);
+                  glLightf(GL_LIGHT0 + id, GL_SPOT_CUTOFF, 180);
+                  glLightf(GL_LIGHT0 + id, GL_SPOT_EXPONENT, 1);
+               }
+
+               /*
+               if(lightObject)
+               {
+                  // Display Light Position
+                  glDisable(GL_LIGHTING);
+                  glDisable(GL_DEPTH_TEST);
+                  glColor3f(1,1,1);
+                  glPointSize(10);
+                  glBegin(GL_POINTS);
+                  glVertex3fv(position);
+                  glEnd();
+                  glEnable(GL_DEPTH_TEST);
+                  glEnable(GL_LIGHTING);
+
+
+                  // Display Target
+                  if(lightObject.flags.root || !lightObject.parent)
+                  {
+                     positionVector = light.target.transform.position;
+                     positionVector.Subtract(positionVector, display.camera.cPosition);
+                  }
+                  else
+                  {
+                     positionVector.MultMatrix(light.target.transform.position,
+                        lightObject.light.target.parent.matrix);
+                     positionVector.Subtract(positionVector, display.camera.cPosition);
+                  }
+
+                  position[0] = positionVector.x;
+                  position[1] = positionVector.y;
+                  position[2] = positionVector.z;
+
+                  glDisable(GL_LIGHTING);
+                  glDisable(GL_DEPTH_TEST);
+                  glColor3f(1,1,0);
+                  glPointSize(10);
+                  glBegin(GL_POINTS);
+                  glVertex3fv(position);
+                  glEnd();
+                  glEnable(GL_DEPTH_TEST);
+                  glEnable(GL_LIGHTING);
+               }
+               */
             }
             else
             {
-               Vector3Df vector { 0,0,-1 };
-               Vector3Df direction;
+               // Directional Light
+               Vector3D vector { 0,0,-1 };
+               Vector3D direction;
                Matrix mat;
-
                mat.RotationQuaternion(light.orientation);
                direction.MultMatrix(vector, mat);
-
-               position[0] = direction.x;
-               position[1] = direction.y;
-               position[2] = direction.z;
-
-               glLightfv(GL_LIGHT0 + id, GL_POSITION, position);
+               l.Normalize(direction);
+               position[0] = (float)l.x, position[1] = (float)l.y, position[2] = (float)l.z, position[3] = 0;
+            }
+            glLightfv(GL_LIGHT0 + id, GL_POSITION, position);
+            if(display.display3D)
+            {
+               Matrix m;
+               Vector3Df v { position[0], position[1], position[2] };
+               Vector3Df l;
+               float * lp = display.display3D.light0Pos;
+               if(display.display3D.camera)
+                  m = display.display3D.camera.viewMatrix;
+               else
+                  m.Identity();
+               l.MultMatrix(v, m);
+               lp[0] = l.x;
+               lp[1] =-l.y;
+               lp[2] =-l.z;
+               lp[3] = position[3];
             }
          }
          else
@@ -3005,6 +3084,11 @@ class OpenGLDisplayDriver : DisplayDriver
          // *** ViewPort ***
          glViewport(x, y, w, h);
 
+         GLMatrixMode(MatrixMode::texture);
+         if(!display.display3D.camera)
+            GLPushMatrix();
+         GLLoadIdentity();
+
          // *** Projection Matrix ***
          GLMatrixMode(MatrixMode::projection);
          if(!display.display3D.camera)
@@ -3049,6 +3133,14 @@ class OpenGLDisplayDriver : DisplayDriver
          // *** View Matrix ***
          GLMultMatrixd(camera.viewMatrix.array);
 
+#if ENABLE_GL_SHADERS
+         if(glCaps_shaders)
+         {
+            defaultShader.select();
+            defaultShader.setCamera(camera);
+         }
+#endif
+
          // *** Lights ***
          // ...
 
@@ -3075,15 +3167,66 @@ class OpenGLDisplayDriver : DisplayDriver
          glDisable(GL_CULL_FACE);
          glDisable(GL_DEPTH_TEST);
 
+         GLDisableClientState(COLORS);
+#if ENABLE_GL_SHADERS
+         if(glCaps_shaders)
+         {
+            GLDisableClientState(TANGENTS1);
+            GLDisableClientState(TANGENTS2);
+         }
+#endif
+         GLDisableClientState(NORMALS);
+#if ENABLE_GL_FFP
+         if(!glCaps_shaders)
+            GLDisableClientState(LIGHTVECTORS);
+#endif
+
+         // *** Restore 2D MODELVIEW Matrix ***
+         GLMatrixMode(MatrixMode::modelView);
+         GLPopMatrix();
+
+         // *** Restore 2D TEXTURE Matrix ***
+         GLMatrixMode(MatrixMode::texture);
+         GLPopMatrix();
+
+         // *** Restore 2D PROJECTION Matrix ***
+         GLMatrixMode(MatrixMode::projection);
+         GLPopMatrix();
+
+         // NOTE: We expect the 2D projection matrix to be the active one for GetSurface to call glOrtho()
+
+#if ENABLE_GL_SHADERS
+         if(glCaps_shaders)
+            defaultShader.select();
+#endif
+
+#if ENABLE_GL_FFP
+         if(!glCaps_shaders)
+         {
+            disableRemainingTMUs(display, 0);
+            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+
+            glDisable(GL_TEXTURE_CUBE_MAP);
+         #if _GLES
+            glDisable(GL_TEXTURE_GEN_STR);
+         #else
+            glDisable(GL_TEXTURE_GEN_R);
+            glDisable(GL_TEXTURE_GEN_S);
+            glDisable(GL_TEXTURE_GEN_T);
+         #endif
+         }
+#endif
+
          GLSetupTexturing(false);
          GLSetupLighting(false);
          GLSetupFog(false);
 
-         GLDisableClientState(COLORS);
-
 #if ENABLE_GL_SHADERS
          if(glCaps_shaders)
-            shader_setPerVertexColor(false);
+         {
+            defaultShader.setPerVertexColor(false);
+            defaultShader.setMaterial(null, 0);
+         }
 #endif
 
 #if ENABLE_GL_FFP
@@ -3094,25 +3237,31 @@ class OpenGLDisplayDriver : DisplayDriver
 #if !defined(__EMSCRIPTEN__)
          glDisable(GL_MULTISAMPLE);
 #endif
-
-         // *** Restore 2D MODELVIEW Matrix ***
-         GLPopMatrix();
-
-         // *** Restore 2D PROJECTION Matrix ***
-         GLMatrixMode(MatrixMode::projection);
-         GLPopMatrix();
       }
-
    }
 
    void ApplyMaterial(Display display, Material material, Mesh mesh)
    {
+      Shader shader = material.shader ? material.shader : defaultShader;
+      MaterialFlags flags = material.flags;
+#if ENABLE_GL_FFP
+      static int lastSeparate = 0;
+      int tmu = 0;
+      bool normalMapped = false;
+      OGLMesh oglMesh = mesh ? mesh.data : null;
+#endif
+
+#if ENABLE_GL_SHADERS
+      if(glCaps_shaders && shader)
+         shader.select();
+#endif
+
       // Basic Properties
-      if(material.flags.doubleSided)
+      if(flags.doubleSided)
       {
 #if ENABLE_GL_FFP
          if(!glCaps_shaders)
-            GLLightModeli(GL_LIGHT_MODEL_TWO_SIDE, !material.flags.singleSideLight);
+            GLLightModeli(GL_LIGHT_MODEL_TWO_SIDE, !flags.singleSideLight);
 #endif
          glDisable(GL_CULL_FACE);
       }
@@ -3126,43 +3275,413 @@ class OpenGLDisplayDriver : DisplayDriver
       }
 
       // Fog
-      GLSetupFog(!material.flags.noFog);
+      GLSetupFog(!flags.noFog);
+
+#if ENABLE_GL_SHADERS
+      if(glCaps_shaders)
+         activeShader.setMaterial(material, mesh.flags);
+#endif
+
+#if ENABLE_GL_FFP
+      if(!glCaps_shaders)
+      {
+         if(material.bumpMap && mesh.lightVectors)
+         {
+            float color[4] = { 1,1,1,1 };
+            glActiveTexture(GL_TEXTURE0 + tmu);
+            glClientActiveTexture(GL_TEXTURE0 + tmu++);
+            glBindTexture(GL_TEXTURE_2D, (GLuint)(uintptr)material.bumpMap.driverData);
+            glDisable(GL_TEXTURE_CUBE_MAP);
+            glEnable(GL_TEXTURE_2D);
+
+            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_DOT3_RGB);
+            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_REPLACE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PRIMARY_COLOR);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+
+            if(0) //((DefaultShaderBits)defaultShader.state).debugging)
+            {
+               glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);
+               glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PRIMARY_COLOR);
+               glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_RGB, GL_CONSTANT);
+               glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR);
+               glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color );
+            }
 
+         #if _GLES
+            glDisable(GL_TEXTURE_GEN_STR);
+         #else
+            glDisable(GL_TEXTURE_GEN_R);
+            glDisable(GL_TEXTURE_GEN_S);
+            glDisable(GL_TEXTURE_GEN_T);
+         #endif
+            glDisable(GL_LIGHTING);
+            lightingEnabled = false;
+
+            GLMatrixMode(GL_TEXTURE);
+            GLLoadIdentity();
+            if(material.uScale && material.vScale)
+               GLScalef(material.uScale, material.vScale, 1);
+            GLMatrixMode(MatrixMode::modelView);
+
+            if(flags.tile)
+            {
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+            }
+            else
+            {
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+            }
+
+            glActiveTexture(GL_TEXTURE0 + tmu);
+            glClientActiveTexture(GL_TEXTURE0 + tmu);
+
+            normalMapped = true;
+
+            // Modulate base color
+            if(material.diffuse.r < 1 || material.diffuse.g < 1 || material.diffuse.b < 1)
+            {
+               tmu++;
+               glDisable(GL_TEXTURE_CUBE_MAP);
+               glEnable(GL_TEXTURE_2D);
+               glBindTexture(GL_TEXTURE_2D, (GLuint)(uintptr)material.bumpMap.driverData);
+               glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+               glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
+               glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+
+               glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
+               glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
+               glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+               glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+
+               glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
+               glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_CONSTANT);
+               glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+               glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+
+               color[0] = material.diffuse.r, color[1] = material.diffuse.g, color[2] = material.diffuse.b, color[3] = 1.0;
+               glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color );
+               glActiveTexture(GL_TEXTURE0 + tmu);
+               glClientActiveTexture(GL_TEXTURE0 + tmu);
+            }
+
+            // Add ambient light
+            {
+               ColorRGB ambient { material.ambient.r * 0.2f, material.ambient.g * 0.2f, material.ambient.g * 0.2f };
+               if(ambient.r > 0 || ambient.g > 0 || ambient.b > 0)
+               {
+                  tmu++;
+                  glDisable(GL_TEXTURE_CUBE_MAP);
+                  glEnable(GL_TEXTURE_2D);
+                  glBindTexture(GL_TEXTURE_2D, (GLuint)(uintptr)material.bumpMap.driverData);
+                  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+                  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_ADD);
+                  glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+
+                  glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
+                  glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_CONSTANT);
+                  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+                  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+
+                  glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
+                  glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_CONSTANT);
+                  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+                  glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+
+                  color[0] = ambient.r, color[1] = ambient.g, color[2] = ambient.b, color[3] = 1.0;
+                  glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color );
+                  glActiveTexture(GL_TEXTURE0 + tmu);
+                  glClientActiveTexture(GL_TEXTURE0 + tmu);
+               }
+            }
+         }
+         else
+         {
+            GLDisableClientState(LIGHTVECTORS);
+            if(!lightingEnabled)
+            {
+               glEnable(GL_LIGHTING);
+               lightingEnabled = true;
+            }
+         }
+      }
+#endif
       // Maps
-      if(material.baseMap && (mesh.texCoords || mesh.flags.texCoords1))
+      if(flags.cubeMap || (material.baseMap && (mesh.texCoords || mesh.flags.texCoords1)))
       {
          Bitmap map = material.baseMap;
-         GLSetupTexturing(true);
-         glBindTexture(GL_TEXTURE_2D, (GLuint)(uintptr)map.driverData);
+         int diffuseTarget = flags.cubeMap ? GL_TEXTURE_CUBE_MAP : GL_TEXTURE_2D;
+
+#if ENABLE_GL_FFP
+         if(!glCaps_shaders)
+         {
+            glActiveTexture(GL_TEXTURE0 + tmu);
+            glClientActiveTexture(GL_TEXTURE0 + tmu++);
+            glEnable(diffuseTarget);
+            glDisable(flags.cubeMap ? GL_TEXTURE_2D : GL_TEXTURE_CUBE_MAP);
+         }
+#endif
+
+#if ENABLE_GL_SHADERS
+         if(glCaps_shaders && !flags.cubeMap)
+            GLSetupTexturing(true);
+#endif
+
+         glBindTexture(diffuseTarget, (GLuint)(uintptr)map.driverData);
+
+#if ENABLE_GL_FFP
+         if(!glCaps_shaders)
+         {
+            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
+            /* // This did not have the desired effect with a GL_ALPHA texture
+            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_TEXTURE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+            */
+
+            if(flags.cubeMap)
+            {
+            #if _GLES
+               glEnable(GL_TEXTURE_GEN_STR);
+               // GL_OBJECT_LINEAR: No extension support?
+               // glTexGeni(GL_TEXTURE_GEN_STR_OES, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+            #else
+               GLfloat xPlane[] = { 1.0f, 0.0f, 0.0f, 0 };
+               GLfloat yPlane[] = { 0.0f,-1.0f, 0.0f, 0 };
+               GLfloat zPlane[] = { 0.0f, 0.0f,-1.0f, 0 };
+               glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+               glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+               glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR);
+               glTexGenfv(GL_R, GL_OBJECT_PLANE, zPlane);
+               glTexGenfv(GL_S, GL_OBJECT_PLANE, xPlane);
+               glTexGenfv(GL_T, GL_OBJECT_PLANE, yPlane);
+
+               glEnable(GL_TEXTURE_GEN_R);
+               glEnable(GL_TEXTURE_GEN_S);
+               glEnable(GL_TEXTURE_GEN_T);
+            #endif
+
+               GLDisableClientState(TEXCOORDS);
+            }
+            else
+            {
+            #if _GLES
+               glDisable(GL_TEXTURE_GEN_STR);
+            #else
+               glDisable(GL_TEXTURE_GEN_R);
+               glDisable(GL_TEXTURE_GEN_S);
+               glDisable(GL_TEXTURE_GEN_T);
+            #endif
+
+               if(tmu > 1)
+                  oglMesh.texCoords.use(texCoord, 2, GL_FLOAT, 0, oglMesh.texCoords.buffer ? null : mesh.texCoords);
+               GLEnableClientState(TEXCOORDS);
+            }
+            glClientActiveTexture(GL_TEXTURE0);
+         }
+#endif
+         if(flags.tile)
+         {
+            glTexParameteri(diffuseTarget, GL_TEXTURE_WRAP_S, GL_REPEAT);
+            glTexParameteri(diffuseTarget, GL_TEXTURE_WRAP_T, GL_REPEAT);
+         }
+         else
+         {
+            glTexParameteri(diffuseTarget, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
+            glTexParameteri(diffuseTarget, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+         }
+      }
+      else
+      {
+         GLSetupTexturing(false);
+      }
 
+#if ENABLE_GL_FFP && !defined(_GLES)
+      if(!glCaps_shaders)
+      {
+         int separate = material.flags.separateSpecular ? GL_SEPARATE_SPECULAR_COLOR : GL_SINGLE_COLOR;
+         if(separate != lastSeparate)
+         {
+            GLLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, separate);
+            lastSeparate = separate;
+         }
+      }
+#endif
+
+      if((flags.cubeMap && material.baseMap) ||
+         (mesh.texCoords && (material.baseMap || material.bumpMap || material.specularMap || material.reflectMap)))
+      {
+#if ENABLE_GL_FFP
+         if(!glCaps_shaders)
+         {
+            glActiveTexture(GL_TEXTURE0 + tmu - 1);
+            glClientActiveTexture(GL_TEXTURE0 + tmu - 1);
+         }
+#endif
          GLMatrixMode(GL_TEXTURE);
          GLLoadIdentity();
          if(material.uScale && material.vScale)
             GLScalef(material.uScale, material.vScale, 1);
          GLMatrixMode(MatrixMode::modelView);
+#if ENABLE_GL_FFP
+         if(!glCaps_shaders)
+         {
+            glActiveTexture(GL_TEXTURE0);
+            glClientActiveTexture(GL_TEXTURE0);
+         }
+#endif
+      }
 
-         if(material.flags.tile)
+#if ENABLE_GL_FFP
+      if(!glCaps_shaders)
+      {
+         if(material.envMap && material.refractiveIndex)
          {
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
+            float color[4] = { material.opacity, material.opacity, material.opacity, 1.0 };
+            glActiveTexture(GL_TEXTURE0 + tmu);
+            glClientActiveTexture(GL_TEXTURE0 + tmu++);
+            glBindTexture(GL_TEXTURE_CUBE_MAP, (GLuint)(uintptr)material.envMap.driverData);
+            glEnable(GL_TEXTURE_CUBE_MAP);
+         #if _GLES
+            glEnable(GL_TEXTURE_GEN_STR);
+            glTexGeni(GL_TEXTURE_GEN_STR_OES, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
+         #else
+            glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
+            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
+            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
+            glEnable(GL_TEXTURE_GEN_R);
+            glEnable(GL_TEXTURE_GEN_S);
+            glEnable(GL_TEXTURE_GEN_T);
+         #endif
+
+            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+
+            if(normalMapped)
+               glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
+            else
+               glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
+
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_TEXTURE);
+
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+
+            if(!normalMapped)
+            {
+               glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_RGB, GL_CONSTANT);
+               glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_ALPHA, GL_CONSTANT);
+               glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR);
+               glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, GL_SRC_ALPHA);
+            }
+
+            glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color );
+
+            GLMatrixMode(MatrixMode::texture);
+            {
+               double * s = display.display3D.camera.viewMatrix.array;
+               double k = 2.0;
+               Matrix m
+               { {
+                  k*s[0],-k*s[4],-k*s[8], 0,
+                  k*s[1],-k*s[5],-k*s[9], 0,
+                  k*s[2],-k*s[6],-k*s[10],0,
+                  0,0,0,1
+               } };
+               GLLoadMatrixd(m.array);
+            }
+            GLMatrixMode(MatrixMode::modelView);
          }
-         else
+
+         if(material.envMap && material.reflectivity)
          {
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
-            glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
+            float color[4] = { 1.0f - material.reflectivity, 1.0f - material.reflectivity, 1.0f - material.reflectivity, 1.0 };
+            glActiveTexture(GL_TEXTURE0 + tmu);
+            glClientActiveTexture(GL_TEXTURE0 + tmu++);
+            glBindTexture(GL_TEXTURE_CUBE_MAP, (GLuint)(uintptr)material.envMap.driverData);
+            glEnable(GL_TEXTURE_CUBE_MAP);
+         #if _GLES
+            glEnable(GL_TEXTURE_GEN_STR);
+            glTexGeni(GL_TEXTURE_GEN_STR_OES, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
+         #else
+            glTexGeni(GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
+            glTexGeni(GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
+            glTexGeni(GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP);
+            glEnable(GL_TEXTURE_GEN_R);
+            glEnable(GL_TEXTURE_GEN_S);
+            glEnable(GL_TEXTURE_GEN_T);
+         #endif
+
+            glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_COMBINE);
+
+            if(normalMapped)
+               glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_MODULATE);
+            else
+               glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_RGB, GL_INTERPOLATE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_COMBINE_ALPHA, GL_MODULATE);
+
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_RGB, GL_PREVIOUS);
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC0_ALPHA, GL_PREVIOUS);
+
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_RGB, GL_TEXTURE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_SRC1_ALPHA, GL_TEXTURE);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_RGB, GL_SRC_COLOR);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_RGB, GL_SRC_COLOR);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND0_ALPHA, GL_SRC_ALPHA);
+            glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND1_ALPHA, GL_SRC_ALPHA);
+
+            if(!normalMapped)
+            {
+               glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_RGB, GL_CONSTANT);
+               glTexEnvi(GL_TEXTURE_ENV, GL_SRC2_ALPHA, GL_CONSTANT);
+               glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_RGB, GL_SRC_COLOR);
+               glTexEnvi(GL_TEXTURE_ENV, GL_OPERAND2_ALPHA, GL_SRC_ALPHA);
+            }
+
+            glTexEnvfv(GL_TEXTURE_ENV, GL_TEXTURE_ENV_COLOR, color );
+
+            GLMatrixMode(MatrixMode::texture);
+            {
+               double * s = display.display3D.camera.inverseTranspose.array;
+               Matrix m
+               { {
+                  s[0],s[1],-s[2],0,
+                  s[4],-s[5],-s[6],0,
+                  -s[8],s[9],s[10],0,
+                  0,0,0,1
+               } };
+               GLLoadMatrixd(m.array);
+            }
+            GLMatrixMode(MatrixMode::modelView);
          }
       }
-      else
-         GLSetupTexturing(false);
-
-#if ENABLE_GL_SHADERS
-      if(glCaps_shaders)
-         shader_setMaterial(material, mesh.flags.colors);
 #endif
 
 #if ENABLE_GL_FFP
       if(!glCaps_shaders)
       {
+         disableRemainingTMUs(display, tmu);
+
          if(mesh.flags.colors)
          {
             GLColorMaterial(GL_FRONT_AND_BACK, GL_AMBIENT_AND_DIFFUSE);
@@ -3180,15 +3699,20 @@ class OpenGLDisplayDriver : DisplayDriver
                glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, color);
             }
          }
+         if(material.power > 0.1)
          {
             float color[4] = { material.specular.r, material.specular.g, material.specular.b, 0 };
             glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, color);
          }
+         else
+         {
+            float color[4] = { 0,0,0,0 };
+            glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, color);
+         }
          {
             float color[4] = { material.emissive.r, material.emissive.g, material.emissive.b, 0 };
             glMaterialfv(GL_FRONT_AND_BACK, GL_EMISSION, color);
          }
-
          glMaterialfv(GL_FRONT_AND_BACK, GL_SHININESS, &material.power);
       }
 #endif
@@ -3200,30 +3724,42 @@ class OpenGLDisplayDriver : DisplayDriver
       if(oglMesh)
       {
          OGLSystem oglSystem = displaySystem.driverData;
+         GLCapabilities caps = glCaps;
          SETCAPS(oglSystem.capabilities);
+
          if(!mesh.flags.vertices)
          {
-            oglMesh.vertices.free(glCaps_vertexBuffer);
+            oglMesh.vertices.free();
             delete mesh.vertices;
          }
          if(!mesh.flags.normals)
          {
-            oglMesh.normals.free(glCaps_vertexBuffer);
+            oglMesh.normals.free();
             delete mesh.normals;
          }
+         if(!mesh.flags.tangents)
+         {
+            oglMesh.tangents.free();
+            delete mesh.tangents;
+         }
+         if(!mesh.flags.lightVectors)
+         {
+            oglMesh.lightVectors.free();
+            delete mesh.lightVectors;
+         }
          if(!mesh.flags.texCoords1)
          {
-            oglMesh.texCoords.free(glCaps_vertexBuffer);
+            oglMesh.texCoords.free();
             delete mesh.texCoords;
          }
          if(!mesh.flags.texCoords2)
          {
-            oglMesh.texCoords2.free(glCaps_vertexBuffer);
+            oglMesh.texCoords2.free();
             // delete mesh.texCoords2;
          }
          if(!mesh.flags.colors)
          {
-            oglMesh.colors.free(glCaps_vertexBuffer);
+            oglMesh.colors.free();
             delete mesh.colors;
          }
          if(!mesh.flags)
@@ -3231,6 +3767,7 @@ class OpenGLDisplayDriver : DisplayDriver
             delete oglMesh;
             mesh.data = null;
          }
+         SETCAPS(caps);
       }
    }
 
@@ -3265,6 +3802,14 @@ class OpenGLDisplayDriver : DisplayDriver
                   else
                      mesh.normals = new Vector3Df[nVertices];
                }
+               if(!mesh.flags.tangents && flags.tangents)
+               {
+                  mesh.tangents = new Vector3Df[2*nVertices];
+               }
+               if(!mesh.flags.lightVectors && flags.lightVectors)
+               {
+                  mesh.lightVectors = new ColorRGB[nVertices];
+               }
                if(!mesh.flags.texCoords1 && flags.texCoords1)
                {
                   mesh.texCoords = new Pointf[nVertices];
@@ -3305,6 +3850,14 @@ class OpenGLDisplayDriver : DisplayDriver
             {
                mesh.colors = renew mesh.colors ColorRGBAf[nVertices];
             }
+            if(flags.tangents)
+            {
+               mesh.tangents = renew mesh.tangents Vector3Df[2 * nVertices];
+            }
+            if(flags.lightVectors)
+            {
+               mesh.lightVectors = renew mesh.lightVectors ColorRGB[nVertices];
+            }
          }
          result = true;
       }
@@ -3314,7 +3867,9 @@ class OpenGLDisplayDriver : DisplayDriver
    void UnlockMesh(DisplaySystem displaySystem, Mesh mesh, MeshFeatures flags)
    {
       OGLSystem oglSystem = displaySystem.driverData;
+      GLCapabilities caps = glCaps;
       SETCAPS(oglSystem.capabilities);
+
       if(glCaps_vertexBuffer)
       {
          OGLMesh oglMesh = mesh.data;
@@ -3334,7 +3889,14 @@ class OpenGLDisplayDriver : DisplayDriver
          if(flags.colors)
             oglMesh.colors.allocate(
                mesh.nVertices * sizeof(ColorRGBAf), mesh.colors, staticDraw);
+
+         if(flags.tangents)
+            oglMesh.tangents.allocate(mesh.nVertices * 2*sizeof(Vector3Df), mesh.tangents, staticDraw);
+
+         if(flags.lightVectors)
+            oglMesh.lightVectors.allocate(mesh.nVertices * sizeof(ColorRGB), mesh.lightVectors, staticDraw);
       }
+      SETCAPS(caps);
    }
 
    bool LockMesh(DisplaySystem displaySystem, Mesh mesh, MeshFeatures flags)
@@ -3347,13 +3909,16 @@ class OpenGLDisplayDriver : DisplayDriver
    void FreeIndices(DisplaySystem displaySystem, OGLIndices oglIndices)
    {
       OGLSystem oglSystem = displaySystem.driverData;
+      GLCapabilities caps = glCaps;
       SETCAPS(oglSystem.capabilities);
+
       if(oglIndices)
       {
-         oglIndices.buffer.free(glCaps_vertexBuffer);
+         oglIndices.buffer.free();
          delete oglIndices.indices;
          delete oglIndices;
       }
+      SETCAPS(caps);
    }
 
    void * AllocateIndices(DisplaySystem displaySystem, int nIndices, bool indices32bit)
@@ -3370,7 +3935,9 @@ class OpenGLDisplayDriver : DisplayDriver
    void UnlockIndices(DisplaySystem displaySystem, OGLIndices oglIndices, bool indices32bit, int nIndices)
    {
       OGLSystem oglSystem = displaySystem.driverData;
+      GLCapabilities caps = glCaps;
       SETCAPS(oglSystem.capabilities);
+
       if(glCaps_vertexBuffer)
       {
          if(!glCaps_intAndDouble && indices32bit)
@@ -3401,6 +3968,7 @@ class OpenGLDisplayDriver : DisplayDriver
                nIndices * (indices32bit ? sizeof(uint32) : sizeof(uint16)),
                oglIndices.indices, staticDraw);
       }
+      SETCAPS(caps);
    }
 
    uint16 * LockIndices(DisplaySystem displaySystem, OGLIndices oglIndices)
@@ -3437,6 +4005,25 @@ class OpenGLDisplayDriver : DisplayDriver
             else
                GLDisableClientState(NORMALS);
 
+#if ENABLE_GL_SHADERS
+            if(glCaps_shaders)
+            {
+               // *** Tangents Stream ***
+               if(mesh.tangents || mesh.flags.tangents)
+               {
+                  GLEnableClientState(TANGENTS1);
+                  GLEnableClientState(TANGENTS2);
+                  oglMesh.tangents.use(tangent1, 3, GL_FLOAT, sizeof(Vector3Df)*2, oglMesh.tangents.buffer ? null : mesh.tangents);
+                  oglMesh.tangents.use(tangent2, 3, GL_FLOAT, sizeof(Vector3Df)*2, oglMesh.tangents.buffer ? (void *)sizeof(Vector3Df) : mesh.tangents+1);
+               }
+               else
+               {
+                  GLDisableClientState(TANGENTS1);
+                  GLDisableClientState(TANGENTS2);
+               }
+            }
+#endif
+
             // *** Texture Coordinates Stream ***
             if(mesh.texCoords || mesh.flags.texCoords1)
             {
@@ -3446,6 +4033,20 @@ class OpenGLDisplayDriver : DisplayDriver
             else
                GLDisableClientState(TEXCOORDS);
 
+#if ENABLE_GL_FFP
+            if(!glCaps_shaders)
+            {
+               // *** Normal Map Aligned Light Vector ***
+               if(mesh.lightVectors || mesh.flags.lightVectors)
+               {
+                  GLEnableClientState(LIGHTVECTORS);
+                  oglMesh.lightVectors.use(lightVector, 3, GL_FLOAT, 0, oglMesh.lightVectors.buffer ? null : mesh.lightVectors);
+               }
+               else
+                  GLDisableClientState(LIGHTVECTORS);
+            }
+            else
+#endif
             // *** Color Stream ***
             if(mesh.colors || mesh.flags.colors)
             {
@@ -3465,6 +4066,24 @@ class OpenGLDisplayDriver : DisplayDriver
             }
             else
                GLDisableClientState(NORMALS);
+#if ENABLE_GL_SHADERS
+            if(glCaps_shaders)
+            {
+               if((mesh.tangents || mesh.flags.tangents) && !display.display3D.collectingHits)
+               {
+                  GLEnableClientState(TANGENTS1);
+                  GLEnableClientState(TANGENTS2);
+                  noAB.use(tangent1, 3, GL_FLOAT, sizeof(Vector3Df)*2, mesh.tangents);
+                  noAB.use(tangent2, 3, GL_FLOAT, sizeof(Vector3Df)*2, mesh.tangents+1);
+               }
+               else
+               {
+                  GLDisableClientState(TANGENTS1);
+                  GLDisableClientState(TANGENTS2);
+               }
+            }
+#endif
+
             if((mesh.texCoords || mesh.flags.texCoords1) && !display.display3D.collectingHits)
             {
                GLEnableClientState(TEXCOORDS);
@@ -3472,6 +4091,20 @@ class OpenGLDisplayDriver : DisplayDriver
             }
             else
                GLDisableClientState(TEXCOORDS);
+
+#if ENABLE_GL_FFP
+            if(!glCaps_shaders)
+            {
+               if((mesh.lightVectors || mesh.flags.lightVectors) && !display.display3D.collectingHits)
+               {
+                  GLEnableClientState(LIGHTVECTORS);
+                  noAB.use(lightVector, 3, GL_FLOAT, sizeof(ColorRGB), mesh.lightVectors);
+               }
+               else
+                  GLDisableClientState(LIGHTVECTORS);
+            }
+            else
+#endif
             if((mesh.colors || mesh.flags.colors) && !display.display3D.collectingHits)
             {
                GLEnableClientState(COLORS);