Why is an OpenGL positional (spot) light interfering with non-positional light

I have a legacy OpenGL fixed-pipeline app which has been ported from Windows (32-bit) to MacOS 64-bit.

The problem is that if I have a scene with a non-positional light, everything works great. If I add a positional spotlight the two lights interact, and I get incorrect results.

This problem does not occur on X86_64 Macs. It does occur when the app is X86_64 running under Rosetta or native ARM64.

So it's either an Apple Silicon OpenGL driver behaviour my code is triggering, or something with the on-chip Apple Silicon graphics.

Here is the "normal" case: the spotlight is to the right:

Here, I have moved the spotlight down (Y = 1). Notice the black areas on the cube. That's incorrect.

Now, I turn off the spotlight by commenting out the "makeALight" call for the spotlight (light 6). Now, the cube is evenly lit.

Here is the test code I use to generate the lights. You will need to install glfw with brew to build it.

#define GL_SILENCE_DEPRECATION 1

#include 
#include 
#include 
#include 
#define STB_IMAGE_IMPLEMENTATION

// Function to generate a checkerboard texture
GLuint generateCheckerboardTexture(int width, int height) {
    int checkerSize = 8; // Size of each square in the checkerboard
    GLubyte *data = new GLubyte[width * height * 3];

    for (int y = 0; y < height; ++y) {
        for (int x = 0; x < width; ++x) {
            int checkerX = x / checkerSize;
            int checkerY = y / checkerSize;
            bool isWhite = (checkerX + checkerY) % 2 == 0;

            GLubyte color = isWhite ? 255 : 0;

            data[(y * width + x) * 3 + 0] = color; // Red
            data[(y * width + x) * 3 + 1] = color; // Green
            data[(y * width + x) * 3 + 2] = color; // Blue
        }
    }

    GLuint textureID;
    glGenTextures(1, &textureID);
    glBindTexture(GL_TEXTURE_2D, textureID);

    // Set texture parameters
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

    // Upload the texture data
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

    // Clean up
    delete[] data;

    return textureID;
}


// Function to create and configure a light source
void makeALight(int index, bool positional,
                GLfloat posX, GLfloat posY, GLfloat posZ,
                GLfloat dirX, GLfloat dirY, GLfloat dirZ,
                GLfloat ambR, GLfloat ambG, GLfloat ambB, GLfloat ambA,
                GLfloat diffR, GLfloat diffG, GLfloat diffB, GLfloat diffA,
                GLfloat specR, GLfloat specG, GLfloat specB, GLfloat specA,
                float beamWidth,
                float constantA, float linearA, float quadA) {
    GLenum glLightIndex = GL_LIGHT0 + index;

    glEnable(glLightIndex);

    GLfloat ambColor[] = {ambR, ambG, ambB, ambA};
    GLfloat diffColor[] = {diffR, diffG, diffB, diffA};
    GLfloat specColor[] = {specR, specG, specB, specA};

    glLightfv(glLightIndex, GL_AMBIENT, ambColor);
    glLightfv(glLightIndex, GL_DIFFUSE, diffColor);
    glLightfv(glLightIndex, GL_SPECULAR, specColor);

    glLightf(glLightIndex, GL_CONSTANT_ATTENUATION, constantA);
    glLightf(glLightIndex, GL_LINEAR_ATTENUATION, linearA);
    glLightf(glLightIndex, GL_QUADRATIC_ATTENUATION, quadA);

    if (positional) {
        GLfloat pos[] = {posX, posY, posZ, 1.0f};
        glLightfv(glLightIndex, GL_POSITION, pos);

        if (beamWidth != 180.0f) {
            glLightf(glLightIndex, GL_SPOT_CUTOFF, beamWidth);

            GLfloat direction[] = {dirX, dirY, dirZ};
            glLightfv(glLightIndex, GL_SPOT_DIRECTION, direction);
        } else {
            glLightf(glLightIndex, GL_SPOT_CUTOFF, 180.0f);
        }
    } else {
        GLfloat pos[] = {dirX, dirY, dirZ, 0.0f};
        glLightfv(glLightIndex, GL_POSITION, pos);
    }
}

