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);
}
}
}
}