On Windows, calling `with-gl-context` during `canvas%` initialization breaks rendering
Nobody has claimed this yet.
- Dominant language
- Racket
- Stars
- 68
- Forks
- 82
- PR merge metrics
- No merged PRs in 30d
Description
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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the provided Racket reproducer, focusing on canvas% initialization and with-gl-context on Windows; compare the fixed-function and core-profile paths with the GLFW/GLEW C example. Done means the shader-based canvas renders the triangle on Windows without breaking the working fixed-function case.
Written by the indexing model from the issue text.
Assessment
- Domain
- computer-graphics, desktop
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100