Search code examples
androidopengl-esxamarinxamarin.androidopentk

Xamarin : OpenTK OpenGL Android crash


I am trying to create an iOS/Android demo app that shows Xamarin OpenTK / OpenGl rendering some cubes on the screen. Everything works in iOS but the same code in Android just crashes. This is completely shared code based off some of their demo code.

I get no information about the crash.

using OpenTK.Graphics.ES20;
using Xamarin.Forms;
using OpenTK.Graphics;
using OpenTK;
using System.Reflection;
using System.IO;
using System.Diagnostics;
using System;

namespace GLDemo
{
    public class App
    {
        public static Page GetMainPage ()
        {   
            return new OpenGLPage ();
        }
    }

    public class OpenGLPage : ContentPage
    {

        uint positionSlot;
        uint colorSlot;
        uint projectionSlot;
        uint modelViewSlot;

        uint colorRenderBuffer;
        uint depthBuffer;

        // cube verticies 
        Vector3[] Verticies = new Vector3[] { 
            new Vector3 (1.0f, -1.0f, 1.0f),
            new Vector3 (1.0f, 1.0f, 1.0f),
            new Vector3 (-1.0f, 1.0f, 1.0f),
            new Vector3(-1.0f, -1.0f, 1.0f),
            new Vector3(1.0f, -1.0f, -1.0f),
            new Vector3(1.0f, 1.0f, -1.0f),
            new Vector3(-1.0f, 1.0f, -1.0f),
            new Vector3(-1.0f, -1.0f, -1.0f)};

        Vector4[] Colors = new Vector4[]{
            new Vector4(0.0f, 0.0f, 0.0f, 1.0f),
            new Vector4(0.0f, 0.0f, 1.0f, 1.0f),
            new Vector4(0.0f, 1.0f, 0.0f, 1.0f),
            new Vector4(0.0f, 1.0f, 1.0f, 1.0f),
            new Vector4(1.0f, 0.0f, 0.0f, 1.0f),
            new Vector4(1.0f, 0.0f, 1.0f, 1.0f),
            new Vector4(1.0f, 1.0f, 0.0f, 1.0f),
            new Vector4(1.0f, 1.0f, 1.0f, 1.0f)};

        byte[] Indices = new byte [] 
        { 0, 1, 2,
            2, 3, 0,
            4, 6, 5,
            4, 7, 6,
            2, 7, 3,
            7, 6, 2,
            0, 4, 1,
            4, 1, 5,
            6, 2, 1,
            1, 6, 5,
            0, 3, 7,
            0, 7, 4};

        public OpenGLPage ()
        {
            Title = "OpenGL";
            var view = new OpenGLView { HasRenderLoop = true };
            var toggle = new Xamarin.Forms.Switch { IsToggled = true };
            var button = new Button { Text = "Display" };

            view.HeightRequest = 300;
            view.WidthRequest = 300;

            bool initialize = false;

            float rotation = 0.0f;
            float translation = 0.0f;
            bool goingRight = true;

            view.OnDisplay = r => {

                if(!initialize){
                    SetupDepthBuffer ();
                    SetupRenderBuffers ();
                    SetupFrameBuffer ();
                    CompileShaders ();
                    SetupVBOs ();
                    initialize = true;
                }

                GL.ClearColor(0.0f,0.0f,0.0f,1.0f);
                GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
                GL.Enable(EnableCap.DepthTest);

                rotation += 0.02f;
                if(goingRight){
                    translation += 0.01f;
                    if(translation > 1.0f){
                        goingRight = false;
                    }
                } else{
                    translation -= 0.01f;
                    if(translation < -1.0f){
                        goingRight = true;
                    }
                }

                for(int i = 0; i < 3; i++){
                    float h = 4.0f * (float)view.Height / (float)view.Width;
                    Matrix4[] projection = new Matrix4[]{ Matrix4.CreatePerspectiveOffCenter(-2, 2, -h/2, h/2, 4, 10) };
                    GL.UniformMatrix4 ((int)projectionSlot, false, ref projection[0]);

                    Matrix4[] modelView = new Matrix4[]{ Matrix4.CreateRotationX(rotation) * Matrix4.CreateRotationY(rotation) * Matrix4.CreateRotationZ(rotation) * Matrix4.CreateTranslation (translation - i * 3.5f + 4.0f, (float)(-1^i) * translation, -7 + translation) };
                    GL.UniformMatrix4 ((int)modelViewSlot, false, ref modelView [0]);

                    GL.DrawElements(BeginMode.Triangles, Indices.Length, DrawElementsType.UnsignedByte, 0);
                }
            };

            toggle.Toggled += (s, a) => {
                view.HasRenderLoop = toggle.IsToggled;
            };
            button.Clicked += (s, a) => view.Display ();

            var stack = new StackLayout { 
                Padding = new Size (20, 20),
                Children = {view, toggle, button}
            };

            Content = stack;


        }

