gui
gui copied to clipboard
On Windows, calling `with-gl-context` during `canvas%` initialization breaks rendering
I cannot get OpenGL contexts created by Racket’s canvas%
to render anything when using the OpenGL programmable pipeline (aka OpenGL 3.0+ “Core”) on Windows. I wrote the following program to test this, using the autogenerated OpenGL bindings in the opengl
package, which draws a white triangle on a black background using both the fixed-function pipeline and the programmable pipeline:
#lang racket/base
(require ffi/vector
opengl
racket/class
racket/gui/base)
(define (gl-gen1 glGen)
(u32vector-ref (glGen 1) 0))
(define (gl-create+compile-shader type src-string)
(define src-bytes (string->bytes/utf-8 src-string))
(define shader (glCreateShader type))
(glShaderSource shader 1 (vector src-bytes) (s32vector (bytes-length src-bytes)))
(glCompileShader shader)
shader)
(define vertex-data (f32vector 0.0 1.0
-1.0 -1.0
1.0 -1.0))
(define vertex-shader-source #<<GLSL
#version 330 core
layout(location = 0) in vec2 position;
void main(void) {
gl_Position.xy = position;
gl_Position.zw = vec2(0.0, 1.0);
}
GLSL
)
(define fragment-shader-source #<<GLSL
#version 330 core
out vec4 color;
void main(void) {
color = vec4(1.0, 1.0, 1.0, 1.0);
}
GLSL
)
(define triangle-canvas:fixed-function%
(class canvas%
(inherit with-gl-context swap-gl-buffers)
(define gl-config (new gl-config%))
(send gl-config set-legacy? #t)
(super-new [style '(gl no-autoclear)]
[gl-config gl-config])
(define/override (on-paint)
(with-gl-context
(λ ()
(glClearColor 0.0 0.0 0.0 0.0)
(glClear (bitwise-ior GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT))
(glVertexPointer 2 GL_FLOAT 0 vertex-data)
(glEnableClientState GL_VERTEX_ARRAY)
(glDrawArrays GL_TRIANGLES 0 3)
(glDisableClientState GL_VERTEX_ARRAY)
(swap-gl-buffers))))))
(define triangle-canvas:shader%
(class canvas%
(inherit with-gl-context swap-gl-buffers)
(define gl-config (new gl-config%))
(send gl-config set-legacy? #f)
(super-new [style '(gl no-autoclear)]
[gl-config gl-config])
(define-values [program vertex-buffer]
(with-gl-context
(λ ()
(glBindVertexArray (gl-gen1 glGenVertexArrays))
(define vertex-shader (gl-create+compile-shader GL_VERTEX_SHADER vertex-shader-source))
(define fragment-shader (gl-create+compile-shader GL_FRAGMENT_SHADER fragment-shader-source))
(define program (glCreateProgram))
(glAttachShader program vertex-shader)
(glAttachShader program fragment-shader)
(glLinkProgram program)
(glDetachShader program vertex-shader)
(glDetachShader program fragment-shader)
(glDeleteShader vertex-shader)
(glDeleteShader fragment-shader)
(define vertex-buffer (gl-gen1 glGenBuffers))
(glBindBuffer GL_ARRAY_BUFFER vertex-buffer)
(glBufferData GL_ARRAY_BUFFER (gl-vector-sizeof vertex-data) vertex-data GL_STATIC_DRAW)
(values program vertex-buffer))))
(define/override (on-paint)
(with-gl-context
(λ ()
(glClearColor 0.0 0.0 0.0 0.0)
(glClear (bitwise-ior GL_COLOR_BUFFER_BIT GL_DEPTH_BUFFER_BIT))
(glUseProgram program)
(glEnableVertexAttribArray 0)
(glBindBuffer GL_ARRAY_BUFFER vertex-buffer)
(glVertexAttribPointer 0 2 GL_FLOAT #f 0 0)
(glDrawArrays GL_TRIANGLES 0 3)
(glDisableVertexAttribArray 0)
(swap-gl-buffers))))))
(define (show-frame label triangle-canvas%)
(define frame (new frame% [label label] [width 300] [height 300]))
(new triangle-canvas% [parent frame])
(send frame show #t))
(show-frame "Fixed-Function" triangle-canvas:fixed-function%)
(show-frame "Shader" triangle-canvas:shader%)
It’s a bit long, but OpenGL is just really verbose. On my MacBook Pro, running macOS 10.14 with an NVIDIA GeForce GT 750M graphics card, I get the following output:

However, on my friend’s laptop, running Windows 7 with an NVIDIA Quadro NVS 4200M, I get this instead:

What’s more, the equivalent program written in C using GLFW and GLEW renders the shader-based triangle just fine:
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
static const char* vertex_shader_source =
"#version 330 core\n"
"layout(location = 0) in vec2 position;\n"
"void main(void) {\n"
" gl_Position.xy = position;\n"
" gl_Position.zw = vec2(0.0, 1.0);\n"
"}\n";
static const char* fragment_shader_source =
"#version 330 core\n"
"out vec4 color;\n"
"void main(void) {\n"
" color = vec4(1.0, 1.0, 1.0, 1.0);\n"
"}\n";
static const GLfloat vertex_data[] = {
0.0f, 1.0f,
-1.0f, -1.0f,
1.0f, -1.0f
};
GLuint create_and_compile_shader(GLenum type, const char* src) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
return shader;
}
int main(void) {
if (!glfwInit()) {
fprintf(stderr, "Failed to initialize GLFW\n");
return 1;
}
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(300, 300, "Shaders", NULL, NULL);
if (!window) {
fprintf(stderr, "Failed to open GLFW window\n");
return 1;
}
glfwMakeContextCurrent(window);
glewExperimental = true;
if (glewInit() != GLEW_OK) {
fprintf(stderr, "Failed to initialize GLEW\n");
return 1;
}
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
GLuint vao;
glGenVertexArrays(1, &vao);
glBindVertexArray(vao);
GLuint vertex_shader = create_and_compile_shader(GL_VERTEX_SHADER, vertex_shader_source);
GLuint fragment_shader = create_and_compile_shader(GL_FRAGMENT_SHADER, fragment_shader_source);
GLuint program = glCreateProgram();
glAttachShader(program, vertex_shader);
glAttachShader(program, fragment_shader);
glLinkProgram(program);
glDetachShader(program, vertex_shader);
glDetachShader(program, fragment_shader);
glDeleteShader(vertex_shader);
glDeleteShader(fragment_shader);
GLuint vertex_buffer;
glGenBuffers(1, &vertex_buffer);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertex_data), vertex_data, GL_STATIC_DRAW);
do {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(program);
glEnableVertexAttribArray(0);
glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
glDrawArrays(GL_TRIANGLES, 0, 3);
glDisableVertexAttribArray(0);
glfwSwapBuffers(window);
glfwPollEvents();
} while(!glfwWindowShouldClose(window));
glfwTerminate();
return 0;
}

I don’t think this is an issue with the opengl
package, either, since it just makes the obvious FFI calls, but it’s always possible. I’m not an OpenGL expert (or even really an amateur), and I’m definitely not knowledgable about Windows. The issue seems to come from somewhere in Racket, though, since the C program works fine.
After more investigation, I’ve found this isn’t actually related to the fixed-function versus programmable pipeline, but rather when the WGL context is created. If I add (with-gl-context void)
to the body of triangle-canvas:fixed-function%
, it also draws only a black screen.
So.....
Just an FYI, racket+opengl on windows is just horribly broken, if you are using an intel gpu. If you are using an nvidea gpu (or a virtual machine gpu), it works just fine.
Benjamin Chung and I have spent a lot of time looking into this with (so far) little luck. It seems to be related to double buffering in how wgl was set up (possibly with the dummy context) but we're not sure.
The machine I’m testing this on has an NVIDIA GPU.
The issue appears to manifest when the WGL context is created before the frame the canvas belongs to is made visible. If I create the canvas, call (send canvas with-gl-context void)
, then call (send frame show #t)
, it breaks, but if I reverse the order of the calls, it works fine. So something is going wrong inside create-gl-context
when the frame is not visible, but I don’t know what.
I hit this issue on Arch Linux with an Nvidia GeForce MX150 running with bumblebee - (send frame show #t)
must run before any calls to with-gl-context
.