PDA

View Full Version : Equivalent for gl_LastFragData[0] on OpenGL



ksfenix
02-10-2014, 05:36 AM
Hello,
I have problem with alpha value in my application.

On OpenGL ES I use gl_LastFragData[0] for blending in shader. But how to do the same on OpenGL.
I spent many hours to check solutions on the Internet.
What I found step by step:

1. Create two FBO, first for store current view(_krFramebuffer) and second for store previous view(_krSecondFramebuffer)
2. Generate texture for second FBO.
3. Before drawing to first buffer use glBlitFramebufferEXT to copy _krFramebuffer to _krSecondFramebuffer.
4. Use texture from _krSecondFramebuffer in shader like gl_LastFragData[0] to get last color and alpha.
5. Blend colors.

But unfortunately this won't work. Result is the same as I don't use any shaders;

Here is my code how I get Texture



CGSize size = self.bounds.size;
glBindFramebufferEXT(GL_READ_FRAMEBUFFER_EXT, _krFramebuffer);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER_EXT, _krSecondFramebuffer);
glBlitFramebufferEXT( 0, 0, size.width, size.height, 0, 0, size.width, size.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _krSecondFramebuffer);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _krTexture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.bounds.size.width, self.bounds.size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _krTexture, 0);

GLuint loc = glGetUniformLocation(_programHandle, "previousTexture");
glUseProgram(_programHandle);

glUniform1i(loc, 0);
glBindFramebufferEXT(GL_DRAW_FRAMEBUFFER, _krFramebuffer);

Here I draw using glDrawElements.


My shaders:
Vertex:


#version 120

attribute vec4 Position;
attribute vec4 SourceColor;

varying vec4 DestinationColor;

void main(void)
{
DestinationColor = SourceColor;
gl_Position = Position;
gl_PointSize = 1.0;
}


Fragment:


#version 120

varying vec4 DestinationColor;

uniform sampler2D previousTexture;

void main(void)
{
vec4 lastColor = texture2D(previousTexture, gl_TexCoord[0].st);

vec3 lastColorRGB = lastColor.rgb;
float lastAlpha = lastColor.a;

vec3 destColorRGB = DestinationColor.rgb;
float destAlpha = DestinationColor.a;

float alpha = max(lastAlpha, destAlpha);

vec3 outColorRGB = destColorRGB * alpha;

vec4 outColor = vec4(outColorRGB, alpha);

gl_FragColor = outColor;
}


P.S. Sorry for my English :(

tonyo_au
02-10-2014, 04:03 PM
glBlitFramebuffer requires a read buffer and a write buffer



glBindFramebuffer(GL_FRAMEBUFFER, 0); // this seems to be required before binding to read and write to different buffers but may be obsolete
glBindFramebuffer(GL_READ_FRAMEBUFFER, from_buffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, to_buffer);
glReadBuffer(GL_COLOR_ATTACHMENT0);
GLenum buffers[] =
{
GL_COLOR_ATTACHMENT0,
};
glDrawBuffers(UT_ArraySize(buffers), buffers);
glBlitFramebuffer(0, 0, Width, Height,
0, 0, Width, Height,
GL_COLOR_BUFFER_BIT, GL_NEAREST );

ksfenix
02-11-2014, 01:40 AM
I tried to modify code like below, but I still have the same problem


CGSize size = self.bounds.size;

glBindFramebuffer(GL_FRAMEBUFFER, 0);
glBindFramebuffer(GL_READ_FRAMEBUFFER, _krFramebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _krSecondFramebuffer);
glReadBuffer(GL_COLOR_ATTACHMENT0);
GLenum buffers[] = {GL_COLOR_ATTACHMENT0,};
glDrawBuffers(1, buffers);
glBlitFramebuffer(0, 0, size.width, size.height, 0, 0, size.width, size.height, GL_COLOR_BUFFER_BIT, GL_NEAREST);

glBindFramebuffer(GL_FRAMEBUFFER, _krSecondFramebuffer);
glEnable(GL_TEXTURE_2D);

glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _krTexture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.bounds.size.width, self.bounds.size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _krTexture, 0);

glUseProgram(_programHandle);
GLuint loc = glGetUniformLocation(_programHandle, "previousTexture");

glUniform1i(loc, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _krFramebuffer);
// Add vertex to bufferData and draw using glDrawElements

