Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OpenGl seems stuck while my main loop waits for channels to tick and deliver events

Tags:

go

opengl

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.

like image 596
Niriel Avatar asked Oct 21 '22 18:10

Niriel


1 Answers

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.
}
like image 93
Niriel Avatar answered Oct 27 '22 10:10

Niriel