IOS and OpenGL

Hey guys,

I’m very new to Juce and I am trying to set up a simple OpenGL app that would run on Win/Linux/MacOSX/iOS/Android with the same code base.
Right now, I’m just trying to display a simple RGB triangle on the screen under every available platform.

So far, this app works well on MacOSX and Windows (RGB triangle is displayed correctly), but somehow it doesn’t work on iOS (it display a black screen with the Juce menu).

Questions :

  1. What the hell is wrong with my code ? I’ve tried many different approaches, let me know if I’m doing so bad practice or anything wrong …
  2. I’m using some #if for the OpenGL ES headers, I have the feeling that it is not the right thing to do, Juce is supposed to handle that, right ?
  3. I haven’t found any GL iOS sample for Juce, is there one somewhere ?

Cheers, here is the code :

#include "../JuceLibraryCode/JuceHeader.h"

#if JUCE_IOS
# include <OpenGLES/ES1/gl.h>
# include <OpenGLES/ES1/glext.h>
# include <OpenGLES/ES2/gl.h>
# include <OpenGLES/ES2/glext.h>
#endif

class Window;
class OpenGLCanvas;
class App;

class OpenGLCanvas : public Component, public OpenGLRenderer, public Timer, public ApplicationCommandTarget
{
public:
    OpenGLCanvas(Window& window)
    {
        m_OpenGLContext.setRenderer(this);
        m_OpenGLContext.setComponentPaintingEnabled (true);
        m_OpenGLContext.attachTo(*getTopLevelComponent());

        startTimer(1000 / 30);
    }

    ~OpenGLCanvas()
    {
        m_OpenGLContext.detach();
    }

    void timerCallback()
    {
    }

    void newOpenGLContextCreated()
    {
    }
    
    virtual void renderOpenGL()
	{
		GLfloat vertices[] =
		{
			-0.5, -0.5, 0.0,
			 0.5, -0.5, 0.0,
			 0.0,  0.5, 0.0
		};
		
		GLfloat colors[] =
		{
			1.0, 0.0, 0.0,
			0.0, 1.0, 0.0,
			0.0, 0.0, 1.0
		};
		
		OpenGLHelpers::clear(Colours::black);
		
		glLoadIdentity();
	
		glEnableClientState(GL_VERTEX_ARRAY);
		glEnableClientState(GL_COLOR_ARRAY);
		
		glVertexPointer(3, GL_FLOAT, 0, vertices);
		glColorPointer(3, GL_FLOAT, 0, colors);
		
		glDrawArrays(GL_TRIANGLES, 0, 3);
		
		glDisableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
		
	}
	
    virtual void openGLContextClosing()
    {
    }

    ApplicationCommandTarget* getNextCommandTarget()
    {
        // this will return the next parent component that is an ApplicationCommandTarget (in this
        // case, there probably isn't one, but it's best to use this method in your own apps).
        return findFirstTargetParentComponent();
    }

    void getAllCommands (Array <CommandID>& commands)
    {
        const CommandID ids[] =
        {
            MoveLeft,
            MoveRight,
            MoveUp,
            MoveDown,
            MoveFar,
            MoveNear,
        };

        commands.addArray(ids, numElementsInArray(ids));
    }

    void getCommandInfo (CommandID commandID, ApplicationCommandInfo& result)
    {
        const String general ("General" );

        switch(commandID)
        {
        case MoveLeft:
            result.setInfo("Camera Movement", "Move left", general, 0);
            result.addDefaultKeypress (KeyPress::leftKey, ModifierKeys::noModifiers);
            break;
        case MoveRight:
            result.setInfo("Camera Movement", "Move right", general, 0);
            result.addDefaultKeypress (KeyPress::rightKey, ModifierKeys::noModifiers);
            break;
        case MoveUp:
            result.setInfo("Camera Movement", "Move up", general, 0);
            result.addDefaultKeypress (KeyPress::pageUpKey, ModifierKeys::noModifiers);
            break;
        case MoveDown:
            result.setInfo("Camera Movement", "Move down", general, 0);
            result.addDefaultKeypress (KeyPress::pageDownKey, ModifierKeys::noModifiers);
            break;
        case MoveFar:
            result.setInfo("Camera Movement", "Move far", general, 0);
            result.addDefaultKeypress (KeyPress::upKey, ModifierKeys::noModifiers);
            break;
        case MoveNear:
            result.setInfo("Camera Movement", "Move near", general, 0);
            result.addDefaultKeypress (KeyPress::downKey, ModifierKeys::noModifiers);
            break;
        default:
            break;
        };
    }