arekkusu
02-11-2014, 01:16 PM
On OpenGL ES I use gl_LastFragData[0] for blending in shader.
There's a lot of misunderstanding here.

Before suggesting any solution: what are you actually trying to do? What is the high-level goal? What is the actual ES framebuffer setup? What is your scene content?

ksfenix
02-12-2014, 01:23 PM
In my application I use OpenGL for drawing on the screen using fingers. I use transparency color for drawing. Result of drawing on iPad is on first image.

My code on iPad:


- (void) setupBuffer
{
glGenRenderbuffers(1, &_colorRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _colorRenderBuffer);
[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(CAEAGLLayer *)self.layer];

glGenFramebuffers(1, &_framebuffer);
glBindFramebuffer(GL_FRAMEBUFFER, _framebuffer);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);

glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER,_colorRenderBuffer);



attribute vec4 a_position;
attribute vec4 a_color;

#ifdef GL_ES
varying lowp vec4 v_fragmentColor;
varying lowp vec4 v_position;
#else
varying vec4 v_fragmentColor;
#endif

void main()
{
v_position = a_position;
v_fragmentColor = a_color;
}



#extension GL_EXT_shader_framebuffer_fetch : require

precision lowp float;

varying vec4 v_fragmentColor;
varying vec4 v_position;

void main(void)
{
vec3 srcColor = v_fragmentColor.rgb;
float srcAlpha = v_fragmentColor.a;

vec3 dstColor = gl_LastFragData[0].rgb;
float dstAlpha = gl_LastFragData[0].a;

float finalAlpha;
vec3 finalColor;

finalAlpha = max(srcAlpha, dstAlpha);
finalColor = srcColor * finalAlpha;

gl_FragColor = vec4(finalColor, finalAlpha);
}

ksfenix
02-12-2014, 01:45 PM
On Desktop I want to have the same effect, but now I have that if I use OpenGL blend.
Configuration of blending and shaders


glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);



#version 120
attribute vec4 Position;
attribute vec4 SourceColor;

varying vec4 DestinationColor;

void main(void)
{
gl_TexCoord[0] = gl_MultiTexCoord0;
DestinationColor = SourceColor;
gl_Position = Position;
gl_PointSize = 1.0;
}



#version 120
varying vec4 DestinationColor;

void main(void)
{
gl_FragColor = DestinationColor;
}


After many configuration I dont have effect like on OpenGL ES.
So I tried to render previous state of view to texture(using FBO) and use it for program blending in shaders like in ES (shaders code is in first post)


- (void)createNewBuffer {
glGenFramebuffers(1, &_krFramebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _krFramebuffer);

glGenRenderbuffers(1, &_krRenderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _krRenderBuffer);

glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, self.bounds.size.width, self.bounds.size.height);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _krRenderBuffer);

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
DLog(@"Error framebuffer ext");
}

glGenFramebuffers(1, &_krSecondFramebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _krSecondFramebuffer);

glGenRenderbuffers(1, &_krSecondRenderbuffer);
glBindRenderbuffer(GL_RENDERBUFFER, _krSecondRenderbuffer);

glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA, self.bounds.size.width, self.bounds.size.height);
glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, _krSecondRenderbuffer);

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
DLog(@"Error framebuffer ext");
}
}

- (void)createTexture {
glBindFramebuffer(GL_FRAMEBUFFER, _krSecondFramebuffer);

glEnable(GL_TEXTURE_2D);

glGenTextures(1, &_krTexture);
glBindTexture(GL_TEXTURE_2D, _krTexture);

glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.bounds.size.width, self.bounds.size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _krTexture, 0);

glBindTexture(GL_TEXTURE_2D, 0);
glDisable(GL_TEXTURE_2D);

if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE)
{
DLog(@"Error :(");
}

glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

Here how I pass texture to shader.



glBindFramebuffer(GL_FRAMEBUFFER, _krSecondFramebuffer);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, _krTexture);

copyBufferContentToAnotherBuffer(_krFramebuffer, _krSecondFramebuffer, self.bounds.size.width, self.bounds.size.height);
glViewport(0, 0, self.bounds.size.width, self.bounds.size.height);

glUseProgram(_programHandle);
GLuint loc = glGetUniformLocation(_programHandle, "previousTexture");

glUniform1i(loc, 0);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _krFramebuffer);

