JayTee JayTee - 1 year ago 69
Swift Question

Combining Multiple Render Targets (MRT) with Multisampling fails on iOS devices, not on the simulator

I'm trying to write an OpenGLES-3.0 Swift app on iOS (>= 8.0) that makes use of Multiple Render Targets (MRT). To get proper antialiasing, I enabled multisampling.

In detail, my rendering architecture looks like this:

  • The Display framebuffer has one renderbuffer attached:

    • The Display renderbuffer : controlled by iOS via EAGLContext.renderbufferStorage(), attached as GL_COLOR_ATTACHMENT0

  • The Sample framebuffer has two renderbuffers attached:

    • The Color renderbuffer I : GL_RGBA8, multisampled, attached as GL_COLOR_ATTACHMENT0

    • The Color renderbuffer II : GL_RGBA8, multisampled, attached as GL_COLOR_ATTACHMENT1

Whenever my layer changes its bounds, I resize all my renderbuffers as Apple does it in the GLPaint sample.

I created a minimal example for you. The rendering itself looks like this:

//Set the GL context, bind the sample framebuffer and specify the viewport:
glBindFramebuffer(GLenum(GL_FRAMEBUFFER), self.sampleFramebuffer)
glViewport(0, 0, self.layerWidth, self.layerHeight)

//Clear both render targets:
glClearBufferfv(GLenum(GL_COLOR), 0, self.colorRenderbufferIClearColor)
glClearBufferfv(GLenum(GL_COLOR), 1, self.colorRenderbufferIIClearColor)

//Specify the vertex attribute (only position, 6 floats for a triangle):
glVertexAttribPointer(0, 2, GLenum(GL_FLOAT), GLboolean(GL_FALSE), GLsizei(2 * sizeof(GLfloat)), nil)

//Use the shader program and render a single triangle:
glDrawArrays(GLenum(GL_TRIANGLES), 0, 3)

//Prepare both framebuffers as source and destination to do multisampling:
glBindFramebuffer(GLenum(GL_READ_FRAMEBUFFER), self.sampleFramebuffer)
glBindFramebuffer(GLenum(GL_DRAW_FRAMEBUFFER), self.displayFramebuffer)

//Specify from which of the attachments we do the multisampling.

//Transfer data between framebuffers and do multisampling:
glBlitFramebuffer(0, 0, self.layerWidth, self.layerHeight, 0, 0, self.layerWidth, self.layerHeight, GLbitfield(GL_COLOR_BUFFER_BIT), GLenum(GL_LINEAR))

//Invalidate the sample framebuffer for this pass:

//Bind the display renderbuffer and present it:
glBindRenderbuffer(GLenum(GL_RENDERBUFFER), self.displayRenderbuffer)

Now to the problem: My sample project draws a blue triangle on red background into the first render target (color renderbuffer I) and a purple triangle on green background into the second render target (color renderbuffer II). By setting blitAttachment in the code, you can select which of the two attachments gets resolved into the display framebuffer.

  • Everything works as expected on the iOS simulator (all devices, all iOS versions).

  • I only have access to an iPad Air (Model A1475, iOS 9.3.4) at the moment. But on the device, there are problems:

    • If I disable multisampling (level = 0 in glRenderbufferStorageMultisample()), everything works.

    • If I enable multisampling (level = 4), I can only blit from GL_COLOR_ATTACHMENT0 (which is color renderbuffer I).

    • Blitting from GL_COLOR_ATTACHMENT1 produces the same result (blue triangle on red), but should lead to the other one (purple triangle on green).

You can reproduce the problem with my attached sample code (DropBox).
So there are two questions:

  1. Could somebody please confirm that this works on the simulator, but not on real devices?

  2. Has anybody an idea about errors in my code? Or is this a known bug?

Thanks in advance!

Answer Source

There seem to be a bit of a strange behavior in this API. The code you linked does indeed work on the simulator but the simulator is quite different from the actual device so I suggest you never use it as reference.

So what seems to happen is that the render buffer is discarded simply too quickly. Why and how this happen I have no idea. You blit the buffers and then invalidate them so simply removing the buffer invalidation will remove the issue. But removing the buffer invalidation is not not suggested so rather ensure that all the tasks have been performed by the GPU before you invalidate them. That means simply calling flush.

Before you call glInvalidateFramebuffer(GLenum(GL_READ_FRAMEBUFFER), 2, [GLenum(GL_COLOR_ATTACHMENT0), GLenum(GL_COLOR_ATTACHMENT1)]) simply call glFlush():

//Resolve from source to destination while applying multisampling:
glBlitFramebuffer(0, 0, self.layerWidth, self.layerHeight, 0, 0, self.layerWidth, self.layerHeight, GLbitfield(GL_COLOR_BUFFER_BIT), GLenum(GL_LINEAR))
OpenGLESView.checkError(dbgDomain, andDbgText: "Failed to blit between framebuffers")


//Invalidate the whole sample framebuffer:
OpenGLESView.checkError(dbgDomain, andDbgText: "Failed to invalidate sample framebuffer")