[iOS] Standard component + OpenGLAppComponent flickering issue

Hello Juce folks,

I decided to ask you about a strange problem. Since I tried to find a solution myself but could not.

The problem is:
if I’m adding OpenGL component to the standard component. Then all the components on the background is flickering with black color.

This is not happening on Mac OS X or in the iOS simulator. It’s happening only with a real device. (I tried with many… iPhone, iPad 13, iPad 11, even with lower iOS versions).

Some observations:
// 1. The flickering only appears if I’m attaching OpenGL component to the “*this”.
openGLContext.attachTo(*this);

  // 2. But if I'm adding to the top component, then everything is fine.
   openGLContext.attachTo(*getTopLevelComponent());

I would leave it as it is with the getTopLevelComponent, but the the whole system getting very, very slow in this case. Like I feel it draws all the components to the openGL as big picture. (not sure… I’m not a specialist), but if with attachTo “*this”, then it’s fast, but the flickering can appear any time.

Some notes about the code:
All my non-opengl components uses:

    setBufferedToImage(true); // but even if I remove this, I still see flickering issue.
    setOpaque(true);

Attached a video of this issue (the YELLOW/ORANGE rect, this is OpenGL Component): https://youtu.be/Q9uShhG20MQ

Thank you!

OpenGL Component:
#include “wave_table_gl.h”
#include “…/…/…/…/…/common/gui_colors.h”

namespace wks::gui {

    WaveTableGL::WaveTableGL() {
        // Indicates that no part of this Component is transparent.
        setOpaque(true);

        // Tell the context to repaint on a loop.
       // openGLContext.setContinuousRepainting(true);
        setVisible (true);
        openGLContext.setContinuousRepainting (false);

        // openGLContext.setOpenGLVersionRequired(juce::OpenGLContext::OpenGLVersion::openGL3_2);
    }

    WaveTableGL::~WaveTableGL() {

        // Tell the context to stop using this Component.
        openGLContext.detach();
    }

    void WaveTableGL::initialise() {

    }

    void WaveTableGL::shutdown() {

    }

    void WaveTableGL::render() {

    }

    void WaveTableGL::newOpenGLContextCreated() {

        // Generate 1 buffer, using our vbo variable to store its ID.
        openGLContext.extensions.glGenBuffers(1, &vbo);

        // Generate 1 more buffer, this time using our IBO variable.
        openGLContext.extensions.glGenBuffers(1, &ibo);

        // Create 4 vertices each with a different colour.
        vertexBuffer = {
                // Vertex 0
                {
                        {-1.0f, 1.0f},        // (-0.5, 0.5)
                        {1.f, 0.f,  0.f, 1.f}  // Red
                },
                // Vertex 1
                {
                        {1.0f,  1.0f},         // (0.5, 0.5)
                        {1.f, 0.5f, 0.f, 1.f} // Orange
                },
                // Vertex 2
                {
                        {1.0f,  -1.0f},        // (0.5, -0.5)
                        {1.f, 1.f,  0.f, 1.f}  // Yellow
                },
                // Vertex 3
                {
                        {-1.0f, -1.0f},       // (-0.5, -0.5)
                        {1.f, 0.f,  1.f, 1.f}  // Purple
                }
        };

        // We need 6 indices, 1 for each corner of the two triangles.
        indexBuffer = {
                0, 1, 2,
               0, 2, 3
        };

        // Bind the VBO.
        openGLContext.extensions.glBindBuffer(GL_ARRAY_BUFFER, vbo);

        // Send the vertices data.
        openGLContext.extensions.glBufferData(
                GL_ARRAY_BUFFER,                        // The type of data we're sending.
                sizeof(Vertex) * vertexBuffer.size(),   // The size (in bytes) of the data.
                vertexBuffer.data(),                    // A pointer to the actual data.
                GL_STATIC_DRAW                          // How we want the buffer to be drawn.
        );

        // Bind the IBO.
        openGLContext.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

        // Send the indices data.
        openGLContext.extensions.glBufferData(
                GL_ELEMENT_ARRAY_BUFFER,
                sizeof(unsigned int) * indexBuffer.size(),
                indexBuffer.data(),
                GL_STATIC_DRAW
        );

        // ...
        vertexShader =
                "attribute vec4 position;\n"
                "\n"

                "\n"
                "void main()\n"
                "{\n"
                "    gl_Position = position;\n"
                "}\n";

        fragmentShader =
#if JUCE_OPENGL_ES
                "varying lowp vec4 destinationColour;\n"
#else
                "varying vec4 destinationColour;\n"
                #endif
                "\n"
                "void main()\n"
                "{\n"
                "    gl_FragColor =vec4(0.95, 0.57, 0.03, 0.7);\n"
                "}\n";

        // Create an instance of OpenGLShaderProgram
        shaderProgram.reset(new OpenGLShaderProgram(openGLContext));

        // Compile and link the shader.
        // Each of these methods will return false if something goes wrong so we'll
        // wrap them in an if statement
        if (shaderProgram->addVertexShader(vertexShader)
            && shaderProgram->addFragmentShader(fragmentShader)
            && shaderProgram->link()) {
            // No compilation errors - set the shader program to be active
            shaderProgram->use();
        } else {
            // Oops - something went wrong with our shaders!
            // Check the output window of your IDE to see what the error might be.
            printf("!!!!!ERROR SHADER !!!!!!");
            jassertfalse;
        }

    }