// Function to render a textured cube
void renderTexturedCube(GLuint textureID) {
    glEnable(GL_TEXTURE_2D);
    glBindTexture(GL_TEXTURE_2D, textureID);

    glBegin(GL_QUADS);

    // Front Face
    glNormal3f(0.0f, 0.0f, 1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, 1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, 1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, 1.0f);

    // Back Face
    glNormal3f(0.0f, 0.0f, -1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);

    // Top Face
    glNormal3f(0.0f, 1.0f, 0.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);

    // Bottom Face
    glNormal3f(0.0f, -1.0f, 0.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);

    // Right face
    glNormal3f(1.0f, 0.0f, 0.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f( 1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f( 1.0f,  1.0f, -1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f( 1.0f,  1.0f,  1.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f( 1.0f, -1.0f,  1.0f);

    // Left Face
    glNormal3f(-1.0f, 0.0f, 0.0f);
    glTexCoord2f(0.0f, 0.0f); glVertex3f(-1.0f, -1.0f, -1.0f);
    glTexCoord2f(1.0f, 0.0f); glVertex3f(-1.0f, -1.0f,  1.0f);
    glTexCoord2f(1.0f, 1.0f); glVertex3f(-1.0f,  1.0f,  1.0f);
    glTexCoord2f(0.0f, 1.0f); glVertex3f(-1.0f,  1.0f, -1.0f);

    glEnd();

    glDisable(GL_TEXTURE_2D);
}

// Function to render a cube with explicit material properties
// Function to render a cube with explicit metallic material properties
void renderCube() {
    // Define metallic material properties
    GLfloat materialAmbient[] = {0.25f, 0.25f, 0.25f, 1.0f}; // Low ambient reflection
    GLfloat materialDiffuse[] = {0.4f, 0.4f, 0.4f, 1.0f};    // Slightly higher diffuse reflection
    GLfloat materialSpecular[] = {0.77f, 0.77f, 0.77f, 1.0f}; // High specular reflection
    GLfloat materialShininess = 76.8f;                        // High shininess for a metallic look

    // Apply the material properties to the cube
    glMaterialfv(GL_FRONT, GL_AMBIENT, materialAmbient);
    glMaterialfv(GL_FRONT, GL_DIFFUSE, materialDiffuse);
    glMaterialfv(GL_FRONT, GL_SPECULAR, materialSpecular);
    glMaterialf(GL_FRONT, GL_SHININESS, materialShininess);

    glBegin(GL_QUADS);

    // Front Face
    glNormal3f(0.0f, 0.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);

    // Back Face
    glNormal3f(0.0f, 0.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);

    // Top Face
    glNormal3f(0.0f, 1.0f, 0.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);

    // Bottom Face
    glNormal3f(0.0f, -1.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);

    // Right Face
    glNormal3f(1.0f, 0.0f, 0.0f);
    glVertex3f(1.0f, -1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, -1.0f);
    glVertex3f(1.0f, 1.0f, 1.0f);
    glVertex3f(1.0f, -1.0f, 1.0f);

    // Left Face
    glNormal3f(-1.0f, 0.0f, 0.0f);
    glVertex3f(-1.0f, -1.0f, -1.0f);
    glVertex3f(-1.0f, -1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, 1.0f);
    glVertex3f(-1.0f, 1.0f, -1.0f);

    glEnd();
}

int main() {
    // Initialize GLFW
    if (!glfwInit()) {
        std::cerr << "Failed to initialize GLFW" << std::endl;
        return -1;
    }
    
    // Create a windowed mode window and its OpenGL context
    // Set the OpenGL version to 2.1
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);
    GLFWwindow* window = glfwCreateWindow(800, 600, "OpenGL Lighting Test", nullptr, nullptr);
    if (!window) {
        std::cerr << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    
    // Make the window's context current
    glfwMakeContextCurrent(window);
    
    // Set viewport
    glViewport(0, 0, 800, 600);
    
    // Set up projection matrix
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(45.0f, 800.0f / 600.0f, 0.1f, 100.0f);
    
    // Set up modelview matrix
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(0.0f, 0.0f, 5.0f,   // Eye position
              0.0f, 0.0f, 0.0f,   // Look-at point
              0.0f, 1.0f, 0.0f);  // Up vector
    
    // Enable depth testing
    glEnable(GL_DEPTH_TEST);
    
    GLuint textureID = generateCheckerboardTexture(256, 256);

    // Enable lighting
    glEnable(GL_LIGHTING);
    
    // Set shading model to smooth
    glShadeModel(GL_SMOOTH);
    
    //
    float amb[] = { 0.70, 0.70, 0.70, 1 };
    glLightModelfv(GL_LIGHT_MODEL_AMBIENT, amb );

    // Set up the lights using the provided makeALight calls
    
    makeALight(6, true,
               7.0f, 1.0f, 0.0f,          // Position
               0.0f, -1.0f, 0.0f,           // Direction
               1.0f, 0.00f, 0.00f, 1.0f,   // Ambient
               1.0f, 0.0f, 0.0f, 1.00f,     // Diffuse
               1.0f, 0.00f, 0.00f, 1.00f,  // Specular
               55.0f,                       // Angle
               0.0f, 0.09f, 0.0f);          // Constant, Linear, Quad
    
    makeALight(0, false,
               0.0f, 0.0f, 0.0f,           // Position
               0.00f, 0.00f, -1.00f,       // Direction
               0.0f, 1.0f, 0.0f, 1.00f,       // Ambient
               0.0f, 1.0f, 0.0f, 1.00f,       // Diffuse
               0.0f, 1.0f, 0.0f, 1.00f,       // Specular
               180.0f,                      // Angle
               1.0f, 0.0f, 0.0f);           // Constant, Linear, Quad
    
    // Main loop
    while (!glfwWindowShouldClose(window)) {
        // Render here
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        
        // Rotate the cube over time
        glPushMatrix();
        glRotatef((float)glfwGetTime() * 50.0f, 1.0f, 1.0f, 0.0f);
        // renderCube();
      renderTexturedCube(textureID);
        glPopMatrix();
        
        // Swap front and back buffers
        glfwSwapBuffers(window);
        
        // Poll for and process events
        glfwPollEvents();
    }
    
  // Clean up and exit
  glDeleteTextures(1, &textureID);
  glfwDestroyWindow(window);
  glfwTerminate();
  return 0;
}
Why is an OpenGL positional (spot) light interfering with non-positional light
 
 
Q