Search code examples
c#openglopentkvao

OpenGL / OpenTK Drawing with Indices : Attempted to Read or Write Protected Memory Issue


I'm trying to display a quad (or two triangles) with C# and OpenGL. Below is my code for my mesh class. In it you have both the creation of the VBOs and VAO, as well as the function I use to render the mesh.

using System;
using System.Collections.Generic;
using System.Text;
using OpenTK.Graphics.OpenGL;
using BuildMe.Core;
using OpenTK;

namespace BuildMe.Render
{
    class Mesh
    {

        private Vector3[] verts;
        private uint[] indices;
        private int instances;
        private int VAO;

        public uint[] Indices { get => indices; set => indices = value; }
        public int Instances { get => instances; set => instances = value; }
        public Vector3[] Verts { get => verts; set => verts = value; }

        public Mesh(Vector3[] verts, uint[] indices, int instances)
        {
            this.verts = verts;
            this.indices = indices;
            this.instances = instances;
            this.VAO = CreateVAO();
            StoreVAOData();
        }

        private int CreateVAO()
        {
            int VAO = GL.GenVertexArray();
            RenderLoop.VAOs.Add(VAO);
            return (VAO);
        }

        private void StoreVAOData()
        {
            GL.BindVertexArray(VAO);

            LoadVerts();
            LoadIndices();

            GL.BindVertexArray(0);
        }

        private void LoadVerts()
        {
            int vbo = GL.GenBuffer();
            GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
            GL.BufferData(BufferTarget.ArrayBuffer, Vector3.SizeInBytes * verts.Length, verts, BufferUsageHint.StaticDraw);
            GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, Vector3.SizeInBytes, 0);
            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
            RenderLoop.VBOs.Add(vbo);
        }

        private void LoadIndices()
        {
            int vbo = GL.GenBuffer();
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, vbo);
            GL.BufferData(BufferTarget.ElementArrayBuffer, sizeof(int) * indices.Length, indices, BufferUsageHint.StaticDraw);
            GL.VertexAttribPointer(0, 1, VertexAttribPointerType.UnsignedInt, false, sizeof(int), 0);
            GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
            RenderLoop.VBOs.Add(vbo);
        }

        public int GetVAO()
        {
            return (VAO);
        }

        public void Render()
        {
            GL.BindVertexArray(this.GetVAO());
            GL.EnableVertexAttribArray(0);
            GL.EnableVertexAttribArray(1);
            GL.DrawElements(PrimitiveType.Triangles, this.Indices.Length, DrawElementsType.UnsignedInt, 0);
            GL.DisableVertexAttribArray(0);
            GL.DisableVertexAttribArray(1);
            GL.BindVertexArray(0);
        }

    }
}

Here is where I call the function to render the mesh.

        private void Render(object sender, FrameEventArgs e)
        {
            GL.EnableClientState(ArrayCap.VertexArray);
            GL.EnableClientState(ArrayCap.IndexArray);
            GL.Color3(Color.Green);
            foreach (Mesh mesh in SceneMeshes)
                mesh.Render();
        }

This is the error I'm getting, if it helps. But I think it just means I've either declared the VBOs wrong or I'm using the wrong functions to render it.

An unhandled exception of type 'System.AccessViolationException' occurred in OpenTK.dll Attempted to read or write protected memory. This is often an indication that other memory is corrupt.


Solution

  • EnableClientState and VertexAttribPointer do not interact together. If you want to use the client side capabilities, then you have to use VertexPointer (see glVertexPointer):

    GL.VertexAttribPointer(0, 3, VertexAttribPointerType.Float, false, Vector3.SizeInBytes, 0);

    GL.VertexPointer(3, VertexAttribPointerType.Float, Vector3.SizeInBytes, 0);
    

    GL.EnableClientState(ArrayCap.IndexArray); does not do what you expect. ArrayCap.IndexArray is meant for color index attributes.
    The index buffer is bound to the target BufferTarget.ElementArrayBuffer. It is not an attribute and has not to be enabled.
    Further more the index buffer is stated in the vertex array object. The instruction GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); would break the binding:

    private void LoadIndices()
    {
        int ibo = GL.GenBuffer();
        GL.BindBuffer(BufferTarget.ElementArrayBuffer, ibo);
        GL.BufferData(BufferTarget.ElementArrayBuffer, sizeof(int) * indices.Length, indices, BufferUsageHint.StaticDraw);
        // GL.VertexAttribPointer(...) <--- REMOVE
        // GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0); // <--- REMOVE
        RenderLoop.VBOs.Add(ibo);
    }
    

    The client side capability is stated in the Vertex Array Object. So it can be stated in LoadVerts():

    private void LoadVerts()
    {
        int vbo = GL.GenBuffer();
        GL.BindBuffer(BufferTarget.ArrayBuffer, vbo);
        GL.BufferData(BufferTarget.ArrayBuffer, Vector3.SizeInBytes * verts.Length, verts, BufferUsageHint.StaticDraw);
        // GL.VertexAttribPointer(...); <---- REMOVE
        GL.VertexPointer(3, VertexAttribPointerType.Float, Vector3.SizeInBytes, 0); // <--- ADD
        GL.EnableClientState(ArrayCap.VertexArray); // <--- ADD
        GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
        RenderLoop.VBOs.Add(vbo);
    }
    

    All the necessary specification is stated in the Vertex Array Object, so it is sufficient to bind the VAO, before the draw call:

    public void Render()
    {
        GL.BindVertexArray(this.GetVAO());
        GL.DrawElements(PrimitiveType.Triangles, this.Indices.Length, DrawElementsType.UnsignedInt, 0);
        GL.BindVertexArray(0);
    }
    
    private void Render(object sender, FrameEventArgs e)
    {
        GL.Color3(Color.Green);
        foreach (Mesh mesh in SceneMeshes)
            mesh.Render();
    }