    void WaveTableGL::renderOpenGL() {

        // Clear the screen by filling it with black.
        OpenGLHelpers::clear(colors::body);

        // Tell the renderer to use this shader program
        shaderProgram->use();

        openGLContext.extensions.glBindBuffer(GL_ARRAY_BUFFER, vbo);
        openGLContext.extensions.glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo);

        // Enable the position attribute.
        openGLContext.extensions.glVertexAttribPointer(
                0,              // The attribute's index (AKA location).
                2,              // How many values this attribute contains.
                GL_FLOAT,       // The attribute's type (float).
                GL_FALSE,       // Tells OpenGL NOT to normalise the values.
                sizeof(Vertex), // How many bytes to move to find the attribute with
                // the same index in the next vertex.
                nullptr         // How many bytes to move from the start of this vertex
                // to find this attribute (the default is 0 so we just
                // pass nullptr here).
        );
        openGLContext.extensions.glEnableVertexAttribArray(0);

        // Enable to colour attribute.
        openGLContext.extensions.glVertexAttribPointer(
                1,                              // This attribute has an index of 1
                4,                              // This time we have four values for the
                // attribute (r, g, b, a)
                GL_FLOAT,
                GL_FALSE,
                sizeof(Vertex),
                (GLvoid *) (sizeof(float) * 2)    // This attribute comes after the
                // position attribute in the Vertex
                // struct, so we need to skip over the
                // size of the position array to find
                // the start of this attribute.
        );
        openGLContext.extensions.glEnableVertexAttribArray(1);


        glDrawElements(
                GL_TRIANGLES,       // Tell OpenGL to render triangles.
                indexBuffer.size(), // How many indices we have.
                GL_UNSIGNED_INT,    // What type our indices are.
                nullptr             // We already gave OpenGL our indices so we don't
                // need to pass that again here, so pass nullptr.
        );


        openGLContext.extensions.glDisableVertexAttribArray(0);
        openGLContext.extensions.glDisableVertexAttribArray(1);
    }

    void WaveTableGL::openGLContextClosing() {

    }

    void WaveTableGL::paint(Graphics &g) {

    }

    void WaveTableGL::parentHierarchyChanged() {

        openGLContext.detach();

        // Finally - we attach the context to this Component.
        openGLContext.attachTo(*this);

    }

}

NewProject2.zip (112.9 KB)

Have to say that this is happening only in the Standalone project (that use juce::AudioProcessorEditor). If I’m making simple GUI application that uses “juce::AudioAppComponent”, then the problem seems to be is gone.

Attached a test project for iOS. Does any body know reason why the screen is flickering? I have to continue write the app, but stuck with this stupid issue when it’s make the app not usable at all.

Thank you!

Any luck on this issue?

Just one more note that I noticed. In the basic gui app ( juce::AudioAppComponent). If I enable “Status Bar Hidden”, then child components is disappear and only opengl remains. i.e. same as in the Standartalone app.

I tried also disable “Requires Full Screen”, but it not helped and the flickering bug still appears.

Hello,

Hasn’t anyone come across this? This seems to be reproducible very easily on any iOS platform. Just add the basic component and then a OpenGL component as child. And you will see how the content from your basic component will gone and will be black.

If at the same time minimize the application or switch to other, then it will appear for a while, but then disappears again. This seems to be a critical issue as it prevents you from using both component types. In my application, I need to draw the main interface via component (CoreGraphics) plus via OpenGL heavy loaded part where I’m drawing wave table. But I’m stuck with this and not sure what todo.

Thanks!

Looks like with “Status Bar Hidden: DISABLED”, works stable, but I’m afraid that after release of my app it will be buggy on other devices. Because on iPad 13 inch it works even if status bar is enabled, but on another device I need to disable status bar to avoid black screen. (and I’m afraid that this is not connected with status-bar at all)

I’d like to hear a comment from the Juce developers as this is a serious issue. Application image is just black if an OpenGL component is added on top of a regular component.

This is what I get on various iOS devices (as you can see everything is black, except opengl component):

Thank you.

A similar issue was reported a few months back: Ios 14.4 ipad black screen - #5 by ed95

It looks like the current behaviour is due to a bug in iOS. The suggested workaround from the linked thread is to enable the status bar in your app by setting UIStatusBarHidden to NO in the app’s .plist.

@reuk Thanks for the quick response. I will continue to observe. But maybe you know some ways to fix this using the Juce framework (even if I add hardcoded fix to the sources)? I would not really like to have a status bar.

BTW. the bug only appears for me if OpenGLComponent attached to (*this), and everything is fine when to getTopComponent

ADDED: “BTW 2.0” The only seems a way to fix this is adding second OpenGLComponent 1x1px (somewhere on the screen). Then status bar can be enabled or disabled.

Thanks!

1 Like
2 Likes