Is it possible to alter a texture in real time on a pixel basis?

If you are a new Irrlicht Engine user, and have a newbie-question, this is the forum for you. You may also post general programming questions here.
Post Reply
ThomasCloneTHX1139
Posts: 5
Joined: Sun Aug 13, 2023 1:41 pm

Is it possible to alter a texture in real time on a pixel basis?

Post by ThomasCloneTHX1139 »

I want to obtain the effect of a virtual screen on which I can draw pixel by pixel while the program is running. At first, I tried following this naive approach:

Code: Select all

 // The virtual screen is created, one pixel at a time
 for (x=0; x<VIRTUAL_SCREEN_WIDTH; x++)
 {
  for (y=0; y<VIRTUAL_SCREEN_HEIGHT; y++)
  {
   virtualscreen[x][y] = smgr->addAnimatedMeshSceneNode( mesh[1] );
   virtualscreen[x][y]->setPosition(core::vector3df(x,-y,0));
   virtualscreen[x][y]->setMaterialFlag(EMF_LIGHTING, false);
   virtualscreen[x][y]->setMaterialFlag(video::EMF_NORMALIZE_NORMALS, true);
   virtualscreen[x][y]->setMaterialFlag(video::EMF_GOURAUD_SHADING, false);
   virtualscreen[x][y]->setMaterialTexture(0,black);
  }
 }

 // We can draw on the virtual screen
 virtualscreen[100][100]->setMaterialTexture(0,white);
}
where every "pixel" of the virtual screen is a square object, 1 unit long. However, it requires a monstruous number of objects: a 1280x720 virtual screen needs 921600 objects, each of which comprises 2 polygons, so 1843200 polygons.

This would change if I didn't need 2 polygons per pixel, but instead I could use a single object, give it a texture with the appropriate resolution, and manipulate the texture directly by writing a function that would look like this:

Code: Select all

void PutTexel(texture, x, y, red, green, blue);
{
 // code here
}
Does Irrlicht allow it? Has anyone ever done it?
CuteAlien
Admin
Posts: 10016
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: Is it possible to alter a texture in real time on a pixel basis?

Post by CuteAlien »

You can acccess texture memory with ITexture::lock(). Writing to it then depends a bit on the format. Maybe copy the code from CImage::setPixel (in CImage.cpp) which basically does just that. At least if there is no memory padding in the texture. To avoid padding use textures with a width which is a multiple of 4. Otherwise you may have to add code which cares about pitch (textures may internally reserve more data so the byte size ends up a multiple of 4, so the real size of each line can be pitch() and not just width*bytes_per_pixel - but this depends on video driver and maybe even on the graphic card).

Once you are done writing the data call ITexture::unlock() to upload the changed texture to the GPU.

Note: texture locking/unlocking is slow - and also a function call per pixel drawing can be slow. One optimization on OpenGL is to set IVideoDriver::setTextureCreationFlag(ETCF_ALLOW_MEMORY_COPY, true) before creating that texture (and false for all others unless you don't care about memory copies). In Irrlicht 1.8 that was default (and 1.8 didn't have the flag yet), in Irrlicht svn trunk you have to call this explicitely. That way texture::lock() doesn't have to download the texture from GPU, but only needs to upload the changed one on unlock(), so that's quite a bit faster.

Also for faster pixel writing people sometimes use defines or inline functions instead of full function calls to avoid the function overhead.

Well, I guess you'll see yourself if it's fast enough :-)

You can also use the Irrlicht functions directly. Bascially - create an IImage and ITexture with same sizes and format. Work with the setPixel functions of IImage. Then as above use lock() on ITexture and lock() or getData on the IImage. Then copy the IImage data over to the texture. While that is usually slower, you don't have to write the pxiel functions yourself. But... slow - those are even virtual function calls. Good enough for simple stuff, but if you do more in your application this can start being too expensive.

There may also be other tricks to do this which may be faster. Like using 2 rendertarget textures and writing one to the other with a shader. And then pass the pixels you want to change to that shader. Bit more complicated... but if speed is of concern then that might be the way. And possible more options with modern OpenGL, but I've not enough experience with that yet myself.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
ThomasCloneTHX1139
Posts: 5
Joined: Sun Aug 13, 2023 1:41 pm

Re: Is it possible to alter a texture in real time on a pixel basis?

Post by ThomasCloneTHX1139 »

