I wrote a simple Go program. Its goal is to rotate a triangle on the screen using OpenGl.
EDIT: The main loop seems to be responsible, OpenGl is fine, but I am doing something wrong with goroutines and/or channels. See at the bottom of this post. I am changing the title of this question appropriately.
The program almost works. It stutters, alternating between rotating as it is supposed to, and just flashing the two OpenGl buffers without drawing anything. About two thirds of the frames I try to render fail silently, and I do not understand why.
This does not depend on my frame rate. Whether I update at 50 or 1 FPS, I get long series of messed up frames followed by shorter series of working frames. I don't think I am asking OpenGl to work too fast (and I call glFinish()
anyway).
I started experimenting and found a strange behavior.
I use glGetUniformLocation
as a first step to send my new rotation matrix to the vertex buffer.
I noticed that if I ask glGetUniformLocation
to tell me where I can find "dummy"
, a parameter that obviously does not exist in the shader, it does not always return -1 as expected ; it sometimes returns 0. That dummy attribute is not responsible for the problem, it is merely a symptom.
I check glGetError after every OpenGl call, they always return NO_ERROR.
I am pasting here my code. I wish I could have made it shorter. My real code checks glError after every single call to OpenGl, and also looks at 0 buffers or -1 locations. This version is lighter. func main
is at the top, followed my the loop that takes care of the rotation.
package main
import (
"fmt"
"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
gl "github.com/chsc/gogl/gl33"
"math"
"time"
"unsafe"
)
const DEG_TO_RAD = math.Pi / 180
type GoMatrix [16]float64
type GlMatrix [16]gl.Float
var good_frames, bad_frames, sdl_events int
func main() {
//=================================================================
// Just opening a window, skip to the next part.
if status := sdl.Init(sdl.INIT_VIDEO); status != 0 {
panic("Could not initialize SDL: " + sdl.GetError())
}
defer sdl.Quit()
sdl.GL_SetAttribute(sdl.GL_DOUBLEBUFFER, 1)
const FLAGS = sdl.OPENGL
if screen := sdl.SetVideoMode(640, 480, 32, FLAGS); screen == nil {
panic("Could not open SDL window: " + sdl.GetError())
}
if err := gl.Init(); err != nil {
panic(err)
}
gl.Viewport(0, 0, 640, 480)
gl.ClearColor(.5, .5, .5, 1)
//=================================================================
// Simplest shaders ever.
// A matrix to move the model, nothing else.
vertex_code := gl.GLString(`
#version 330 core
in vec3 vpos;
uniform mat4 MVP;
void main() {
gl_Position = MVP * vec4(vpos, 1);
}
`)
// Everything is red.
fragment_code := gl.GLString(`
#version 330 core
void main(){
gl_FragColor = vec4(1,0,0,1);
}
`)
vs := gl.CreateShader(gl.VERTEX_SHADER)
fs := gl.CreateShader(gl.FRAGMENT_SHADER)
gl.ShaderSource(vs, 1, &vertex_code, nil)
gl.ShaderSource(fs, 1, &fragment_code, nil)
gl.CompileShader(vs)
gl.CompileShader(fs)
prog := gl.CreateProgram()
gl.AttachShader(prog, vs)
gl.AttachShader(prog, fs)
gl.LinkProgram(prog)
// Did it compile?
var link_status gl.Int
gl.GetProgramiv(prog, gl.LINK_STATUS, &link_status)
if link_status == gl.FALSE {
var info_log_length gl.Int
gl.GetProgramiv(prog, gl.INFO_LOG_LENGTH, &info_log_length)
if info_log_length == 0 {
panic("Program linking failed but OpenGL has no log about it.")
} else {
info_log_gl := gl.GLStringAlloc(gl.Sizei(info_log_length))
defer gl.GLStringFree(info_log_gl)
gl.GetProgramInfoLog(prog, gl.Sizei(info_log_length), nil, info_log_gl)
info_log := gl.GoString(info_log_gl)
panic(info_log)
}
}
gl.UseProgram(prog)
attrib_vpos := gl.Uint(gl.GetAttribLocation(prog, gl.GLString("vpos")))
//=================================================================
// One triangle.
positions := [...]gl.Float{-.5, -.5, 0, .5, -.5, 0, 0, .5, 0}
var vao gl.Uint
gl.GenVertexArrays(1, &vao)
gl.BindVertexArray(vao)
var vbo gl.Uint
gl.GenBuffers(1, &vbo)
gl.BindBuffer(gl.ARRAY_BUFFER, vbo)
gl.BufferData(gl.ARRAY_BUFFER,
gl.Sizeiptr(unsafe.Sizeof(positions)),
gl.Pointer(&positions[0]),
gl.STATIC_DRAW)
gl.EnableVertexAttribArray(attrib_vpos)
gl.VertexAttribPointer(attrib_vpos, 3, gl.FLOAT, gl.FALSE, 0, gl.Pointer(nil))
//=================================================================
Loop(prog)
fmt.Println("Good frames", good_frames)
fmt.Println("Bad frames ", bad_frames)
fmt.Println("SDL events ", sdl_events)
}
func Loop(program gl.Uint) {
start_time := time.Now()
ticker := time.NewTicker(100 * time.Millisecond)
defer ticker.Stop()
running := true
for running {
select {
case tick_time := <-ticker.C:
OnTick(start_time, tick_time, program)
case event := <-sdl.Events:
running = OnSdlEvent(event)
}
}
}
func OnSdlEvent(event interface{}) bool {
sdl_events++
switch event.(type) {
case sdl.QuitEvent:
return false // Stop the main loop.
}
return true // Do not stop the main loop.
}
func OnTick(start_time, tick_time time.Time, program gl.Uint) {
duration := tick_time.Sub(start_time).Seconds()
speed := 10.
angle := math.Mod(duration*speed, 360)
gom := RotZ(angle)
MVP := ToGlMatrix(gom)
/* HERE, SOMETHING FISHY HAPPENS.
Problem: sometimes, actually often, OpenGl returns 0 instead of -1 for
the dummy parameter. This is entirely correlated to the stuttering.
With my implementation of OpenGl, swap buffer does a real swap.
That means I get to see the last two pictures rendered.
Thing is, I can see the swap, that means the pictures are different.
That means that the call to DrawArrays is ignored.
OpenGl is just crapping its pants.
*/
matrix_loc := gl.GetUniformLocation(program, gl.GLString("MVP"))
dummy_matrix_loc := gl.GetUniformLocation(program, gl.GLString("dummy"))
if gl.GetError() != gl.NO_ERROR {
fmt.Println("Error get location") // Never happens.
}
if dummy_matrix_loc == -1 {
good_frames++ // Because is SHOULD fail.
} else {
bad_frames++ // That's not normal.
}
gl.UniformMatrix4fv(matrix_loc, 16, gl.TRUE, &MVP[0])
if gl.GetError() != gl.NO_ERROR {
fmt.Println("Error send matrix") // Never happens.
}
gl.Clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
if gl.GetError() != gl.NO_ERROR {
fmt.Println("Error clearing") // Never happens.
}
gl.DrawArrays(gl.TRIANGLES, 0, 3)
if gl.GetError() != gl.NO_ERROR {
fmt.Println("Error drawing") // Never happens.
}
gl.Finish() // Does not seem to make anything work better.
sdl.GL_SwapBuffers()
}
func RotZ(angle float64) GoMatrix {
var gom GoMatrix
a := angle * DEG_TO_RAD
c := math.Cos(a)
s := math.Sin(a)
gom[0] = c
gom[1] = s
gom[4] = -s
gom[5] = c
gom[10] = 1
gom[15] = 1
return gom
}
func ToGlMatrix(gom GoMatrix) GlMatrix {
var glm GlMatrix
glm[0] = gl.Float(gom[0])
glm[1] = gl.Float(gom[1])
glm[2] = gl.Float(gom[2])
glm[3] = gl.Float(gom[3])
glm[4] = gl.Float(gom[4])
glm[5] = gl.Float(gom[5])
glm[6] = gl.Float(gom[6])
glm[7] = gl.Float(gom[7])
glm[8] = gl.Float(gom[8])
glm[9] = gl.Float(gom[9])
glm[10] = gl.Float(gom[10])
glm[11] = gl.Float(gom[11])
glm[12] = gl.Float(gom[12])
glm[13] = gl.Float(gom[13])
glm[14] = gl.Float(gom[14])
glm[15] = gl.Float(gom[15])
return glm
}
As requested, the output of glxinfo
.
> glxinfo
name of display: :0
display: :0 screen: 0
direct rendering: Yes
server glx vendor string: ATI
server glx version string: 1.4
server glx extensions:
GLX_ARB_multisample, GLX_EXT_import_context, GLX_EXT_texture_from_pixmap,
GLX_EXT_visual_info, GLX_EXT_visual_rating, GLX_OML_swap_method,
GLX_SGI_make_current_read, GLX_SGI_swap_control, GLX_SGIS_multisample,
GLX_SGIX_fbconfig, GLX_SGIX_pbuffer, GLX_SGIX_visual_select_group
client glx vendor string: ATI
client glx version string: 1.4
client glx extensions:
GLX_ARB_create_context, GLX_ARB_create_context_profile,
GLX_ARB_get_proc_address, GLX_ARB_multisample, GLX_EXT_import_context,
GLX_EXT_visual_info, GLX_EXT_visual_rating, GLX_MESA_allocate_memory,
GLX_MESA_copy_sub_buffer, GLX_MESA_swap_control,
GLX_MESA_swap_frame_usage, GLX_NV_swap_group, GLX_OML_swap_method,
GLX_SGI_make_current_read, GLX_SGI_swap_control, GLX_SGI_video_sync,
GLX_SGIS_multisample, GLX_SGIX_fbconfig, GLX_SGIX_pbuffer,
GLX_SGIX_swap_barrier, GLX_SGIX_swap_group, GLX_SGIX_visual_select_group,
GLX_EXT_texture_from_pixmap, GLX_EXT_framebuffer_sRGB,
GLX_ARB_fbconfig_float, GLX_AMD_gpu_association
GLX version: 1.4
GLX extensions:
GLX_ARB_create_context, GLX_ARB_create_context_profile,
GLX_ARB_get_proc_address, GLX_ARB_multisample, GLX_EXT_import_context,
GLX_EXT_visual_info, GLX_EXT_visual_rating, GLX_MESA_swap_control,
GLX_NV_swap_group, GLX_OML_swap_method, GLX_SGI_make_current_read,
GLX_SGI_swap_control, GLX_SGI_video_sync, GLX_SGIS_multisample,
GLX_SGIX_fbconfig, GLX_SGIX_pbuffer, GLX_SGIX_swap_barrier,
GLX_SGIX_swap_group, GLX_SGIX_visual_select_group,
GLX_EXT_texture_from_pixmap
OpenGL vendor string: ATI Technologies Inc.
OpenGL renderer string: ATI Mobility Radeon HD 4500 Series
OpenGL version string: 3.3.11005 Compatibility Profile Context
OpenGL shading language version string: 3.30
OpenGL extensions:
GL_AMDX_debug_output, GL_AMDX_vertex_shader_tessellator,
GL_AMD_conservative_depth, GL_AMD_debug_output,
GL_AMD_depth_clamp_separate, GL_AMD_draw_buffers_blend,
GL_AMD_name_gen_delete, GL_AMD_performance_monitor, GL_AMD_pinned_memory,
GL_AMD_sample_positions, GL_AMD_seamless_cubemap_per_texture,
GL_AMD_shader_stencil_export, GL_AMD_texture_cube_map_array,
GL_AMD_texture_texture4, GL_AMD_vertex_shader_tessellator,
GL_ARB_ES2_compatibility, GL_ARB_blend_func_extended,
GL_ARB_color_buffer_float, GL_ARB_copy_buffer, GL_ARB_depth_buffer_float,
GL_ARB_depth_clamp, GL_ARB_depth_texture, GL_ARB_draw_buffers,
GL_ARB_draw_buffers_blend, GL_ARB_draw_elements_base_vertex,
GL_ARB_draw_instanced, GL_ARB_explicit_attrib_location,
GL_ARB_fragment_coord_conventions, GL_ARB_fragment_program,
GL_ARB_fragment_program_shadow, GL_ARB_fragment_shader,
GL_ARB_framebuffer_object, GL_ARB_framebuffer_sRGB,
GL_ARB_geometry_shader4, GL_ARB_get_program_binary,
GL_ARB_half_float_pixel, GL_ARB_half_float_vertex, GL_ARB_imaging,
GL_ARB_instanced_arrays, GL_ARB_map_buffer_range, GL_ARB_multisample,
GL_ARB_multitexture, GL_ARB_occlusion_query, GL_ARB_occlusion_query2,
GL_ARB_pixel_buffer_object, GL_ARB_point_parameters, GL_ARB_point_sprite,
GL_ARB_provoking_vertex, GL_ARB_sample_shading, GL_ARB_sampler_objects,
GL_ARB_seamless_cube_map, GL_ARB_separate_shader_objects,
GL_ARB_shader_bit_encoding, GL_ARB_shader_objects,
GL_ARB_shader_precision, GL_ARB_shader_stencil_export,
GL_ARB_shader_texture_lod, GL_ARB_shading_language_100, GL_ARB_shadow,
GL_ARB_shadow_ambient, GL_ARB_sync, GL_ARB_texture_border_clamp,
GL_ARB_texture_buffer_object, GL_ARB_texture_buffer_object_rgb32,
GL_ARB_texture_compression, GL_ARB_texture_compression_rgtc,
GL_ARB_texture_cube_map, GL_ARB_texture_cube_map_array,
GL_ARB_texture_env_add, GL_ARB_texture_env_combine,
GL_ARB_texture_env_crossbar, GL_ARB_texture_env_dot3,
GL_ARB_texture_float, GL_ARB_texture_gather,
GL_ARB_texture_mirrored_repeat, GL_ARB_texture_multisample,
GL_ARB_texture_non_power_of_two, GL_ARB_texture_query_lod,
GL_ARB_texture_rectangle, GL_ARB_texture_rg, GL_ARB_texture_rgb10_a2ui,
GL_ARB_texture_snorm, GL_ARB_timer_query, GL_ARB_transform_feedback2,
GL_ARB_transform_feedback3, GL_ARB_transpose_matrix,
GL_ARB_uniform_buffer_object, GL_ARB_vertex_array_bgra,
GL_ARB_vertex_array_object, GL_ARB_vertex_buffer_object,
GL_ARB_vertex_program, GL_ARB_vertex_shader,
GL_ARB_vertex_type_2_10_10_10_rev, GL_ARB_viewport_array,
GL_ARB_window_pos, GL_ATI_draw_buffers, GL_ATI_envmap_bumpmap,
GL_ATI_fragment_shader, GL_ATI_meminfo, GL_ATI_separate_stencil,
GL_ATI_texture_compression_3dc, GL_ATI_texture_env_combine3,
GL_ATI_texture_float, GL_ATI_texture_mirror_once, GL_EXT_abgr,
GL_EXT_bgra, GL_EXT_bindable_uniform, GL_EXT_blend_color,
GL_EXT_blend_equation_separate, GL_EXT_blend_func_separate,
GL_EXT_blend_minmax, GL_EXT_blend_subtract, GL_EXT_compiled_vertex_array,
GL_EXT_copy_buffer, GL_EXT_copy_texture, GL_EXT_direct_state_access,
GL_EXT_draw_buffers2, GL_EXT_draw_instanced, GL_EXT_draw_range_elements,
GL_EXT_fog_coord, GL_EXT_framebuffer_blit, GL_EXT_framebuffer_multisample,
GL_EXT_framebuffer_object, GL_EXT_framebuffer_sRGB,
GL_EXT_geometry_shader4, GL_EXT_gpu_program_parameters,
GL_EXT_gpu_shader4, GL_EXT_histogram, GL_EXT_multi_draw_arrays,
GL_EXT_packed_depth_stencil, GL_EXT_packed_float, GL_EXT_packed_pixels,
GL_EXT_pixel_buffer_object, GL_EXT_point_parameters,
GL_EXT_provoking_vertex, GL_EXT_rescale_normal, GL_EXT_secondary_color,
GL_EXT_separate_specular_color, GL_EXT_shadow_funcs, GL_EXT_stencil_wrap,
GL_EXT_subtexture, GL_EXT_texgen_reflection, GL_EXT_texture3D,
GL_EXT_texture_array, GL_EXT_texture_buffer_object,
GL_EXT_texture_compression_latc, GL_EXT_texture_compression_rgtc,
GL_EXT_texture_compression_s3tc, GL_EXT_texture_cube_map,
GL_EXT_texture_edge_clamp, GL_EXT_texture_env_add,
GL_EXT_texture_env_combine, GL_EXT_texture_env_dot3,
GL_EXT_texture_filter_anisotropic, GL_EXT_texture_integer,
GL_EXT_texture_lod, GL_EXT_texture_lod_bias, GL_EXT_texture_mirror_clamp,
GL_EXT_texture_object, GL_EXT_texture_rectangle, GL_EXT_texture_sRGB,
GL_EXT_texture_shared_exponent, GL_EXT_texture_snorm,
GL_EXT_texture_swizzle, GL_EXT_timer_query, GL_EXT_transform_feedback,
GL_EXT_vertex_array, GL_EXT_vertex_array_bgra,
GL_IBM_texture_mirrored_repeat, GL_KTX_buffer_region, GL_NV_blend_square,
GL_NV_conditional_render, GL_NV_copy_depth_to_color,
GL_NV_explicit_multisample, GL_NV_float_buffer, GL_NV_half_float,
GL_NV_primitive_restart, GL_NV_texgen_reflection, GL_NV_texture_barrier,
GL_SGIS_generate_mipmap, GL_SGIS_texture_edge_clamp, GL_SGIS_texture_lod,
GL_SUN_multi_draw_arrays, GL_WIN_swap_hint, WGL_EXT_swap_control
EDIT: The main loop seems to be responsible, OpenGl is fine, but I am doing something wrong with goroutines and/or channels.
If I replace my main loop with something that does not rely on channels and goroutines, then OpenGl behaves perfectly.
func Loop(program gl.Uint) {
start_time := time.Now()
stop_time := start_time.Add(time.Duration(30 * time.Second))
running := true
for running {
tick_time := time.Now()
OnTick(start_time, tick_time, program)
time.Sleep(10 * time.Millisecond)
if tick_time.After(stop_time) {
running = false
}
}
}
My problem may come from the fact that OpenGl is in the main Goroutine, which blocks while waiting for the channels to deliver a tick or a SDL event. I don't know, I am confused.
Changing the title from glGetUniformLocation often returns 0 instead of -1 for an attribute that does not exist to OpenGl seems stuck while my main loop waits for channels to tick and deliver events.
The problem comes from the fact that the graphic libraries SDL and OpenGl impose restrictions to the use of threads. Golang shuffles goroutines around between threads as it sees fit, unaware of these restrictions. Consequently, some calls are lost, unpredictable behaviors occur.
By locking OpenGl to the main thread, I could have it working, without having to give up using goroutines.
This is the solution I adopted:
http://code.google.com/p/go-wiki/wiki/LockOSThread
Note that there is a typo in the function do
: one should read mainfunc
instead of ch
.
My main file now looks like this:
// Arrange that main.main runs on main thread.
func init() {
runtime.LockOSThread()
}
// Queue of work to run in main thread.
var mainfunc = make(chan func())
// Run all the functions that need to run in the main thread.
func Main() {
for f = range mainfunc {
f()
}
}
// Put the function f on the main thread function queue.
func do(f func()) {
done := make(chan bool, 1)
mainfunc <- func() {
f()
done <- true
}
<-done
// The real main function.
func main() {
go Everything()
Main()
}
// Your entire application comes here.
func Everything() {
defer close(mainfunc)
do(func(){
// init SDL, OpenGl, all that.
})
defer sdl.Quit()
var some_variable int // Starts at 0.
do(func(){
// OpenGl code
some_variable = 42 // Closure allows for communication.
})
Loop(some_variable) // Now 42.
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With