Here is drawing uisng glDrawElements




void copyBufferContentToAnotherBuffer(GLuint sourceBuffer, GLuint destinationBuffer, GLuint width, GLuint height) {

glBindFramebuffer(GL_READ_FRAMEBUFFER, sourceBuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, destinationBuffer);
glViewport(0, 0, width, height);

glReadBuffer(GL_COLOR_ATTACHMENT0);

GLenum buffers[] = {GL_COLOR_ATTACHMENT0,};

glDrawBuffers(1, buffers);
glBlitFramebuffer(0, 0, width, height, 0, 0, width, height, GL_COLOR_BUFFER_BIT, GL_NEAREST);
}

After that I still have effect like in second picture.

What I do wrong?

How to add image to post? I don't have permission for that. This is obligatory for present my problem.

Images:
1. i58 dot tinypic dot com /2uo1068.png
2. i58 dot tinypicdot com /syl3es.png

arekkusu
02-12-2014, 02:51 PM
In my application I use OpenGL for drawing on the screen using fingers.

If you only need one finger at a time (that is, each frame you only draw one new quad-- and to have worked in iOS you must have been using EAGLDrawablePropertyRetainedBacking), then by far the simplest thing to do is to use regular blending with two passes.

You want this custom blend equation:
A = MAX(Asrc, Adst)
RGB = RGBsrc * A

...which you can implement without any extra framebuffer trickery, in two passes:
1) glColorMask(0,0,0,1); glBlendEquation(GL_MAX); draw quad
2) glColorMask(1,1,1,0); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_DST_ALPHA, GL_ZERO); draw the same quad


There are a lot of other bugs in your code which I'm happy to discuss.

ksfenix
02-12-2014, 02:58 PM
What other bugs you see ? :)


Which you can implement without any extra framebuffer trickery, in two passes:
1) glColorMask(0,0,0,1); glBlendEquation(GL_MAX); draw quad
2) glColorMask(1,1,1,0); glBlendEquation(GL_FUNC_ADD); glBlendFunc(GL_DST_ALPHA, GL_ZERO); draw the same quad
This will work on Desktop Open GL?

arekkusu
02-12-2014, 04:31 PM
This will work on Desktop Open GL?
It will work on every Intel Mac ever made.
GL_MAX is an ancient feature from GL1.4; you have to go back about ten years (http://en.wikipedia.org/wiki/Radeon_R100) to find a computer that it won't work on.

It will work on iOS, too (but more slowly than using gl_LastFragData.)




What other bugs you see ?

Lots. First, let's look at your original code to understand why it doesn't work:


glBlitFramebufferEXT(...)
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, _krSecondFramebuffer);
...
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, self.bounds.size.width, self.bounds.size.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
...
glFramebufferTexture2DEXT(GL_FRAMEBUFFER_EXT, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, _krTexture, 0);

In this sequence, you've just blit from _krFramebuffer to _krSecondFramebuffer, and then you're immediately re-allocating a new _krTexture and attaching it to _krSecondFramebuffer. So the contents of _krSecondFramebuffer are effectively undefined junk when you sample. This sequence doesn't make any sense.

Let's first review the concept of what the framebuffer is.

It looks like you're porting a GLPaint (https://developer.apple.com/library/ios/samplecode/GLPaint/Introduction/Intro.html) app from iOS to OSX. On iOS, you're using a single EAGL framebuffer, and I'm assuming that you're drawing a new quad each frame at the finger position, and accumulating all of the results into the single framebuffer over time. On iOS, this works by using kEAGLDrawablePropertyRetainedBacking. That EAGL framebuffer has a special "presentable" renderbuffer attached to it. By default, when you call -presentRenderbuffer: its contents become undefined (because, for performance reasons, the OS is maintaining a double- or triple-buffered queue behind your back.) kEAGLDrawablePropertyRetainedBacking changes the behavior so that it works more like a single buffer you can keep drawing into forever (but behind your back maybe it is copying the contents every time you present.)

On OSX, the equivalent is a window with an NSOpenGLView. In the pixel format, you can either make it single-buffered, or you can use NSOpenGLPFABackingStore to mimic iOS.

In either case, this "system drawable" is a special thing different from other FBOs; you can't texture from it (well... on OSX you can, with [NSOpenGLContext createTexture:fromView:internalFormat:] but let's ignore that.)