CuteAlien wrote: Sun May 10, 2026 3:40 pm You can also use the Irrlicht functions directly. Bascially - create an IImage and ITexture with same sizes and format. Work with the setPixel functions of IImage. Then as above use lock() on ITexture and lock() or getData on the IImage. Then copy the IImage data over to the texture. While that is usually slower, you don't have to write the pxiel functions yourself. But... slow - those are even virtual function calls. Good enough for simple stuff, but if you do more in your application this can start being too expensive.
All right, so I had to google a lot of stuff, because this is more low-level than I'm used to, but in the end, I've managed to write the PutTexel function the way I wanted to.

Code: Select all

void dlPutTexel(ITexture* texture, uint16_t x, uint16_t y, uint8_t red, uint8_t green, uint8_t blue)
{
 // This function puts a single pixel on a texture
 uint16_t image_x, image_y; // 65535*65535 pixels ought to be enough for everyone
 IImage* bak;

 // Note to self: a void pointer can hold an address of any type and can be typecasted to any type
 void* imagedata;
 void* texturedata;

 u32 datasize;

 image_x=texture->getSize().Width;
 image_y=texture->getSize().Height;
 bak=driver->createImage(video::ECF_A8R8G8B8, core::dimension2d<u32>(image_x,image_y));

 // texture data is copied to the backup (necessary, otherwise anu graphical operation will erase the previous ones)
 // lock the IImage to get access to the raw pixel array
 imagedata=bak->lock();

 // lock the texture to get access to GPU memory
 texturedata=texture->lock();
 if (texturedata&&imagedata)
 {
  // data size in bytes is the IImage size in pixels, multiplied by 4 because each pixel holds 4 bytes of data
  // (alpha, red, green, blue)
  datasize=bak->getDimension().Width*bak->getDimension().Height*4;

  // perform the copy (this is when the texture modification appears)
  memcpy(imagedata,texturedata,datasize);

  // unlock both because the pointers aren't needed anymore
  texture->unlock();
  bak->unlock();
 }

 // all pixel operations must be done here
 bak->setPixel(x,y,SColor(255,red,green,blue),false);

 // lock the IImage to get access to the raw pixel array
 imagedata=bak->lock();

 // lock the texture to get access to GPU memory
 texturedata=texture->lock();
 if (texturedata&&imagedata)
 {
  // data size in bytes is the IImage size in pixels, multiplied by 4 because each pixel holds 4 bytes of data
  // (alpha, red, green, blue)
  datasize=bak->getDimension().Width*bak->getDimension().Height*4;

  // perform the copy (this is when the texture modification appears)
  memcpy(texturedata,imagedata,datasize);

  // unlock both because the pointers aren't needed anymore
  texture->unlock();
  bak->unlock();
 }
}
I was about to ask about void pointers, because the page I copypasted that part of the code from didn't explain them and I got them confused with null pointers, but then I googled "void pointer" and learned what they are.
All I have to do now is to time the function to see whether it's fast enough for what I need.
CuteAlien
Admin
Posts: 10016
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: Is it possible to alter a texture in real time on a pixel basis?

Post by CuteAlien »

Oh no - if you do _that_ each pixel your cpu will shoot itself to get out of the pain! Don't create a new image every call, memory allocations are extremely slow. And you don't have to.

The idea was - create it once! Keep the image object around - never create another. If you want to initialize it with some texture originally you can use IVideoDriver::createImage - one of those takes a texture as parameter. Then you only need setPixel on that image (as often as you want). And maybe remember if you changed it. Then after changing it you upload it once per frame (any more is overkill). That's where you do the texture->lock, then copy the image data into that pointer, then unlock the texture again. Then there's also no need to do it the other way round and copy the texture each time to the image - the image already has the up-to-date version.
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
CuteAlien
Admin
Posts: 10016
Joined: Mon Mar 06, 2006 2:25 pm
Location: Tübingen, Germany
Contact:

Re: Is it possible to alter a texture in real time on a pixel basis?

Post by CuteAlien »

I made a quick example for this: https://github.com/mzeilfelder/irr-play ... rawing.cpp

(also noticed if you do it that way you don't need the ETCF_ALLOW_MEMORY_COPY flag as you already have your own copy)
IRC: #irrlicht on irc.libera.chat
Code snippet repository: https://github.com/mzeilfelder/irr-playground-micha
Free racer made with Irrlicht: http://www.irrgheist.com/hcraftsource.htm
Post Reply