        void SetupRenderBuffers(){
            GL.GenRenderbuffers (1, out colorRenderBuffer);
            GL.BindBuffer (BufferTarget.ArrayBuffer, colorRenderBuffer);
            GL.RenderbufferStorage (RenderbufferTarget.Renderbuffer, RenderbufferInternalFormat.Rgba4, 300, 300);
        }

        void SetupFrameBuffer(){
            uint frameBuffer;
            GL.GenFramebuffers (1, out frameBuffer);
            GL.BindFramebuffer (FramebufferTarget.Framebuffer, frameBuffer);
            GL.FramebufferRenderbuffer (FramebufferTarget.Framebuffer, FramebufferSlot.ColorAttachment0,
                RenderbufferTarget.Renderbuffer, colorRenderBuffer);
            GL.FramebufferRenderbuffer (FramebufferTarget.Framebuffer, FramebufferSlot.DepthAttachment,
                RenderbufferTarget.Renderbuffer, depthBuffer);
        }

        void SetupDepthBuffer(){
            GL.GenRenderbuffers (1, out depthBuffer);
            GL.BindRenderbuffer (RenderbufferTarget.Renderbuffer, depthBuffer);
            GL.RenderbufferStorage (RenderbufferTarget.Renderbuffer, RenderbufferInternalFormat.DepthComponent16, 300, 300);
        }

        uint CompileShader(string shaderName, ShaderType shaderType){
            string prefix;

            #if __IOS__ 
            prefix = "GLDemo.iOS.";
            #endif
            #if __ANDROID__
            prefix = "GLDemo.Android.";
            #endif

            var assembly = typeof(App).GetTypeInfo ().Assembly;


            foreach (var res in assembly.GetManifestResourceNames())
                System.Diagnostics.Debug.WriteLine("found resource: " + res);

            Stream stream = assembly.GetManifestResourceStream (prefix + shaderName + ".glsl");

            string shaderString;

            using (var reader = new StreamReader (stream)) {
                shaderString = reader.ReadToEnd ();
            }
            Debug.WriteLine (shaderString);
            uint shaderHandle = (uint)GL.CreateShader (shaderType);
            GL.ShaderSource ((int)shaderHandle, shaderString);
            GL.CompileShader (shaderHandle);

            return shaderHandle;
        }

        void CompileShaders(){
            uint vertexShader = CompileShader ("SimpleVertex", ShaderType.VertexShader);
            uint fragmentShader = CompileShader ("SimpleFragment", ShaderType.FragmentShader);
            uint programHandle = (uint)GL.CreateProgram ();
            GL.AttachShader (programHandle, vertexShader);
            GL.AttachShader (programHandle, fragmentShader);
            GL.LinkProgram (programHandle);
            GL.UseProgram (programHandle);

            positionSlot = (uint)GL.GetAttribLocation (programHandle, "Position");
            colorSlot = (uint)GL.GetAttribLocation (programHandle, "SourceColor");
            projectionSlot = (uint)GL.GetUniformLocation (programHandle, "Projection");
            modelViewSlot = (uint)GL.GetUniformLocation (programHandle, "Modelview");

            GL.EnableVertexAttribArray (positionSlot);
            GL.EnableVertexAttribArray (colorSlot);
            GL.EnableVertexAttribArray (projectionSlot);
            GL.EnableVertexAttribArray (modelViewSlot);

        }

