racket / racket/gui

On Windows, calling `with-gl-context` during `canvas%` initialization breaks rendering

Open
#135 4 comments 0 reactions 0 assignees View on GitHub

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

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.