Keeping the closest equivalent to the iOS setup, the sequence should be something like:
* Create window, using NSOpenGLPFABackingStore to match EAGLDrawablePropertyRetainedBacking. This is "FBO zero" that will accumulate all drawing over time.
* Create one previousTexture, attach it to previousFBO. Initially clear it.
each frame:
* render this frame's new finger quad to window, with shader using custom blending, sampling from previousTexture
* blit (composited window result) to previousFBO to use next frame (you can optimize the blit to just the area around the quad.)

You really don't want to re-TexImage every frame, you only need to do that when the framebuffer changes size. You also only need to call glViewport when the size actually changes.

I'm discussing the basic framebuffer concept here, because in your later code you're creating multiple framebuffers and renderbuffers, and it looks very confused.
Another option would be to create two FBOs, with two textures, and ping-pong between them every frame. In that sort of setup, the window does not need NSOpenGLPFABackingStore. This is different than what you're doing in iOS, but is a common pattern in more complicated apps (i.e. games) that need a post-processing pass and render to texture anyway.



Let's look at some other little misunderstandings:

CGSize size = self.bounds.size;
This will be in points, not in pixels. It won't properly support a retina display.






GLuint loc = glGetUniformLocation(_programHandle, "previousTexture");
glUseProgram(_programHandle);
glUniform1i(loc, 0);

This is redundant. Uniforms are initialized to zero at link time.
If you were going to use a different unit, you should do this setup during init, not each frame.






gl_PointSize = 1.0;

This has no effect in desktop GL unless you glEnable(GL_PROGRAM_POINT_SIZE).
Unlike ES, desktop GL defaults to context-state glPointSize(). And this is all meaningless unless you're actually drawing points.






glBindFramebuffer(GL_FRAMEBUFFER, 0); // this seems to be required before binding to read and write to different buffers

Certainly not required.






glBindFramebuffer(GL_READ_FRAMEBUFFER, _krFramebuffer);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, _krSecondFramebuffer);
glReadBuffer(GL_COLOR_ATTACHMENT0);
GLenum buffers[] = {GL_COLOR_ATTACHMENT0,};
glDrawBuffers(1, buffers);

This is all redundant too. Read and Draw buffer state is tracked per-framebuffer. And it defaults to GL_COLOR_ATTACHMENT0 when you create an FBO.
(Basically, unless you use MRT you never need to call glRead/DrawBuffer.)



Finally, let's directly answer the original question. First, make sure you carefully understand what EXT_shader_framebuffer_fetch actually does:

* gl_LastFragData exposes the current framebuffer value as a fragment shader input. This happens per fragment. So if you draw two overlapping triangles, they are shaded in order: for the first triangle, gl_LastFragData sees i.e. the clear color. For the second triangle, gl_LastFragData sees the first triangle. You could shade millions of (potentially overlapping) triangles in a single draw call, and they would all be composited correctly, in order, with your custom blend equation.

This functionality relies on the TBDR architecture of iOS devices, and does not have any direct replacement on desktop GL. There are some options to approximate it:

* ARB_shader_image_load_store (GL4.2+) lets you read and write from arbitrary texels. If you render to a texture, you could manually implement blending. This is the closest replacement, but it's not available on OSX (even if it was, it would only work on recent GL4-capable Macs, so you'd still need another solution for the millions of older Macs.)
* NV_texture_barrier lets you sample from the same texture you are currently rendering to, but you have to manually flush writes before reads. So it doesn't handle overlapping triangles in a single draw call-- you have to manually split overlap into separate draws. (On the upside, you're not limited to reading only the current pixel; you can read neighboring texels too.) This is available everywhere in 10.9 Mavericks.
* Manually copy to a texture (via glBlitframebuffer, or glCopyTexImage) and sample from it. This is what you've attempted. Obviously, you can only copy what you've drawn, so you can decide to copy once per frame, or per overlapping triangle. This is available everywhere.

So for a completely general replacement, there isn't an easy answer; handing the overlapping case is complicated.

Your specific use case (assuming there is only one finger drawing at a time, so there is no chance of overlapping triangles in a single draw call) seems simple enough that NV_texture_barrier can work; however you have to be rendering to a texture, not the system drawable. Copying data to a texture can also work. But really I think the simplest answer (and closest to iOS-- no extra framebuffers) is to render each quad in two passes like I said in the previous reply.