        void SetupVBOs(){
            uint vertexBuffer;
            GL.GenBuffers (1, out vertexBuffer);
            GL.BindBuffer (BufferTarget.ArrayBuffer, vertexBuffer);
            GL.BufferData (BufferTarget.ArrayBuffer,(IntPtr)(Vector3.SizeInBytes * Verticies.Length), Verticies, BufferUsage.StaticDraw);
            GL.VertexAttribPointer (positionSlot, 3, VertexAttribPointerType.Float, false, Vector3.SizeInBytes, 0);

            uint colorBuffer;
            GL.GenBuffers (1, out colorBuffer);
            GL.BindBuffer (BufferTarget.ArrayBuffer, colorBuffer);
            GL.BufferData (BufferTarget.ArrayBuffer, (IntPtr)(Vector4.SizeInBytes * Colors.Length), Colors, BufferUsage.StaticDraw);
            GL.VertexAttribPointer (colorSlot, 4, VertexAttribPointerType.Float, false, Vector4.SizeInBytes, 0);

            uint indexBuffer;
            GL.GenBuffers (1, out indexBuffer);
            GL.BindBuffer (BufferTarget.ElementArrayBuffer, indexBuffer);
            GL.BufferData (BufferTarget.ElementArrayBuffer,(IntPtr)(sizeof(byte) * Indices.Length), Indices, BufferUsage.StaticDraw);
        }
    }
}

UPDATE

I believe this is something to with Android and VBO. Known issue? Anyone know how to solve it

Update 2

Updated to

void SetupVBOs(){
            uint vertexBuffer;
            GL.GenBuffers (1, out vertexBuffer);
            GL.BindBuffer (BufferTarget.ArrayBuffer, vertexBuffer);
            GL.BufferData (BufferTarget.ArrayBuffer,(IntPtr)(Vector3.SizeInBytes * Verticies.Length), Verticies, BufferUsage.StaticDraw);
            GL.VertexAttribPointer (positionSlot, 3, VertexAttribPointerType.Float, false, Vector3.SizeInBytes, 0);

            uint colorBuffer;
            GL.GenBuffers (1, out colorBuffer);
            GL.BindBuffer (BufferTarget.ArrayBuffer, colorBuffer);
            GL.BufferData (BufferTarget.ArrayBuffer, (IntPtr)(Vector4.SizeInBytes * Colors.Length), Colors, BufferUsage.StaticDraw);
            GL.VertexAttribPointer (colorSlot, 4, VertexAttribPointerType.Float, false, Vector4.SizeInBytes, 0);
        }

I try

GL.DrawElements(BeginMode.Triangles, Indices.Length, DrawElementsType.UnsignedByte, Indices);

it crashes on device

I try

            unsafe
            {
                fixed (byte* ptr = Indices)
                {
                    GL.DrawElements(BeginMode.Triangles, Indices.Length, DrawElementsType.UnsignedByte, new IntPtr(ptr));

                }
            }

And nothing renders with error

[Adreno-EGLSUB] : Invalid native buffer. Failed to queueBuffer [Adreno-EGLSUB] : native buffer is NULL


Solution

  • For some reason, Android crashes when we use an IBO (Index Buffer Object) with Xamarin.Forms + OpenGL. Instead pass the index information into DrawElements.
    void SetupVBOs(){
        uint vertexBuffer;
        GL.GenBuffers (1, out vertexBuffer);
        GL.BindBuffer (BufferTarget.ArrayBuffer, vertexBuffer);
        GL.BufferData (BufferTarget.ArrayBuffer,(IntPtr)(Vector3.SizeInBytes * Verticies.Length), Verticies, BufferUsage.StaticDraw);
        GL.VertexAttribPointer (positionSlot, 3, VertexAttribPointerType.Float, false, Vector3.SizeInBytes, 0);
    
        uint colorBuffer;
        GL.GenBuffers (1, out colorBuffer);
        GL.BindBuffer (BufferTarget.ArrayBuffer, colorBuffer);
        GL.BufferData (BufferTarget.ArrayBuffer, (IntPtr)(Vector4.SizeInBytes * Colors.Length), Colors, BufferUsage.StaticDraw);
        GL.VertexAttribPointer (colorSlot, 4, VertexAttribPointerType.Float, false, Vector4.SizeInBytes, 0);
    }
    

    EDIT

    I have revisited some OpenTK the past week or so, and I figured it out!

    GL.DrawElements() is meant to be used to pass in the index data, if you prefer to use an IBO, bind it up as you have shown, and instead use

    GL.DrawArrays(BeginMode.Triangles, 0, Indices.Length);