glCopyTexSubImage2D darkens the color

I’m writing an emulator. Its virtual framebuffer was originally fully software rendered, and I used OpenGL just to scale-blit on the screen. Now I’m implementing hardware-accelerated rendering. The framebuffer is initially rendered to the back buffer at a fixed dimension. Then, the pixels are read to a texture with glCopyTexSubImage2D, which is then scale-blitted right before the buffer swap. I cannot render scaled objects directly because that’s not correct emulation.

I tested on 2 machines, and on one of them, the color appears to be considerably darker. But when I show the initial rendering directly without the read-back to a texture, the colors on both machines are identical. So I’m supposing glCopyTexSubImage2D is causing the color values to be changed, whether it’s due to my bad code or less likely a driver bug.

The images are in the order of machine 1 - machine 2 - machine 1 direct - machine 2 direct. machine 1 is where it becomes considerably darker (Windows 10, Intel Xe integrated graphics).

If you see carefully, even the direct versions have a slight difference in color, but that’s in acceptable range.

The actual code is already quite complex, so I wrote a minimal program just to show the issue.

This is where all the rendering is done for the example images, and I’m suspecting something has been done wrong here. The full code is also posted at the bottom. Using legacy OpenGL is because Windows on virtual machines is one of my targets.

//#define DIRECT
static void paint(int w, int h) {
  glViewport(0, 0, FB_W, FB_H);
  glClear(GL_COLOR_BUFFER_BIT);
  glColor4f(1, 1, 1, 1);
  glRectf(0, 0, FB_W, FB_H);
  glColor4f(0.3f, 0.5f, 0.7f, 0.7f);
  glRectf(0, 0, FB_W - 50, FB_H - 50);
  glColor4f(0.7f, 0.5f, 0.3f, 0.3f);
  glRectf(50, 50, FB_W, FB_H);
  #if !defined(DIRECT)
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, FB_W, FB_H);
    set_viewport(w, h);
    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_TEXTURE_2D);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glDisable(GL_TEXTURE_2D);
  #endif
}
#define SDL_MAIN_HANDLED
#include <math.h>
#include <GL/gl.h>
#include "SDL2/SDL.h"
#if defined(_WIN32)
  #include <windows.h>
#endif

#define FB_W 320
#define FB_H 240
#define FB_TEX_W 0x200
#define FB_TEX_H 0x100
static const float VERTEX_COORD[] = {0, FB_H - FB_TEX_H, FB_TEX_W,
  FB_H - FB_TEX_H, 0, FB_H, FB_TEX_W, FB_H};
static const float TEX_COORD[] = {0, 1, 1, 1, 0, 0, 1, 0};

static void set_viewport(int w, int h) {
  int p = w * FB_H;
  int q = h * FB_W;
  if (p > q) {
    float w_ = (float)q / FB_H;
    glViewport(roundf((w - w_) * 0.5f), 0, roundf(w_), h);
  } else {
    float h_ = (float)p / FB_W;
    glViewport(0, roundf((h - h_) * 0.5f), w, roundf(h_));
  }
}

//#define DIRECT
static void paint(int w, int h) {
  glViewport(0, 0, FB_W, FB_H);
  glClear(GL_COLOR_BUFFER_BIT);
  glColor4f(1, 1, 1, 1);
  glRectf(0, 0, FB_W, FB_H);
  glColor4f(0.3f, 0.5f, 0.7f, 0.7f);
  glRectf(0, 0, FB_W - 50, FB_H - 50);
  glColor4f(0.7f, 0.5f, 0.3f, 0.3f);
  glRectf(50, 50, FB_W, FB_H);
  #if !defined(DIRECT)
    glCopyTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 0, 0, FB_W, FB_H);
    set_viewport(w, h);
    glClear(GL_COLOR_BUFFER_BIT);
    glEnable(GL_TEXTURE_2D);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    glDisable(GL_TEXTURE_2D);
  #endif
}

int main() {
  SDL_SetMainReady();
  #if defined(_WIN32)
    HMODULE lib = LoadLibraryA("user32");
    BOOL (WINAPI *SetProcessDPIAware)(void) = GetProcAddress(lib,
      "SetProcessDPIAware");
    if (SetProcessDPIAware) {
      SetProcessDPIAware();
    }
    FreeLibrary(lib);
  #endif
  SDL_Init(SDL_INIT_VIDEO);
  SDL_Window *window = SDL_CreateWindow("test", SDL_WINDOWPOS_UNDEFINED,
    SDL_WINDOWPOS_UNDEFINED, 350, 350,
    SDL_WINDOW_RESIZABLE | SDL_WINDOW_OPENGL);
  SDL_GLContext gl = SDL_GL_CreateContext(window);
  glShadeModel(GL_FLAT);
  glEnable(GL_BLEND);
  glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
  glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_REPLACE);
  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_MAG_FILTER, GL_NEAREST);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
  glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, FB_TEX_W, FB_TEX_H, 0, GL_BGRA_EXT,
    GL_UNSIGNED_BYTE, 0);
  glOrtho(0, FB_W, FB_H, 0, 1, -1);
  glEnableClientState(GL_VERTEX_ARRAY);
  glEnableClientState(GL_TEXTURE_COORD_ARRAY);
  glVertexPointer(2, GL_FLOAT, 0, VERTEX_COORD);
  glTexCoordPointer(2, GL_FLOAT, 0, TEX_COORD);
  SDL_Event e;
  while (SDL_WaitEvent(&e)) {
    switch (e.type) {
    case SDL_QUIT:
      return 0;
    case SDL_WINDOWEVENT:
      if (e.window.event == SDL_WINDOWEVENT_EXPOSED) {
        int w, h;
        SDL_GL_GetDrawableSize(window, &w, &h);
        paint(w, h);
        SDL_GL_SwapWindow(window);
      }
    }
  }
}

glColor3f sets a current color that applies to all subsequent drawing, not just the one immediately after it. So the final glColor4f in your code is also being applied to your glDrawArrays call. You need to set the current color back to 1,1,1,1 unless that’s what you want. So simply put another glColor4f(1,1,1,1) in there before making the glDrawArrays call.

GL_TEXTURE_ENV_MODE is set to GL_REPLACE so shouldn’t the color not be applied? (Is this correct English…)

The colors that get darker have a decreased alpha value in the framebuffer. The machine where it doesn’t get dark has GL_ALPHA_BITS == 0 . OpenGL blending changes the destination alpha value, and the decreased alpha value after blending seems to affect the color when read in to a texture.

Yep, the alpha channel in the framebuffer doesn’t affect the color displayed to the screen but it does affect the color when being copied to a texture. I simply masked the alpha channel with glColorMask, and it works as expected!

My bad… it wasn’t glCopyTexSubImage2d that changed the color, but the fact that I blended with the semi-transparent pixels copied from the framebuffer. Enabling and disabling blending at the right moment was the correct solution.

1 Like