Search code examples
linuxopenglgoglfwcgo

Go causes OpenGL to segfault with time.Tick but not time.After


I have the following two files:

bridge.go:

package cube

// #cgo LDFLAGS: -lGL -lGLEW -lglfw
// #include <GLFW/glfw3.h>
// int init(GLFWwindow**);
// void render(GLFWwindow*);
import "C"

import (
    "fmt"
    "time"
)

func Init() {
    var window *_Ctype_GLFWwindow
    windowWat := (*[0]byte)(window)
    fmt.Printf("Calling init\n")
    if C.init(&windowWat) != 1 {
        return
    }
    window = (*_Ctype_GLFWwindow)(windowWat)

    //t := time.Tick(time.Second) // Doesn't work
    t := time.After(time.Second) // Works

    <-t
    fmt.Println("Rendering")
    C.render((*[0]byte)(window))

    select {}
}

cube.c:

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>

#define INIT_WINDOW_W (800)
#define INIT_WINDOW_H (800)

void render(GLFWwindow* window) {
    glClearColor(135.0f / 255.0f, 206.0f / 255.0f, 250.f / 254.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glfwSwapBuffers(window);
}

static void glfw_error(int errno, const char* description) {
    fprintf(stderr, "GLFW [%d] %s\n", errno, description);
    exit(1);
}

int init(GLFWwindow** window) {
    GLenum glewErr;

    glfwSetErrorCallback(glfw_error);
    if (!glfwInit()) {
        return 0;
    }

    *window = glfwCreateWindow(
        INIT_WINDOW_H,
        INIT_WINDOW_W,
        "wat",
        NULL,
        NULL
    );
    if (*window == NULL) {
        glfwTerminate();
        return 0;
    }

    glfwMakeContextCurrent(*window);

    glewErr = glewInit();
    if (glewErr != GLEW_OK) {
        fprintf(stderr, "glewInit failed: %s\n", glewGetErrorString(glewErr));
        return 0;
    }

    if (!GL_VERSION_2_0) {
        fprintf(stderr, "Don't have OpenGL >= 2.0\n");
        return 0;
    }

    return 1;
}

As well as a main.go file which calls cube.Init() and nothing else.

My issue lies in the lines just before calling C.render. If I do a time.After, it works fine and displays a blue window as it's supposed to. If I do a time.Tick, it will rarely also display that blue window, but either display a black window or segfault. This test case is significantly boiled down from my actual code.

I have a feeling that go's scheduler is messing things up somehow, but I can't think how (or how to test/fix that). I'm curious if anyone has any ideas what's causing this, or can think of any ways to investigate further.

Other possibly significant information:

  • Arch linux, 3.14.4-1-ARCH.x86_64
  • GLFW 3.0.4-1
  • GLEW 1.10.0-2
  • nVidia GeForce GTX 570M
  • nVidia driver 337.12-1
  • go1.2 linux/amd64

EDIT:

Here's the segfault message:

SIGSEGV: segmentation violation
PC=0x7fda7d6a2e29
signal arrived during cgo execution

runtime.cgocall(0x401260, 0x7fda7d6f0e58)
        /opt/go/src/pkg/runtime/cgocall.c:149 +0x11b fp=0x7fda7d6f0e40
game/cube._Cfunc_render(0x24e13b0)
        game/cube/_obj/_cgo_defun.c:62 +0x31 fp=0x7fda7d6f0e58
game/cube.Init()
        /tmp/wat/cube/bridge.go:28 +0x156 fp=0x7fda7d6f0ee0
main.main()
        /tmp/wat/main.go:10 +0xac fp=0x7fda7d6f0f48
runtime.main()
        /opt/go/src/pkg/runtime/proc.c:220 +0x11f fp=0x7fda7d6f0fa0
runtime.goexit()
        /opt/go/src/pkg/runtime/proc.c:1394 fp=0x7fda7d6f0fa8

goroutine 3 [syscall]:
runtime.goexit()
        /opt/go/src/pkg/runtime/proc.c:1394

rax     0x0
rbx     0x24e13b0
rcx     0x7fda7d6f0e58
rdx     0x7fda7d6f0df0
rdi     0x24e13b0
rsi     0xc210001900
rbp     0xc21002a000
rsp     0x7fda76506dc8
r8      0xc210001120
r9      0x7fda7d6f0df0
r10     0x7fda76506ba0
r11     0x7fda7d6a2e20
r12     0x0
r13     0x7fda76507700
r14     0x0
r15     0x7fda76d07c80
rip     0x7fda7d6a2e29
rflags  0x10202
cs      0x33
fs      0x0
gs      0x0
exit status 2

And it appears to segfault at the call to glClearColor


Solution

  • Turning my comment into an answer:

    For some reason OpenGL often requires everything run in the same OS thread. time.Tick and time.After call different runtime functions and one may be causing different opengl calls to run in separate threads. By default, Go makes does not guarantee a goroutine runs on a specific thread.

    To fix this, you need to use runtime.LockOSThread. This will ensure that goroutine and only that goroutine ever run on the current thread.

    You can read more on the topic here: https://groups.google.com/forum/#!topic/golang-nuts/5Pvv1Gr1eoo