# Saving OpenGL renders on iOS

**URL:** <https://forum.juce.com/t/saving-opengl-renders-on-ios/51321>\
**Category:** MacOSX and iOS\
**Created:** [May 6, 2022, 1:31pm UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321 "2022-05-06T13:31:17Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![splintersilk](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/splintersilk/32/14724_2.png) [@splintersilk](https://forum.juce.com/u/splintersilk)\
**Post date:** [May 6, 2022, 1:31pm UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321/1 "2022-05-06T13:31:17Z")

</div>

Hi there

Within an active openGLContext (ie inside the render method) I can use this:

```auto
        Image snapshotImage = Image(OpenGLImageType().create(Image::ARGB, getWidth(), getHeight(), true));
        OpenGLFrameBuffer *buffer = OpenGLImageType::getFrameBufferFrom(snapshotImage);
        buffer->makeCurrentRenderingTarget();
        renderScene();
        buffer->releaseAsRenderingTarget();

```

…  
From then on (but within the same method so that the openGLContext is still active and current) I can save the image to disk, or manipulate it in other ways really nicely on MacOS.

With iOS however I get fatal errors:

_GL\_INVALID\_ENUM at /Users/jeffr/git/submodules/tracktion\_engine/modules/juce/modules/juce\_opengl/opengl/juce\_OpenGLFrameBuffer.cpp : 125_

Something is passing back a number of which there is no corresponding enum value.

I have tried other ways of manipulating the data captured via _makeCurrentRenderingTarget_, such as doing a _std::memcpy_. Even _snapshotImage.getPixelAt(int pixelX, int pixelY)_. Everything I have tried thus far will trigger a crash.

My guess is that the memory for openGL on iOS is protected somehow, perhaps because of threading?

Does anyone have any idea/advice on how I might be able to get past this please?

With best wishes  
Jeff

---

<div class="post-metadata">

**Author:** ![parawave](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/parawave/32/5292_2.png) [@parawave](https://forum.juce.com/u/parawave)\
**Post date:** [May 6, 2022, 2:33pm UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321/2 "2022-05-06T14:33:20Z")

</div>

The interesting thing here is that GL\_INVALID\_ENUM is triggered on unbind().

```auto
context.extensions.glBindFramebuffer (GL_FRAMEBUFFER, context.getFrameBufferID());
JUCE_CHECK_OPENGL_ERROR

```

So the default context framebuffer is invalid, or either there is another call previously in renderScene(); that triggers the error.

Try using a OpenGLHelpers::resetErrorState() before the framebuffer unbind to check if  
it’s really the unbind causing it.

iOS is using GLES right? What GL version are you using for the context? Could be a totally unrelated thing causing a wrong enum error. Like glEnable(… ) or other calls.

---

<div class="post-metadata">

**Author:** ![reuk](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/reuk/32/21494_2.png) [@reuk](https://forum.juce.com/u/reuk)\
**Post date:** [May 6, 2022, 3:21pm UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321/3 "2022-05-06T15:21:23Z")

</div>

It looks like `glBindFramebuffer` should only generate `GL_INVALID_ENUM` if the first argument is not `GL_((READ|DRAW)_)?FRAMEBUFFER`. It’s more likely that the error is triggered elsewhere, and we only find out about it here.

You could try using [`glDebugMessageCallback`](https://www.khronos.org/registry/OpenGL-Refpages/es3/html/glDebugMessageCallback.xhtml) to register a function that will be called on error, and stick a breakpoint in that function to find out exactly where the error originates.

---

<div class="post-metadata">

**Author:** ![parawave](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/parawave/32/5292_2.png) [@parawave](https://forum.juce.com/u/parawave)\
**Post date:** [May 6, 2022, 3:32pm UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321/4 "2022-05-06T15:32:18Z")

</div>

If the GLES core profile is used, it’s probably glEnable (GL\_TEXTURE\_2D) that triggers it (error 1280).

Currently the calls are only disabled for Android.

Discovered this recently here (5).

> [@Various OpenGL Fixes (Windows)](https://forum.juce.com/t/various-opengl-fixes-windows/51289):
>
> Related to wglCreateContextAttribsARB @t0m @reuk I looked into a few more WGL extensions and discovered some interesting things. Probably information overload. But hey, feel free to implement these one at a time sweat_smile It looks more like it actually is, I swear joy If anyone has hints or ideas for other improvements, please share them here. 1. Context Release behavior Reason: If there are context switches mid frame, the command pipeline will flush and trigger parts of renderin…

---

<div class="post-metadata">

**Author:** ![splintersilk](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/splintersilk/32/14724_2.png) [@splintersilk](https://forum.juce.com/u/splintersilk)\
**Post date:** [May 6, 2022, 4:30pm UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321/5 "2022-05-06T16:30:12Z")

</div>

Hey there @parawave and @reuk. Really appreciate the fast response and thoughts on this.

Hmm I’m a little confused though. No errors are thrown up from the  
`buffer->makeCurrentRenderingTarget()` or the  
`buffer->releaseAsRenderingTarget()`;

In fact if I only have this (below) in the render method I don’t get an error.

```nohighlight
        Image snapshotImage = Image(OpenGLImageType().create(Image::ARGB, getWidth(), getHeight(), true));
        OpenGLFrameBuffer *buffer = OpenGLImageType::getFrameBufferFrom(snapshotImage);
        buffer->makeCurrentRenderingTarget();
        renderScene();
        buffer->releaseAsRenderingTarget();

```

…  
but if I append this:

```
   snapshotImage.getPixelAt(50,50);

```

…it will error. Maybe it’s cos without that line the rest just gets compiled out for not being used. Adding a breakpoint and stepping in and over I find that the this line:

```
   const BitmapData srcData (*this, x, y, 1, 1);

```

…in _Colour Image::getPixelAt_ of juce\_image.cpp is where I get the invalid enum.  
If I resume execution I ultimately crash out with a SIGTRAP here in _\_\_mutex.base_.

```cpp
condition_variable::wait(unique_lock<mutex>& __lk, _Predicate__ pred)
{
    while (!__pred())
        wait(__lk);
}

```

Is that a clue? That a mutex was involved?

I’m happy to upload a simple test case of this which is based on the OpenGL tutorial from the JUCE website. Basically my test case is that, with the teapot embedded as binary data, and my above code put into the render() method.

---

<div class="post-metadata">

**Author:** ![parawave](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/parawave/32/5292_2.png) [@parawave](https://forum.juce.com/u/parawave)\
**Post date:** [May 6, 2022, 4:51pm UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321/6 "2022-05-06T16:51:33Z")

</div>

Try something like this, to see if any errors are produced in between. GL errors are only triggered if you check them explicitly with glGetError !

```auto

Image snapshotImage = Image(OpenGLImageType().create(Image::ARGB, getWidth(), getHeight(), true));
OpenGLFrameBuffer *buffer = OpenGLImageType::getFrameBufferFrom(snapshotImage);

buffer->makeCurrentRenderingTarget();

OpenGLHelpers::resetErrorState();
glFinish();

renderScene();

for (;;)
{
	const GLenum e = glGetError();
	if (e == GL_NO_ERROR)
		break;
		
	jassertfalse;
}

OpenGLHelpers::resetErrorState();
glFinish();

buffer->releaseAsRenderingTarget();

OpenGLHelpers::resetErrorState();
glFinish();

snapshotImage.getPixelAt(50,50);

```

Don’t forget. GL commands are async. Could be the the framebuffers are not valid, or not accessible yet. So throw in glFinish() to make sure everything is executed until that point. CPU/GPU sync.

---

<div class="post-metadata">

**Author:** ![splintersilk](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/splintersilk/32/14724_2.png) [@splintersilk](https://forum.juce.com/u/splintersilk)\
**Post date:** [May 6, 2022, 5:25pm UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321/7 "2022-05-06T17:25:32Z")

</div>

> [@parawave](#):
>
> ```auto
> OpenGLHelpers::resetErrorState();
> glFinish();
> 
> snapshotImage.getPixelAt(50,50);
> 
> ```

Hey @parawave  
Thanks for that! I was really hopeful that that might work, but alas I ended up with the same mutex\_base error, even with all the glFinish() statements (to be clear I did use what you kindly provided verbatim, as well as reordering stuff.

I also tried this where I add a snapshotImage.createCopy() to try and force an explicit copy of the data out into a memberVariable with class scope/lifetime (rather than function scope/lifetime).

```cpp
        Image snapshotImage = Image(OpenGLImageType().create(Image::ARGB, getWidth(), getHeight(), true));
        OpenGLFrameBuffer *buffer = OpenGLImageType::getFrameBufferFrom(snapshotImage);

        buffer->makeCurrentRenderingTarget();
        OpenGLHelpers::resetErrorState();
        glFinish();

        renderScene();

        for (;;)
        {
            const GLenum e = glGetError();
            if (e == GL_NO_ERROR)
                break;

            jassertfalse;
        }

        OpenGLHelpers::resetErrorState();
        glFinish();

        buffer->releaseAsRenderingTarget();
        OpenGLHelpers::resetErrorState();
        glFinish();

        memberVariableImage = snapshotImage.createCopy();

        OpenGLHelpers::resetErrorState();
        glFinish();

        memberVariableImage.getPixelAt(50,50);

```

Oddly enough the crash doesn’t happen on the createCopy() but with the getPixel, which is odd as I’m actually running it on a totally different image!

It’s like whenever I try to touch the image data it throws a hissy fit. I am still wondering if iOS is multithreading the rendering and write to image data, and that maybe its tricky to get to that one moment when the image is complete and acailable for access (please pardon my lack of understanding).

Surely it must still be possible though right? Is there a more powerful version of glFinish() that really does grind everything to a halt so you can sync things safely?

---

<div class="post-metadata">

**Author:** ![parawave](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/parawave/32/5292_2.png) [@parawave](https://forum.juce.com/u/parawave)\
**Post date:** [May 6, 2022, 7:08pm UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321/8 "2022-05-06T19:08:47Z")

</div>

Wow this is odd.

Pixel access on an Image uses BitmapData. In case of OpenGLImage a temporary framebuffer copy with glReadPixels. Clone on the other hand uses Graphics to draw one framebuffer into another.

Perhaps really some access problem. Or more likely the format? Check out OpenGLFrameBuffer::readPixels

You can essentially just copy the code there to get PixelARGB directly. Pseudo:

```auto
bind framebuffer ()
glPixelStorei (GL_PACK_ALIGNMENT, 4);
glReadPixels (.... , JUCE_RGBA_FORMAT, GL_UNSIGNED_BYTE, pixels );
unbind framebuffer()

```

Now it could be that the framebuffer read doesn’t support reading pixels in that format. Not sure.  
[https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glReadPixels.xhtml](https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glReadPixels.xhtml)

You should really try to nail down what GL function actually causes the error. Attaching the debug callbakc gives more specific messages.

> **[LearnOpenGL - Debugging](https://learnopengl.com/In-Practice/Debugging)**
>
> Learn OpenGL . com provides good and clear modern 3.3+ OpenGL tutorials with clear examples. A great resource to learn modern OpenGL aimed at beginners.

---

<div class="post-metadata">

**Author:** ![splintersilk](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/splintersilk/32/14724_2.png) [@splintersilk](https://forum.juce.com/u/splintersilk)\
**Post date:** [May 7, 2022, 6:32pm UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321/9 "2022-05-07T18:32:00Z")

</div>

Thanks again for the help, I finally got this working.

Ultimately this is what I needed:

```cpp
        glReadBuffer(GL_BACK);
        glReadPixels(0, 0, width, height, GL_RGBA, GL_UNSIGNED_BYTE, pixels.data );

```

Without the GL\_BACK I was getting some corrupt image data, which made sense based on my google search travels.

Also bear in mind the dpi issue or you’ll render “cropped regions”.

If someone finds this thread and has questions feel free to ask, I’ll try and help 🙂

---

<div class="post-metadata">

**Author:** ![fbecir](https://sea2.discourse-cdn.com/flex026/user_avatar/forum.juce.com/fbecir/32/12998_2.png) [@fbecir](https://forum.juce.com/u/fbecir)\
**Post date:** [May 7, 2026, 7:21am UTC](https://forum.juce.com/t/saving-opengl-renders-on-ios/51321/10 "2026-05-07T07:21:11Z")

</div>

Hello

I use the method listed here (my application works on Windows, MacOS and LINUX), but I have a strange result.

Here, if I do a screenshot :

 ![Screen](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/b/a/ba14e48b3f4852d24de8150a89124f3f039e78fd.png)

Here if I use my export function :

 ![Save](https://us1.discourse-cdn.com/flex026/uploads/juce/original/3X/a/e/aeab952146852750e4c867ef2a7843fa50fdd0e5.png)

I am viewing LIDAR points. On the screenshot, all the points are present, but with my Save function, it seems that some of them are missing.

Any idea ?

Thanks a lot for your time !