    bool perform(const InvocationInfo& info)
    {
        return true;
    }

private:
    OpenGLContext    m_OpenGLContext;

    enum CommandIDs
    {
        MoveLeft   = 0x0001,
        MoveRight  = 0x0002,
        MoveUp     = 0x0003,
        MoveDown   = 0x0004,
        MoveFar    = 0x0005,
        MoveNear   = 0x0006,
    };
};

class Window : public DocumentWindow
{
public:
    Window()
        : DocumentWindow("MyApp", Colours::grey, DocumentWindow::allButtons, true)
    {
        addKeyListener(m_CommandManager.getKeyMappings());

        m_Canvas = new OpenGLCanvas(*this);
        setContentComponent(m_Canvas);
        // m_Canvas->setApplicationCommandManagerToWatch(&m_CommandManager);

        m_CommandManager.registerAllCommandsForTarget(m_Canvas);
    }

    ~Window()
    {
    }

    void closeButtonPressed()
    {
        JUCEApplication::quit();
    }

private:
    ApplicationCommandManager    m_CommandManager;
    ScopedPointer<OpenGLCanvas>  m_Canvas;
};

class App  : public JUCEApplication
{
public:
    App()
    {
    }

    ~App()
    {
    }

    void initialise (const String& commandLine)
    {
        #if JUCE_IOS || JUCE_ANDROID
            m_Window.setVisible (true);
            m_Window.setFullScreen (true);
        #else
            m_Window.centreWithSize (800, 600);
            m_Window.setVisible (true);
        #endif
    }

    void shutdown()
    {
    }

    void systemRequestedQuit()
    {
        quit();
    }

    const String getApplicationName()
    {
        return "MyApp";
    }

    const String getApplicationVersion()
    {
        return ProjectInfo::versionString;
    }

    bool moreThanOneInstanceAllowed()
    {
        return false;
    }

    void anotherInstanceStarted (const String& commandLine)
    {
        
    }

private:
    Window         m_Window;
};

START_JUCE_APPLICATION(App)

Your questions w/ answers:

  1. See below. On iOS and Android it seems the default context is setup for 2.x / shaders and not 1.x like your sample.
  2. You shouldn’t need to add the OpenGL ES headers
  3. Not too many demos available for standalone GL. I’d say rip out the “OpenGLDemo.cpp” file from the Juce Demo and try and get that working first.

You may want to verify a few things.

  1. Change the clear color from black to make sure the context is setup correctly at all.

  2. Verify what version of GL/ES is setup for the context. By default on Android / iOS it seems 2.x is the default context setup hence you need to use shaders for your GL code.
    2a. The 1.x code you are trying won’t render if things are setup for 2.x.

  3. On Android right now the Juce backend is not fully finished and there are a lot of bugs. The best way to test a standalone GL example is to only add the GL based component to a window “setContentOwned()”; you also have to have setOpacity(true) to the GL component on Android. There are other Juce internal problems to slightly modify for use on Android as well.

I’ll hopefully be contributing back my changes and GL demos soon.

Other software things:
I use this “openGLContext.attachTo (*this)” instead of the top level component which is the entire window.
If you aren’t getting rendering callbacks you could throw “openGLContext.triggerRepaint()” into the timer callback method.

Hello,

I would be really interested in knowing if your solution to use openGL on ios worked. If it is the case, could you explain how you did it.

 

Thanks !!!