Hi there
Can anyone give me some help on this issue?
Hi Renaissance,
I’m not entirely sure what your use case is here. Do you mean the frame that you want to take a screen-shot of has a different effect applied to it compared to your normal render, for example a post-processing effect?
It sounds like render to texture will solve this, as you will be performing an off-screen render (see our RenderToTexture training course for an example use of this). You can perform a render pass for your “screen-shot” effect, then do a glReadPixels() to retrieve the rendered image (capture the screen-shot), then perform your normal render pass that will be rendered to the display.
In our PVRShell, there is a function called PVRShellScreenSave(), which can be used to save a screen shot of the current render. If you are already using the shell, you can use this function as-is. Otherwise, you can read through the code to see how we’ve implemented this feature.
You should be aware that there is a cost associated with glReadPixels() as it will remove the parallelism between the GPU and CPU (stalls will be introduced). For this reason, you should try to use this sparingly and should be aware of the performance hit you will taken when it is used.
Thanks,
Joe
Thank you so much, Joe.
Btw, which graphics API are you using? OGLES 1.x or 2.0?
Unless I’ve misunderstood your use case and you have the need to display frame i-1 repeatly (e.g. frames i, i+1, i+2 render exactly the same image as frame i-1), then I think you should do the following
…
OffScreenRender(render off screen, then capture with glReadPixels()) Frame i (render the normal scene within the RenderScene() block to render it to the screen)
Frame i+1(render to screen)
Frame i+2(render to screen)
…
So, basically you will still call RenderScene to do your normal render, but will also perform an off-screen render at the start of this for the render that you want to save. I’ll give some pseudo code to explain this (based on our OGLES2 render to texture training course - all psuedo code would be implemented in RenderScene() function):
/* Bind FBO for offscreen render /
glBindFramebuffer(GL_FRAMEBUFFER, m_uFBO);
…
/ Perform the off-screen render that you want to save /
…
/ Retrieve the rendered image - NOTE: this will stall the CPU as it has to wait for the GPU render to complete*/
glReadPixels();
// We are done with rendering to our FBO so switch back to the back buffer.
glBindFramebuffer(GL_FRAMEBUFFER, m_i32OriginalFbo);
…
/* Perform normal render that you want to be visible on the screen */
…
[/CODE]<br />
<br />/* Bind FBO for offscreen render */<br />
<br />glBindFramebuffer(GL_FRAMEBUFFER, m_uFBO);<br />
<br />...<br />
<br />/* Perform the off-screen render that you want to save */<br />
<br />...<br />
<br />/* Retrieve the rendered image - NOTE: this will stall the CPU as it has to wait for the GPU render to complete*/<br />
<br />glReadPixels();<br />
<br /><br />
<br />// We are done with rendering to our FBO so switch back to the back buffer.<br />
<br />glBindFramebuffer(GL_FRAMEBUFFER, m_i32OriginalFbo);<br />
<br />...<br />
<br />/* Perform normal render that you want to be visible on the screen */<br />
<br />...<br />
<br />
#include <math.h>
#include "OGLES2Tools.h"
shader attributes
******************************************************************************/
// vertex attributes
enum EVertexAttrib {
VERTEX_ARRAY, TEXCOORD_ARRAY, eNumAttribs };
const char* g_aszAttribNames[] = {
"inVertex", "inTexCoord" };
enum EUniform {
eMVPMatrix, eNumUniforms };
const char* g_aszUniformNames[] = {
"MVPMatrix" };
Content file names
******************************************************************************/
const char c_szFragShaderSrcFile[] = "FragShader.fsh";
const char c_szFragShaderBinFile[] = "FragShader.fsc";
const char c_szVertShaderSrcFile[] = "VertShader.vsh";
const char c_szVertShaderBinFile[] = "VertShader.vsc";
** Class: OGLES2RenderToTexture
******************************************************************************/
class OGLES2RenderToTexture : public PVRShell
{
// Print3D class used to display text
CPVRTPrint3D m_Print3D;
GLuint m_uiVertShader;
GLuint m_uiFragShader;
GLuint m_auiTexture[2];
GLuint m_uiTrunkTex;
GLuint m_uiVbo;
GLuint m_uFBO[1];
GLuint m_auiFbo[2];
int m_i32CurrentFbo;
int m_i32OriginalFbo;
GLuint m_auiDepthBuffer[2];
GLubyte *m_pScreenBuf;
// Group shader programs and their uniform locations together
struct
{
GLuint uiId;
GLuint auiLoc[eNumUniforms];
}
m_ShaderProgram;
float m_fAngle2;
unsigned int m_ui32Time;
virtual bool InitApplication();
virtual bool InitView();
virtual bool ReleaseView();
virtual bool QuitApplication();
virtual bool RenderScene();
bool LoadShaders(CPVRTString* pErrorStr);
void LoadVbos();
bool DrawScreen_S();
bool RenderFractal();
};
@Function LoadTextures
@Description Loads the textures required for this training course
******************************************************************************/
void OGLES2RenderToTexture::LoadTextures()
{
/*
Initialise the textures
*/
glGenTextures(1, &m_uiTrunkTex);
glBindTexture(GL_TEXTURE_2D, m_uiTrunkTex);
GLuint* pTexData = new GLuint[32*32];
for (int i=0; i<32; i++)
{
for (int j=0; j<32; j++)
{
pTexData[i*32+j] = 0xFF000000 + ((255 - (j-16)*(j-15)) << 8) + ((j * 8) & 0xFF);
}
}
delete [] pTexData;
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glGenTextures(2, m_auiTexture);
for (int i = 0; i < 2; ++i)
{
// Binds this texture handle so we can load the data into it
glBindTexture(GL_TEXTURE_2D, m_auiTexture);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, m_i32TexSize, m_i32TexSize, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
}
}
@Function LoadShaders
@Output pErrorStr A string describing the error on failure
@Return bool true if no error occured
@Description Loads and compiles the shaders and links the shader programs
required for this training course
******************************************************************************/
bool OGLES2RenderToTexture::LoadShaders(CPVRTString* pErrorStr)
{
/*
Load and compile the shaders from files.
Binary shaders are tried first, source shaders
are used as fallback.
*/
if (PVRTShaderLoadFromFile(
c_szVertShaderBinFile, c_szVertShaderSrcFile, GL_VERTEX_SHADER, GL_SGX_BINARY_IMG, &m_uiVertShader, pErrorStr) != PVR_SUCCESS)
{
return false;
}
c_szFragShaderBinFile, c_szFragShaderSrcFile, GL_FRAGMENT_SHADER, GL_SGX_BINARY_IMG, &m_uiFragShader, pErrorStr) != PVR_SUCCESS)
{
return false;
}
Set up and link the shader program
*/
{
PVRShellSet(prefExitMessage, pErrorStr->c_str());
return false;
}
for (int i = 0; i < eNumUniforms; ++i)
{
m_ShaderProgram.auiLoc = glGetUniformLocation(m_ShaderProgram.uiId, g_aszUniformNames);
}
}
@Function LoadVbos
@Description Loads the vertex data required for this training course into a
vertex buffer object
******************************************************************************/
void OGLES2RenderToTexture::LoadVbos()
{
float afVertexData[] = {
// trunk
-0.1f, -1.0f, 0.5f, 0.0f, 1.0f,
0.1f, -1.0f, 0.5f, 1.0f, 1.0f,
-0.08f, -0.4f, 0.5f, 0.0f, 0.2f,
0.08f, -0.4f, 0.5f, 1.0f, 0.2f,
0.0f, -0.3f, 0.5f, 0.5f, 0.0f,
-0.65f, 0.0f, 0.65f, 0.0f, 0.0f,
-0.65f, 1.3f, 0.65f, 0.0f, 1.0f,
0.65f, 0.0f, 0.65f, 1.0f, 0.0f,
0.65f, 1.3f, 0.65f, 1.0f, 1.0f,
};
glBindBuffer(GL_ARRAY_BUFFER, m_uiVbo);
glBufferData(GL_ARRAY_BUFFER, sizeof(afVertexData), afVertexData, GL_STATIC_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
@Function InitApplication
@Return bool true if no error occured
@Description Code in InitApplication() will be called by PVRShell once per
run, before the rendering context is created.
Used to initialize variables that are not dependant on it
(e.g. external modules, loading meshes, etc.)
If the rendering context is lost, InitApplication() will
not be called again.
******************************************************************************/
bool OGLES2RenderToTexture::InitApplication()
{
// Get and set the read path for content files
CPVRTResourceFile::SetReadPath((char*)PVRShellGet(prefReadPath));
return true;
}
@Function QuitApplication
@Return bool true if no error occured
@Description Code in QuitApplication() will be called by PVRShell once per
run, just before exiting the program.
If the rendering context is lost, QuitApplication() will
not be called.
******************************************************************************/
bool OGLES2RenderToTexture::QuitApplication()
{
return true;
}
@Function InitView
@Return bool true if no error occured
@Description Code in InitView() will be called by PVRShell upon
initialization or after a change in the rendering context.
Used to initialize variables that are dependant on the rendering
context (e.g. textures, vertex buffers, etc.)
******************************************************************************/
bool OGLES2RenderToTexture::InitView()
{
// Find the largest square power of two texture that fits into the viewport
m_i32TexSize = 1;
int iSize = PVRT_MIN(PVRShellGet(prefWidth), PVRShellGet(prefHeight));
while (m_i32TexSize * 2 < iSize) m_i32TexSize *= 2;
m_ui32Framenum = rand() % 5000;
m_ui32Time = PVRShellGetTime();
Initialize VBO data and load textures
*/
LoadVbos();
LoadTextures();
Load and compile the shaders & link programs
*/
CPVRTString ErrorStr;
if (!LoadShaders(&ErrorStr))
{
PVRShellSet(prefExitMessage, ErrorStr.c_str());
return false;
}
glUniform1i(glGetUniformLocation(m_ShaderProgram.uiId, "sTexture"), 0);
Initialize Print3D
*/
bool bRotate = PVRShellGet(prefIsRotated) && PVRShellGet(prefFullScreen);
{
PVRShellSet(prefExitMessage, "ERROR: Cannot initialise Print3Dn");
return false;
}
/*
Create two handles for a frame buffer object.
*/
glGenFramebuffers(2, m_auiFbo);
m_i32CurrentFbo = 1;
Get the currently bound frame buffer object. On most platforms this just gives 0.
*/
glGetIntegerv(GL_FRAMEBUFFER_BINDING, &m_i32OriginalFbo);
Attach the renderable objects (e.g. textures) to the frame buffer object now as
they will stay attached to the frame buffer object even when it is not bound.
*/
for(int i = 0; i < 2; ++i)
{
/*
Firstly, to do anything with a frame buffer object we need to bind it. In the case
below we are binding our frame buffer object to the frame buffer.
*/
glBindFramebuffer(GL_FRAMEBUFFER, m_auiFbo);
To render to a texture we need to attach it texture to the frame buffer object.
GL_COLOR_ATTACHMENT0 tells it to attach the texture to the colour buffer, the 0 on the
end refers to the colour buffer we want to attach it to as a frame buffer object can
have more than one colour buffer.
*/
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_auiTexture, 0);
glClear(GL_COLOR_BUFFER_BIT);
Create and bind a depth buffer to the frame buffer object.
required for most uses of frame buffer objects so its attachment is being
demonstrated here.
*/
glGenRenderbuffers(1, &m_auiDepthBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, m_auiDepthBuffer);
Currently it is unknown to GL that we want our new render buffer to be a depth buffer.
glRenderbufferStorage will fix this and in this case will allocate a depth buffer of
m_i32TexSize by m_i32TexSize.
*/
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, m_i32TexSize, m_i32TexSize);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_auiDepthBuffer);
}
Unbind the frame buffer object so rendering returns back to the backbuffer.
*/
glBindFramebuffer(GL_FRAMEBUFFER, m_i32OriginalFbo);
glClearColor(0.6f, 0.8f, 1.0f, 1.0f);
}
@Function ReleaseView
@Return bool true if no error occured
@Description Code in ReleaseView() will be called by PVRShell when the
application quits or before a change in the rendering context.
******************************************************************************/
bool OGLES2RenderToTexture::ReleaseView()
{
// Delete textures
glDeleteTextures(2, m_auiTexture);
glDeleteTextures(1, &m_uiTrunkTex);
glDeleteProgram(m_ShaderProgram.uiId);
glDeleteShader(m_uiFragShader);
glDeleteBuffers(1, &m_uiVbo);
glDeleteFramebuffers(2, m_auiFbo);
glDeleteRenderbuffers(2, m_auiDepthBuffer);
m_Print3D.ReleaseTextures();
}
@Function RenderScene
@Return bool true if no error occured
@Description Main rendering loop function of the program. The shell will
call this function every frame.
eglSwapBuffers() will be performed by PVRShell automatically.
PVRShell will also manage important OS events.
Will also manage relevent OS events. The user has access to
these events through an abstraction layer provided by PVRShell.
******************************************************************************/
bool OGLES2RenderToTexture::RenderScene()
{
/* vary the branch angles on the fractal sinusoidally */
m_fAngle = (float)(sin(0.25*PVRT_PIf*(float)(m_ui32Framenum)/256.0f))* 70.0f;
m_fAngle2 = (float)(sin((79.0f/256.0f)*2.0*PVRT_PIf*(float)(m_ui32Framenum)/256.0f))*100.0f + 30.0f;
m_fAngle *= 0.017453f;
m_fAngle2 *= 0.017453f;
if(PVRShellGetTime() - m_ui32Time > 10)
{
m_ui32Time = PVRShellGetTime();
m_ui32Framenum += 2;
m_ui32Framenum = 0;
}
glDisable(GL_DEPTH_TEST);
glDisable(GL_CULL_FACE);
if (m_framenumber == 5)
{
if(!DrawScreen_S())
return false;
}
if(!DrawScreen())
return false;
// Displays the demo name using the tools. For a detailed explanation, see the training course IntroducingPVRTools
m_Print3D.DisplayDefaultTitle("Render to Texture", "Using FBO", ePVRTPrint3DLogoIMG);
m_Print3D.Flush();
}
{
/*
We're going to do the following steps to create the effect. Texture 1 refers to the texture
attached to the first FBO. Texture 2 refers to the texture attached to the second FBO.
2. We draw two quads with Texture 1 applied.
3. We draw the trunk.
4. We make the back buffer current.
5. We draw 6 quads with Texture 2 applied.
7. We draw two quads with Texture 2 applied. Texture 2 still contains
the image from the last frame.
8. We draw the trunk.
9. We make the back buffer current.
10. We draw 6 quads with Texture 1 applied.
12. We draw two quads with Texture 1 applied. Texture 1 still contains
the image from the last frame.
13. We draw the trunk.
14. We make the back buffer current.
15. We draw 6 quads with Texture 2 applied.
*/
glUseProgram(m_ShaderProgram.uiId);
glBindBuffer(GL_ARRAY_BUFFER, m_uiVbo);
glDisable(GL_CULL_FACE);
glEnableVertexAttribArray(TEXCOORD_ARRAY);
glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
glVertexAttribPointer(TEXCOORD_ARRAY, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
Draw the fractal onto the current m_ui32Texture
*/
if(!RenderFractal())
return false;
fMatrix = PVRTMat4::Identity();
Bind the projection model view matrix (PMVMatrix) to
the associated uniform variable in the shader
*/
glUniformMatrix4fv(m_ShaderProgram.auiLoc[eMVPMatrix], 1, GL_FALSE, fMatrix.ptr());
glClearColor(0.6f, 0.8f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Set the viewport ot fill the screen.
*/
glViewport(0, 0, PVRShellGet(prefWidth), PVRShellGet(prefHeight));
PVRTMat4 fRotZ;
fMatrix = PVRTMat4::Scale(0.8f *(float)PVRShellGet(prefHeight) / (float)PVRShellGet(prefWidth), 0.8f, 0.8f);
fRotZ = PVRTMat4::RotationZ(1.047f);
glBlendFunc(GL_DST_COLOR, GL_ONE);
/* Bind the texture that we have rendered too.*/
glBindTexture(GL_TEXTURE_2D, m_auiTexture[m_i32CurrentFbo]);
for(int i = 0; i < 6; ++i)
{
// Set the transformationh matrix
glUniformMatrix4fv(m_ShaderProgram.auiLoc[eMVPMatrix], 1, GL_FALSE, fMatrix.ptr());
glDrawArrays(GL_TRIANGLE_STRIP, 5, 4);
fMatrix = fMatrix * fRotZ;
}
m_i32CurrentFbo = 1 - m_i32CurrentFbo;
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(VERTEX_ARRAY);
glDisableVertexAttribArray(TEXCOORD_ARRAY);
}
{
/* Use the program created with the fragment and vertex shaders. */
glUseProgram(m_ShaderProgram.uiId);
glBindBuffer(GL_ARRAY_BUFFER, m_uiVbo);
glDisable(GL_CULL_FACE);
glEnableVertexAttribArray(TEXCOORD_ARRAY);
glVertexAttribPointer(VERTEX_ARRAY, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), 0);
glVertexAttribPointer(TEXCOORD_ARRAY, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
Draw the fractal onto the current m_ui32Texture
*/
if(!RenderFractal())
return false;
fMatrix = PVRTMat4::Identity();
Bind the projection model view matrix (PMVMatrix) to
the associated uniform variable in the shader
*/
glUniformMatrix4fv(m_ShaderProgram.auiLoc[eMVPMatrix], 1, GL_FALSE, fMatrix.ptr());
glClearColor(0.6f, 0.8f, 1.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
Set the viewport ot fill the screen.
*/
glViewport(0, 0, PVRShellGet(prefWidth), PVRShellGet(prefHeight));
PVRTMat4 fRotZ;
fMatrix = PVRTMat4::Scale(0.8f *(float)PVRShellGet(prefHeight) / (float)PVRShellGet(prefWidth), 0.8f, 0.8f);
fRotZ = PVRTMat4::RotationZ(1.047f);
glBlendFunc(GL_DST_COLOR, GL_ONE);
/* Bind the texture that we have rendered too.*/
glBindTexture(GL_TEXTURE_2D, m_auiTexture[m_i32CurrentFbo]);
for(int i = 0; i < 6; ++i)
{
// Set the transformationh matrix
glUniformMatrix4fv(m_ShaderProgram.auiLoc[eMVPMatrix], 1, GL_FALSE, fMatrix.ptr());
glDrawArrays(GL_TRIANGLE_STRIP, 5, 4);
fMatrix = fMatrix * fRotZ;
}
glReadPixels(0, 0, 800, 600,GL_RGB,GL_UNSIGNED_BYTE,m_pScreenBuf);
{
for(int i=799; i>=0; i--)
{
fprintf(fp,"%d ",m_pScreenBuf[(j*800+i)*3+2]);
fprintf(fp,"%d ",m_pScreenBuf[(j*800+i)*3+1]);
fprintf(fp,"%d ",m_pScreenBuf[(j*800+i)*3+0]);
}
}
free (m_pScreenBuf);
// Swap the FBOs
m_i32CurrentFbo = 1 - m_i32CurrentFbo;
glBindBuffer(GL_ARRAY_BUFFER, 0);
glDisableVertexAttribArray(VERTEX_ARRAY);
glDisableVertexAttribArray(TEXCOORD_ARRAY);
}
* Function Name : RenderFractal
* Description : Draws the RenderToTexture
*******************************************************************************/
bool OGLES2RenderToTexture::RenderFractal()
{
/*
To do anything with a frame buffer object we need to bind it. In the case
below we are binding our frame buffer object to the frame buffer.
*/
glBindFramebuffer(GL_FRAMEBUFFER, m_auiFbo[m_i32CurrentFbo]);
If everything went ok then we can render to the texture.
*/
if(glCheckFramebufferStatus(GL_FRAMEBUFFER) == GL_FRAMEBUFFER_COMPLETE)
{
PVRTMat4 fMatrix, fTrans, fRot;
glViewport(0, 0, m_i32TexSize, m_i32TexSize);
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, m_auiTexture[1 - m_i32CurrentFbo]);
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE);
Initialise the translation array that we are going to use to translate both of the
the quads that we are going to render to.
*/
fTrans = PVRTMat4::Translation(0.0f, -0.4f, 0.0f);
Set up the rotation matrix that we are going to use to rotate the two quads
that have the previous texture created for the previous frame bound
to them.
*/
fRot = PVRTMat4::RotationZ(m_fAngle + m_fAngle2);
fMatrix = fTrans * fRot;
/*
Set the transformation matrix in the shader.
*/
glUniformMatrix4fv(m_ShaderProgram.auiLoc[eMVPMatrix], 1, GL_FALSE, fMatrix.ptr());
glDrawArrays(GL_TRIANGLE_STRIP, 5, 4);
Rotate the second quad the other way.
*/
fRot = PVRTMat4::RotationZ(m_fAngle - m_fAngle2);
fMatrix = fTrans * fRot;
glUniformMatrix4fv(m_ShaderProgram.auiLoc[eMVPMatrix], 1, GL_FALSE, fMatrix.ptr());
glDrawArrays(GL_TRIANGLE_STRIP, 5, 4);
Now draw the trunk.
*/
// Bind the trunk texture
glBindTexture(GL_TEXTURE_2D, m_uiTrunkTex);
glUniformMatrix4fv(m_ShaderProgram.auiLoc[eMVPMatrix], 1, GL_FALSE, fMatrix.ptr());
glDrawArrays(GL_TRIANGLE_STRIP, 0, 5);
Unbind the frame buffer object so rendering returns back to the backbuffer.
*/
glBindFramebuffer(GL_FRAMEBUFFER, m_i32OriginalFbo);
return true;
}
@Function NewDemo
@Return PVRShell* The demo supplied by the user
@Description This function must be implemented by the user of the shell.
The user should return its PVRShell object defining the
behaviour of the application.
******************************************************************************/
PVRShell* NewDemo()
{
return new OGLES2RenderToTexture();
}
End of file (OGLES2RenderToTexture.cpp)
******************************************************************************/
Maybe I know the reason now. I should attach a Renderbuffer, GL_DEPTH_ATTACHMENT and just like what the original code does to the original buffer… Correct me if I am wrong.
No, glReadPixels just transfers the pixels from the currently bound FB on the GPU to the CPU/RAM. In order not to show the result, you need to render to an FBO, not the FB, i.e. 0.
Based on the above method, I can successfully make off-screen rendering and image saving in the frame work of one training course. The code is running well on the PC version. Then I re-compile the code and re-run it on OMAP system, I still can get the off-screen image saved. But the image content is all-zero. In other word, the image is all black.
The codes I used for these two cases are the same. Can anyone give me a hint why the OMAP version failed?
I just figure out that the reason is I called the glReadPixels() function wrong.