LloydP's picture

GL.GenBuffers hits Invalid Operation Error

I am trying to convert a WebGL app to a native Win Forms app using OpenTK.

When I try to create my VBOs, I am getting a GL Error of Invalid Operation for GL.GenBuffer.

My first thought was the app is running in a really old version of OpenGL that doesn't support VBOs, So I told it to try a 3.0 context, and still no joy.

I am running Windows 8, VS2012, .net 4.5 and my gfx card is an nVidia 250 GTS. OpenTK claims I'm able to support up to OpenGL 3.3.

When I push my points to the gpu manually, it works, but bearing in mind this has come from WebGL, everything is setup to batch into mega VBOs, I'd like to use them for a) performance and B) keeping the two code bases similar for maintainability.

Any suggestions where I might be going wrong?

(PS. Is there a way to use ES20 on a desktop? It would be better for me to use that as it would match up more with WebGL)


Comments

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.
Snorkel's picture

VBOs work for sure, you can't expect help without posting some code ;-)

also

ES20 runs inside chrome using ANGLE ( http://code.google.com/p/angleproject/ )

LloydP's picture

Ok, well here's my Layer class converted to c#.

Essentially, I'm loading up a pretty large 3D CAD model. The geometry is static, but there's a metric fuck-ton of it, so for performance I need to follow the nvidia school of batch, batch, batch.

What I do (in webgl to minimise gl calls) is to split the model into layers by 'material' type which is essentially RGBA colour. I then push each item (eg a door or a wall) into a 'mega VBO' which contains the geometry for all items of that material type. This means I only call DrawArray something like 20x for a model with 10,000 meshes in it. This means performance is O(m) where m is the number of materials in the model.

When I convert my javascript to c# and use opentk, it's failing in CreateVBO() at GL.GenBuffers()

    public class Layer
    {
        public Color Material { get; set; }
        public ConcurrentDictionary<GLGeometry, bool> Children = new ConcurrentDictionary<GLGeometry,bool>();
 
        private System.Boolean pauseRender = false;
        private List<Single> positions = new List<Single>();
        private List<Single> normals = new List<Single>();
        private Int32[] VBOid = new int[2];
        private System.Boolean CreatedBuffers = false;
        private Int32 id;
 
        public Layer(){
            Resetbuffers();
        }
 
        /// <summary>
        /// Adds a single piece of geometry and resets the buffers to account for new piece
        /// </summary>
        /// <param name="geo">the piece of geometry to add</param>
        internal void AddGeometry(GLGeometry geo)
        {
            pauseRender = true;
            Children.TryAdd(geo, true);
            Resetbuffers();
            pauseRender = false;
        }
 
        /// <summary>
        /// Adds a batch of geometry and resets the buffers to account for new pieces
        /// </summary>
        /// <param name="geos">the list of geometries to add</param>
        internal void AddGeometry(List<GLGeometry> geos)
        {
            pauseRender = true;
            foreach (GLGeometry g in geos)
            {
                Children.TryAdd(g, true);
            }
            Resetbuffers();
            pauseRender = false;
        }
 
        /// <summary>
        /// Rebuilds the VBOs with the current position/normal data for the Layer
        /// </summary>
        void Resetbuffers()
        {
            //create the list of data to buffer
            lock (positions)
            {
                lock (normals)
                {
                    positions.Clear();
                    normals.Clear();
 
                    foreach (GLGeometry g in Children.Keys)
                    {
                        if (g != null)
                        {
                            positions.AddRange(g.Positions);
                            normals.AddRange(g.Normals);
                        }
                    }
                }
            }
 
            //if we have already created the buffers, then delete them and recreate them
            if (CreatedBuffers == true)
            {
                GL.DeleteBuffers(2, VBOid);
            }
 
            VBOid[0] = CreateVBO(positions.ToArray());
            VBOid[1] = CreateVBO(normals.ToArray());
 
            if (VBOid[0] != 0 && VBOid[1] != 0)
            CreatedBuffers = true;
        }
 
        /// <summary>
        /// Creates a VBO and fills it with the specified data
        /// </summary>
        /// <param name="data">The data to fill the VBO with</param>
        /// <returns>The ID of the buffer location</returns>
        unsafe int CreateVBO(Single[] data)
        {
            Int32 id = 0;
            Int32 length = (data.Length * 8 * sizeof(Single));
 
            //unbind - just in case this is causing us the invalid exception problems
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
 
            //try to create buffer - this is where it dies!
            GL.GenBuffers(1, out id);
 
            //check error code (this is where we detect the Invalid Operation error)
            ErrorCode ec = GL.GetError();
            if (ec != ErrorCode.NoError)
            {
                MessageBox.Show("Attempt to Allocate VBOs on GPU Failed with Error: " + ec.ToString());
                return 0;
            }
 
            GL.BindBuffer(BufferTarget.ArrayBuffer, id);
            GL.BufferData(BufferTarget.ArrayBuffer, (IntPtr)(length), data, BufferUsageHint.DynamicDraw);           
 
            //check the buffer to make sure it matches expectations
            Int32 bufferSize;
            GL.GetBufferParameter(BufferTarget.ArrayBuffer, BufferParameterName.BufferSize, &bufferSize);
            if (length * sizeof(float) != bufferSize)
            {
                MessageBox.Show("Vertex array not uploaded correctly");
                return 0;
            }
 
            //reset binding so we can do work elsewhere
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
 
            return id;
        }
 
        /// <summary>
        /// Binds the current context to our VBOs for this Layer
        /// </summary>
        /// <param name="pointid">location in the shader for our points</param>
        /// <param name="normalid">location in the shader for our normals</param>
        void BindBuffers(Int32 pointid, Int32 normalid) {
 
            GL.BindBuffer(BufferTarget.ArrayBuffer, VBOid[0]);
            GL.EnableVertexAttribArray(pointid);
            GL.VertexAttribPointer(id, 3, VertexAttribPointerType.Float, false, 0, 0);
            GL.BindBuffer(BufferTarget.ArrayBuffer, VBOid[1]);
            GL.EnableVertexAttribArray(normalid);
            GL.VertexAttribPointer(id, 3, VertexAttribPointerType.Float, false, 0, 0);
        }
 
        /// <summary>
        /// Draw the layer. Called in the render loop
        /// </summary>
        /// <param name="pointid">location in the shader for our points</param>
        /// <param name="normalid">location in the shader for our normals</param>
        /// <param name="materialid">location in the shader for our RGBA Material</param>
        public void Draw(Int32 pointid, Int32 normalid, Int32 materialid)
        {
            lock (positions)
            {
                lock (normals)
                {
                    if (!pauseRender)
                    {
                        BindBuffers(pointid, normalid);
                        GL.Uniform4(materialid, this.Material);
                        GL.DrawArrays(BeginMode.Triangles, 0, positions.Count);
                    }
                }
            }
        }
    }
 
 public class GLGeometry
    {
        public Int64 EntityLabel { get; set; }
        public List<Single> Positions { get; set; }
        public List<Single> Normals { get; set; }
    }

Help very much appreciated. Thanks in advance, and thanks for the ANGLE suggestion. I was aware it was used for webgl, but didn't realise I could drop the dlls in and use it for my GLES 2 stuff too!

LloydP's picture

OK - I feel like a real tool. Problem solved.

The problem wasn't actually related to VBOs at all, there was a previous GL Error thrown, and the first time I called for errors was after I created my VBOs.

Thanks.