Related
When using OpenGL's glTexSubImage2D and glTexSubImage3D functions, with a sub image that does not equal the actual dimensions of the texture, should the data pointer contain data packed to match the actual texture dimensions or the dimensions of the sub image?
For example, if you had a simple 3x3 texture, and you wanted to upload only the center pixel, that would be a sub image with x offset 1, y offset 1, width 1, and height 1, and you would call...
glTexSubImage2D(GL_TEXTURE_2D, 0, 1, 1, 1, 1, GL_RED, GL_UNSIGNED_BYTE, data)
Should data look like { 255 } or like { 0, 0, 0, 0, 255, 0, 0, 0, 0 } ?
The size of the texture doesn't matter.
The size of the subregion updated does. Specifically, glTexSubImage2D(target, level, xoffset, yoffset, width, height, format, type, data) expects data to point to a rectangular image of size (width, height) of appropriate type and format. The way the data is unpacked from the memory is governed by the GL_UNPACK_ALIGNMENT, GL_UNPACK_ROW_LENGTH, and friends. See the OpenGL specification ยง8.4 Pixel Rectangles.
In your particular case data has to point to a single value like { 255 }.
I have created 2d texture array like this
glTexImage3D(GL_TEXTURE_2D_ARRAY,
0, // No mipmaps
GL_RGBA8, // Internal format
width, height, 100, // width,height,layer count
0, // border?
GL_RGBA, // format
GL_UNSIGNED_BYTE, // type
0); // pointer to data
How do I increase its size from 100 to 200 for example? I guess I would have to create a new 2d array with size 200 and copy the images with glCopyTexSubImage3D over?
glBindTexture(GL_TEXTURE_2D_ARRAY, texture_id);
glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY,
0,
0, 0, 0,
0, 0,
width, height
);
glDeleteTextures(1, &texture_id);
GLuint new_tex_id;
glGenTextures(1, &new_tex_id);
glBindTexture(GL_TEXTURE_2D_ARRAY, new_tex_id);
glTexImage3D(GL_TEXTURE_2D_ARRAY,
0,
GL_RGBA8,
width, height, 200,
0,
GL_RGBA,
GL_UNSIGNED_BYTE,
0);
//How do I get the data in `GL_READ_BUFFER` into my newly bound texture?
texture_id = new_tex_id;
But how do I actually get the data out of the GL_READ_BUFFER?
glCopyTexSubImage copies data from the framebuffer, not from a texture. That's why it doesn't take two texture objects to copy with.
Copying from a texture into another texture requires glCopyImageSubData. This is an OpenGL 4.3 function, from ARB_copy_image. A similar function can also be found in NV_copy_image, which may be more widely supported.
BTW, you should generally avoid doing this operation at all. If you needed a 200 element array texture, you should have allocated that the first time.
The glCopyImageSubData() function that #NicolBolas pointed out is the easiest solution if you're ok with requiring OpenGL 4.3 or later.
You can use glCopyTexSubImage3D() for this purpose. But since the source for this function is the current read framebuffer, you need to bind your original texture as a framebuffer attachment. The code could roughly look like this:
GLuint fbo = 0;
glGenFramebuffers(1, &fbo);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo);
glBindTexture(GL_TEXTURE_2D_ARRAY, new_tex_id);
for (int layer = 0; layer < 100; ++layer) {
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0, tex_id, 0, layer);
glCopyTexSubImage3D(GL_TEXTURE_2D_ARRAY,
0, 0, 0, layer, 0, 0, width, height);
}
You can also use glBlitFramebuffer() instead:
GLuint fbos[2] = {0, 0};
glGenFramebuffers(2, fbos);
glBindFramebuffer(GL_READ_FRAMEBUFFER, fbos[0]);
glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbos[1]);
for (int layer = 0; layer < 100; ++layer) {
glFramebufferTextureLayer(GL_READ_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0, tex_id, 0, layer);
glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER,
GL_COLOR_ATTACHMENT0, new_tex_id, 0, layer);
glBlitFramebuffer(
0, 0, width, height, 0, 0, width, height,
GL_COLOR_BUFFER_BIT, GL_NEAREST);
}
The two options should be more or less equivalent. I would probably go with glBlitFramebuffer() since it's a newer function (introduced in 3.0), and it might be much more commonly used. So it might be more optimized. But if this is performance critical in your application, you should try both, and compare.
When I resize my window, I need to resize my textures that are attached to my framebuffer. I tried calling glTexStorage2D again, with different size parameters. However that does not work.
How can I resize the textures attached to my framebuffer? (Including the depth attachment)
EDIT
Code I tried:
glBindTexture(m_target, m_name);
glTexStorage2D(m_target, 1, m_format, m_width, m_height);
glBindTexture(m_target, 0);
where m_name, m_target and m_format are saved from the original texture and m_width and m_height are the new dimensions.
EDIT2
Please tell me why this has been downvoted so I can fix the question.
EDIT3
Here, someone else had the same problem.
I found that the texture was being rendered correctly to the FBO, but that it was being displayed at the wrong size. It was as if the first time the texture was sent to the default framebuffer the texture size was set permanently, and then when a resized texture was sent it was being treated as if it was the original size. For example, if the first texture was 100x100 and the second texture was 50x50 then the entire texture would be displayed in the bottom left quarter of the screen. Conversely, if the original texture was 50x50 and the new texture 100x100 then the result would be the bottom left quarter of the texture being displayed over the whole screen.
However, he uses a shader to fix this. That's not how I want to do this. There has to be another solution, right?
If you were using glTexImage2D (...) to allocate storage for your texture, it would be possible to re-allocate the storage for any image in the texture at any time without first deleting the texture.
However, you are not using glTexImage2D (...), you are using glTexStorage2D (...). This creates an immutable texture object, whose storage requirements are set once and can never be changed again. Any calls to glTexImage2D (...) or glTexStorage2D (...) after you allocate storage initially will generate GL_INVALID_OPERATION and do nothing else.
If you want to create a texture whose size can be changed at any time, do not use glTexStorage2D (...). Instead, pass some dummy (but compatible) values for the data type and format to glTexImage2D (...).
For instance, if you want to allocate a texture with 1 LOD that is m_widthxm_height:
glTexImage2D (m_target, 0, m_format, m_width, m_height, 0, GL_RED, GL_FLOAT, NULL);
If m_width or m_height change later on, you can re-allocate storage the same way:
glTexImage2D (m_target, 0, m_format, m_width, m_height, 0, GL_RED, GL_FLOAT, NULL);
This is a very different situation than if you use glTexStorage2D (...). That will prevent you from re-allocating storage, and will simply create a GL_INVALID_OPERATION error.
You should review the manual page for glTexStorage2D (...), it states the following:
Description
glTexStorage2D specifies the storage requirements for all levels of a two-dimensional texture or one-dimensional texture array simultaneously. Once a texture is specified with this command, the format and dimensions of all levels become immutable unless it is a proxy texture. The contents of the image may still be modified, however, its storage requirements may not change. Such a texture is referred to as an immutable-format texture.
The behavior of glTexStorage2D depends on the target parameter.
When target is GL_TEXTURE_2D, GL_PROXY_TEXTURE_2D, GL_TEXTURE_RECTANGLE, GL_PROXY_TEXTURE_RECTANGLE or GL_PROXY_TEXTURE_CUBE_MAP, calling glTexStorage2D is equivalent, assuming no errors are generated, to executing the following pseudo-code:
for (i = 0; i < levels; i++) {
glTexImage2D(target, i, internalformat, width, height, 0, format, type, NULL);
width = max(1, (width / 2));
height = max(1, (height / 2));
}
When target is GL_TEXTURE_CUBE_MAP, glTexStorage2D is equivalent to:
for (i = 0; i < levels; i++) {
for (face in (+X, -X, +Y, -Y, +Z, -Z)) {
glTexImage2D(face, i, internalformat, width, height, 0, format, type, NULL);
}
width = max(1, (width / 2));
height = max(1, (height / 2));
}
When target is GL_TEXTURE_1D or GL_TEXTURE_1D_ARRAY, glTexStorage2D is equivalent to:
for (i = 0; i < levels; i++) {
glTexImage2D(target, i, internalformat, width, height, 0, format, type, NULL);
width = max(1, (width / 2));
}
Since no texture data is actually provided, the values used in the pseudo-code for format and type are irrelevant and may be considered to be any values that are legal for the chosen internalformat enumerant. [...] Upon success, the value of GL_TEXTURE_IMMUTABLE_FORMAT becomes GL_TRUE. The value of GL_TEXTURE_IMMUTABLE_FORMAT may be discovered by calling glGetTexParameter with pname set to GL_TEXTURE_IMMUTABLE_FORMAT. No further changes to the dimensions or format of the texture object may be made. Using any command that might alter the dimensions or format of the texture object (such as glTexImage2D or another call to glTexStorage2D) will result in the generation of a GL_INVALID_OPERATION error, even if it would not, in fact, alter the dimensions or format of the object.
I have an application that decodes a video file using FFMPEG (in a separate thread) and renders this texture using PBOs in another. All the PBO do-hickey happens in the following function:
void DynamicTexture::update()
{
if(!_isDirty)
{
return;
}
/// \todo Check to make sure that PBOs are supported
if(_usePbo)
{
// In multi PBO mode, we keep swapping between the PBOs
// We use one PBO to actually set the texture data that we will upload
// and the other we use to update/modify. Once modification is complete,
// we simply swap buffers
// Unmap the PBO that was updated last so that it can be released for rendering
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _pboIds[_currentPboIndex]);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
Util::GLErrorAssert();
// bind the texture
glBindTexture(GL_TEXTURE_2D, _textureId);
Util::GLErrorAssert();
// copy pixels from PBO to texture object
// Use offset instead of pointer.
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, _width, _height,
(_channelCount==4)?GL_RGBA:GL_RGB,
GL_UNSIGNED_BYTE, 0);
Util::GLErrorAssert();
// Now swap the pbo index
_currentPboIndex = (++_currentPboIndex) % _numPbos;
// bind PBO to update pixel values
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _pboIds[_currentPboIndex]);
Util::GLErrorAssert();
// map the next buffer object into client's memory
// Note that glMapBuffer() causes sync issue.
// If GPU is working with this buffer, glMapBuffer() will wait(stall)
// for GPU to finish its job
GLubyte* ptr = (GLubyte*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER, GL_WRITE_ONLY);
Util::GLErrorAssert();
if(ptr)
{
// update data directly on the mapped buffer
_currentBuffer = ptr;
Util::GLErrorAssert();
}
else
{
printf("Unable to map PBO!");
assert(false);
}
// It is good idea to release PBOs with ID 0 after use.
// Once bound with 0, all pixel operations behave normal ways.
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
Util::GLErrorAssert();
// If a callback was registered, call it
if(_renderCallback)
{
(*_renderCallback)(this);
}
}
else
{
glBindTexture(GL_TEXTURE_2D, _textureId);
glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
_width, _height, (_channelCount==4)?GL_RGBA:GL_RGB,
GL_UNSIGNED_BYTE,
&(_buffer[0])
);
Util::GLErrorAssert();
}
// Reset the dirty flag after updating
_isDirty = false;
}
In the decoding thread, I simply update _currentBuffer and set the _isDirty flag to true. This function is called in the render thread.
When I use a single PBO, i.e. when _numPbos=1 in the above code, then the rendering works fine without any stutter. However, when I use more than one PBO, there is a visible stutter in the video. You can find a sample of me rendering 5 videos with _numPbos=2 here. The more number of PBOs I use, the worse the stutter becomes.
Theoretically, the buffer that I am updating and the buffer than I am using for render are different, so there should be no glitch of this sort. I want to use double/triple buffering so as to increase rendering performance.
I am looking for some pointers/hints as to what could be going wrong.
I dont know, if it is your problem, but after you call this:
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, _pboIds[_currentPboIndex]);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
Util::GLErrorAssert();
You are calling
glBindTexture
But you are still operating with buffer at index _currentPboIndex.
In my code, I have two indices - index and nextIndex
In init I set
index = 0;
nextIndex = 1;
Than my update pipeline is like this:
index = (index + 1) % 2;
nextIndex = (nextIndex + 1) % 2;
uint32 textureSize = sizeof(RGB) * width * height;
GL_CHECK( glBindBuffer(GL_PIXEL_UNPACK_BUFFER, pbo[nextIndex]) );
GL_CHECK( glBufferData(GL_PIXEL_UNPACK_BUFFER, textureSize, 0, GL_STREAM_DRAW_ARB) );
GL_CHECK( gpuDataPtr = glMapBufferRange(GL_PIXEL_UNPACK_BUFFER, 0, textureSize, GL_MAP_WRITE_BIT | GL_MAP_INVALIDATE_BUFFER_BIT) );
//update data gpuDataPtr
GL_CHECK( glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER_ARB) );
//bind texture
GL_CHECK( glBindBufferARB(GL_PIXEL_UNPACK_BUFFER, pbo[index]) );
GL_CHECK( glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0,
width, height, glFormat, GL_UNSIGNED_BYTE, 0) );
GL_CHECK( glBindBufferARB(type, 0) );
I have a texture 2d array of TEXTURE_2D.I need to clear the content of the textures before each draw pass.I am trying to do it with PBO.But I am getting INVALID_OPERATION error.
Here is how I create the array of images:
glGenTextures(1,&_texID);
glBindTexture (GL_TEXTURE_2D_ARRAY,_texID);
glTexStorage3D(GL_TEXTURE_2D_ARRAY,1,GL_RGBA32F,width,height,numTextures);
glBindTexture (GL_TEXTURE_2D_ARRAY,0);
glBindImageTexture(0, _texID, 0, GL_FALSE, 0, GL_READ_WRITE, GL_RGBA32F);
Here is how I clear it:
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, clearBuffer);
glBindTexture(GL_TEXTURE_2D_ARRAY, itexArray->GetTexID());
for(int i =0; i <numTextures ;++i) {
glTexSubImage3D(GL_TEXTURE_2D_ARRAY,1,0, 0, 0, _viewportWidth, _viewportHeight, i , GL_RGBA, GL_FLOAT, NULL);
}
glBindTexture(GL_TEXTURE_2D_ARRAY, 0);
I have numTextures = 8,so 8 texture layers in the array.When I start clearing them in the loop,first 4 are cleared without errors but from the forth on I ma getting INVALID_OPERATION.
UPDATE:
I solved PBO INVALID_OPERATION issue by enlarging PBO size from 2048x2048 to 4096x4096 but the result is that the textures of texture array are still not cleared properly.For example,at startup of the program leftovers can be still seen which disappear only after the rendered objects start moving around the viewport.
Here is the setup for clearing PBO:
GLint frameSize =MAX_FRAMEBUFFER_WIDTH * MAX_FRAMEBUFFER_HEIGHT * sizeof(float);
glGenBuffers(1, &clearBuffer);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER,clearBuffer);
glBufferData(GL_PIXEL_UNPACK_BUFFER,frameSize,NULL,GL_STATIC_DRAW);
//fill the buffer with color:
vec4* data = (vec4*)glMapBuffer(GL_PIXEL_UNPACK_BUFFER,GL_WRITE_ONLY);
memset(data,0x00,frameSize);
glUnmapBuffer(GL_PIXEL_UNPACK_BUFFER);
glBindBuffer(GL_PIXEL_UNPACK_BUFFER, 0);
Where MAX_FRAMEBUFFER_WIDTH and MAX_FRAMEBUFFER_HEIGHT are both 4096
Level is level of detail, i.e. mipmap level, in most cases it is 0, depth would be array index in your case.
Your glTexSubImage3D call is broken.
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 1,
0, 0, 0, //offset (first image)
_viewportWidth, _viewportHeight, i, //size (getting larger)
GL_RGBA, GL_FLOAT, NULL);
First of all, of course Vasaka is right in that you shouldn't write to mipmap level 1 (which doesn't even exist), but 0. But even then this call will try to put a 3D image of size _viewportWidth * _viewportHeight * i at the first array index, which is surely not what you want. Instead you want to clear a 2D image of size _viewportWidth * _viewportHeight at position i. So your call should actually look this way:
glTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0,
0, 0, i, //offset (ith image)
_viewportWidth, _viewportHeight, 1, //size (proper 2D image)
GL_RGBA, GL_FLOAT, NULL);
And your problem with needing a larger PBO than neccessary is easily solved by including a 4 in the computation of frameSize. Your PBO is treated (and explained by you) as containing 4-vectors of floats, yet you compute the size in bytes of it as if it just contained single floats. That's why it magically works for a doubled dimension, since this would properly increase the size of the PBO 4 times, as neccessary, but it only hides the actual problem of forgetting the component count in the size computation.
EDIT: By the way, instead of maintaining a huge PBO which contains nothing but 0s, you could also try to attach the respective image layer to an FBO and do a simple glClear in each loop iteration. Don't know which one is more efficient (but I'd guess glClear being more optimized than a whole image copy), but it at least makes this large PBO obsolete.