我在Go中有这个简单的OpenGL程序。
当我编译并运行它时,主游戏循环在崩溃之前经历了大约9次迭代。
rendering for the 0 time
rendering for the 1 time
rendering for the 2 time
rendering for the 3 time
rendering for the 4 time
rendering for the 5 time
rendering for the 6 time
SIGSEGV: segmentation violation
PC=0x7fdab95a0e29
signal arrived during cgo execution
runtime.cgocall(0x414f90, 0x7fdab9887e88)
/usr/lib/go/src/pkg/runtime/cgocall.c:149 +0x11b fp=0x7fdab9887e70
github.com/go-gl/gl._Cfunc_glClear(0xc200004100)
github.com/go-gl/gl/_obj/_cgo_defun.c:340 +0x31 fp=0x7fdab9887e88
github.com/go-gl/gl.Clear(0x4100)
/mnt/data/Dropbox/Coding/Go/src/github.com/go-gl/gl/gl.go:161 +0x25 fp=0x7fdab9887e98
main.draw()
/home/josh/Coding/Go/src/github.com/JoshWillik/Wander/wander.go:120 +0x25 fp=0x7fdab9887eb8
main.main()
/home/josh/Coding/Go/src/github.com/JoshWillik/Wander/wander.go:52 +0x300 fp=0x7fdab9887f48
runtime.main()
/usr/lib/go/src/pkg/runtime/proc.c:220 +0x11f fp=0x7fdab9887fa0
runtime.goexit()
/usr/lib/go/src/pkg/runtime/proc.c:1394 fp=0x7fdab9887fa8
goroutine 3 [syscall]:
runtime.goexit()
/usr/lib/go/src/pkg/runtime/proc.c:1394
rax 0x0
rbx 0x7fdab9887e88
rcx 0x7fdab9887e88
rdx 0x7fdab9887e20
rdi 0x4100
rsi 0xc210001900
rbp 0xc21002a000
rsp 0x7fdab2a4ddd8
r8 0xc210001120
r9 0x7fdab9887e20
r10 0x0
r11 0x286
r12 0x0
r13 0x7fdab9a74000
r14 0x0
r15 0x7fdab2a4e700
rip 0x7fdab95a0e29
rflags 0x10202
cs 0x33
fs 0x0
gs 0x0
如果我删除了shouldRender
函数中基于时间的逻辑,它会在崩溃之前进行大约28-29次迭代。
如果我移除gl.Clear()
功能中对draw
的呼叫,则会在崩溃前持续到90秒。
如果我移除fmt.Println()
中对shouldRender
的调用,游戏会按预期运行而不会崩溃。 (我已经测试了大约2或3分钟,所以差不多有1万帧)
这让我怀疑对fmt.Println()
的调用在某种程度上导致了分段违规。我误读了这些迹象吗?如果没有,Println()
这样的核心功能如何不稳定?
package main
import (
f "fmt"
"github.com/go-gl/gl"
glfw "github.com/go-gl/glfw3"
"math"
"time"
)
var (
numRendered = 0
lastDraw = time.Now()
fps = 60
seconds = time.Now()
attr gl.AttribLocation
)
func main(){
if !glfw.Init(){
f.Println("Failed to init glfw")
panic("Cannot initialize glfw library")
}
defer glfw.Terminate()
//glfw.WindowHint(glfw.DepthBits, 16)
window, err := glfw.CreateWindow(300, 300, "Wander", nil, nil)
if err != nil{
panic(err)
}
window.SetFramebufferSizeCallback(reshape)
window.SetKeyCallback(key)
window.MakeContextCurrent()
glfw.SwapInterval(1)
width, height := window.GetFramebufferSize()
reshape(window, width, height)
if gl.Init() != 0 {
panic("Failed to init GL")
}
prog := setupProgram()
defer prog.Delete()
prog.Use()
attr = prog.GetAttribLocation("offset")
setup()
for !window.ShouldClose() {
if shouldRender(){
draw()
}
animate()
window.SwapBuffers()
glfw.PollEvents()
}
}
func setupProgram()(prog gl.Program){
vertexSource := `
#version 430 core
layout (location = 0) in vec4 offset;
const vec4 vertecies[3] = vec4[3](
vec4(0.25, 0.5, 0.5, 1.0),
vec4(-0.25, 0.5, 0.5, 1.0),
vec4(-0.25, -0.5, 0.5, 1.0)
);
void main(){
gl_Position = vertecies[gl_VertexID] + offset;
}`
fragmentSource := `
#version 430 core
out vec4 color;
void main(){
color = vec4(1.0, 0.0, 0.0, 0.0); // red, blue, green, ??
}`
vert, frag := gl.CreateShader(gl.VERTEX_SHADER), gl.CreateShader(gl.FRAGMENT_SHADER)
defer vert.Delete()
defer frag.Delete()
vert.Source(vertexSource)
frag.Source(fragmentSource)
vert.Compile()
frag.Compile()
prog = gl.CreateProgram()
prog.AttachShader(vert)
prog.AttachShader(frag)
prog.Link()
prog.Use()
f.Println(prog.GetInfoLog())
return
}
func key(window *glfw.Window, k glfw.Key, s int, action glfw.Action, mods glfw.ModifierKey) {
if action != glfw.Press {
return
}
switch glfw.Key(k){
case glfw.KeyEscape:
window.SetShouldClose(true);
default:
return
}
}
func reshape(window *glfw.Window, width, height int){
gl.Viewport(0, 0, width, height)
}
func draw(){
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
gl.DrawArrays(gl.TRIANGLES, 0, 3)
}
func shouldRender() bool{
if int(time.Since(lastDraw) * time.Second) >= 1000/fps{
//f.Println("rendering for the ", numRendered, " time")
numRendered++
lastDraw = time.Now()
return true
}
return false;
}
func animate(){
now := float64(time.Since(seconds))
offset := [4]float32{
float32(math.Sin(now)),
float32(math.Cos(now)),
0.0,0.0}
attr.Attrib4fv(&offset)
red := gl.GLclampf(math.Sin(now) * 0.25 + 0.75)
blue := gl.GLclampf(math.Cos(now) * 0.25 + 0.75)
green := gl.GLclampf(time.Since(seconds))
_ = green;
gl.ClearColor(red, blue, 0.2, 0.0)
}
答案 0 :(得分:13)
我在我的机器上运行你的代码 - 在64位Windows上运行MinGW-w64。虽然我使用println的代码比你的代码运行时间长(在崩溃之前超过30k的调用),但我观察到了同样的行为。
堆栈跟踪在调用gl函数时报告了它,这给了我一个暗示:可能错误与OpenGL上下文有关。
确实,如果你添加
import (
//...
"runtime"
//...
)
行
runtime.LockOSThread()
在主要功能的顶部,错误消失(或者至少在线程锁定它在我的机器上运行了几分钟,显然我无法证明它永远不会崩溃)。
当Goroutines被阻止执行某些任务(如IO)时,Go运行时偶尔会分离额外的线程以保持程序的移动。
我怀疑它发生的事情是,有时在调用Println时,Goroutine在系统调用中被阻止,因此运行时通过在另一个线程上运行主goroutine来“帮助”你。由于OpenGL上下文绑定到一个线程,这会导致程序在GL调用上崩溃,因为你正在调用错误的线程。
将runtime.LockOSThread()添加到main的顶部会强制主Goroutine始终在同一个线程上执行,从而将所有GL调用保持在正确的上下文中。
我应该补充说,我简要地浏览了fmt软件包的源代码,从我看到的内容中我无法证明goroutine / thread废话肯定会发生;但是,我强烈怀疑这是基于我对Go运行时的了解以及LockOSThread似乎解决它的事实。
无论哪种方式:当您使用OpenGL或OpenAL等依赖于绑定到单个线程的C库的C库时,请确保始终使用runtime.LockOSThread将其运行的Goroutine锁定到一个线程。 / p>
更新:我在the Go scheduler上找到了这个文件,如果我正确地阅读它,就会证实我的怀疑。打印等系统调用可以调用生成的新线程,以便在IO上阻止程序时继续调用goroutine。