How to create a texture at run time? Topic is solved

Discussion area about developing with Ogre-Next (2.1, 2.2 and beyond)


jwwalker
Goblin
Posts: 248
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 18

How to create a texture at run time?

Post by jwwalker »

The wiki article on dynamic textures uses classes that are now in the Deprecated folder, like TextureManager and HardwarePixelBuffer. Is there a more modern way of accomplishing the same thing?
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5436
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1343

Re: How to create a texture at run time?

Post by dark_sylinc »

You must always use a StagingTexture.

There is no way to map a Texture directly. This follows how GPUs actually work (except some Intel chips which can swizzle memory on the fly through the bus; but still the modern APIs don't allow it anyway).

From CPU perspective we fill a texture row by row; however internally when we upload it from StagingTexture to Texture GPUs reorder the data for better cache usage or fast bilinear filtering (i.e. morton order, interleaving and swizzling channels at the bit level).

Older APIs let you map directly but they hid this fact and returned a malloc'ed pointer then later would do the staging stuff behind the scenes. Very inefficient in some cases.

Just create 3 StagingTexture (one per VaoManager::getDynamicBufferMultiplier) keep them around (don't destroy them) and alternate between them to upload to the GPU, e.g. each frame:

Code: Select all

// At init time
for( i < vaoManager->getDynamicBufferMultiplier() )
{
  // Use 100 so that we have no memory waste because we plan on keeping this StagingTexture around
  // exclusively for us
  stagingTexture[i] = textureManager->getStagingTexture( width, height, depth, numSlices, format, 100u );
}

// Every frame
stagingTexture[frameIdx]; // use it
frameIdx = (frameIdx + 1u) % vaoManager->getDynamicBufferMultiplier(); // We're done

// At shutdown
for( i < vaoManager->getDynamicBufferMultiplier() )
{
  textureManager->removeStagingTexture( stagingTexture[i] );
}
As for how to use a StagingTexture, that's exactly the same as the first time around or every frame; it won't matter. See IrradianceVolume::updateIrradianceVolumeTexture, Font::loadTextureFromFont, CompositorManager2::getNullShadowTexture, or Image2::uploadTo (this one covers every edge case for every type of format, can be a bit hard to read)

Cheers
Matias
jwwalker
Goblin
Posts: 248
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 18

Re: How to create a texture at run time?

Post by jwwalker »

Thanks. I'm not actually planning on changing a texture dynamically, I just wanted to be able to create a texture without using Ogre's resource loading system. So I'm guessing I only need one StagingTexture, and I don't need to worry about getDynamicBufferMultiplier(). Creating a texture seems to succeed, but when I actually try to apply the texture to an item, there is an exception.

My texture creation code:

Code: Select all

Ogre::TextureGpu* texture = textureManager->createTexture(
	Ogre::String(inName),
	Ogre::GpuPageOutStrategy::SaveToSystemRam,
	Ogre::TextureFlags::ManualTexture,
	Ogre::TextureTypes::Type2D );
texture->setPixelFormat( Ogre::PFG_RGBA8_UINT );
texture->setTextureType( Ogre::TextureTypes::Type2D );
texture->setResolution( (Ogre::uint32) width,
	(Ogre::uint32) height );

if (texture->getResidencyStatus() == Ogre::GpuResidency::OnStorage)
{
	texture->_transitionTo( Ogre::GpuResidency::Resident, nullptr );
	texture->_setNextResidencyStatus( Ogre::GpuResidency::Resident );
}

Ogre::StagingTexture* stagingTexture = textureManager->getStagingTexture(
	(Ogre::uint32) width, (Ogre::uint32) height, 1u, 1u,
	texture->getPixelFormat() );
stagingTexture->startMapRegion();
Ogre::TextureBox texBox = stagingTexture->mapRegion(
	(Ogre::uint32) width, (Ogre::uint32) height,
	1u, 1u, texture->getPixelFormat() );
texBox.copyFrom( imBuffer.data(), (Ogre::uint32) width,
	(Ogre::uint32) height, (Ogre::uint32) bytesPerRow );
stagingTexture->stopMapRegion();
stagingTexture->upload( texBox, texture, 0, 0, 0, true );
textureManager->removeStagingTexture( stagingTexture );
And then I tried to use it like this (I also tried using the version of setTexture that takes a name):

Code: Select all

Ogre::HlmsPbsDatablock* litBlock =
	reinterpret_cast<Ogre::HlmsPbsDatablock*>(
	hlmsLit->createDatablock( Ogre::IdString("foo"), "foo",
		Ogre::HlmsMacroblock(), Ogre::HlmsBlendblock(),
		Ogre::HlmsParamVec() ));
litBlock->setTexture( 0, texture );
litItem->setDatablock( litBlock );
And the exception is:
failed assertion `Fragment Function(main_metal): incorrect type of texture (MTLTextureType2D) bound at texture binding at index 2 (expect MTLTextureType2DArray) for textureMaps0[0].'
Why is something expecting a 2D array?
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5436
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1343

Re: How to create a texture at run time?

Post by dark_sylinc »

jwwalker wrote: Sat Oct 23, 2021 11:49 pm Thanks. I'm not actually planning on changing a texture dynamically, I just wanted to be able to create a texture without using Ogre's resource loading system.
You could've started there!

UpdatingDecalsAndAreaLightTexGameState::setupLightTexture in Samples/2.0/ApiUsage/UpdatingDecalsAndAreaLightTex shows two ways of creating a texture (sync and async)
jwwalker wrote: Sat Oct 23, 2021 11:49 pm And the exception is:
failed assertion `Fragment Function(main_metal): incorrect type of texture (MTLTextureType2D) bound at texture binding at index 2 (expect MTLTextureType2DArray) for textureMaps0[0].'
Why is something expecting a 2D array?
Your code is fine, but HlmsPbs expects 2D Array textures.

Normally, you'd use AutomaticBatching flag, which creates a 2D Array and pretends it's 2D (by only talking to a single slice, but internally it's inside a pool to minimize texture swapping and batch more draws together in the same API call).

If you use AutomaticBatching, UpdatingDecalsAndAreaLightTexGameState shows how to upload data to it. Note that AutomaticBatching by default tries to open it from disk (or a listener, if registered to TextureGpuManager). UpdatingDecalsAndAreaLightTexGameState shows how to side step that.

If you don't want to use AutomaticBatching, then your texture must be of type 2DArray, not 2D. You can create a 2DArray of slice = 1, this is allowed; and your current code should work.

Cheers
Matias
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5436
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1343

Re: How to create a texture at run time?

Post by dark_sylinc »

Btw I believe:

Code: Select all

if (texture->getResidencyStatus() == Ogre::GpuResidency::OnStorage)
{
	texture->_transitionTo( Ogre::GpuResidency::Resident, nullptr );
	texture->_setNextResidencyStatus( Ogre::GpuResidency::Resident );
}
Is probably better to replace it with:

Code: Select all

if (texture->getResidencyStatus() == Ogre::GpuResidency::OnStorage)
{
	texture->scheduleTransitionTo( Ogre::GpuResidency::Resident, nullptr );
}
To make it future proof with 2.3
jwwalker
Goblin
Posts: 248
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 18

Re: How to create a texture at run time?

Post by jwwalker »

dark_sylinc wrote: Sun Oct 24, 2021 12:04 am
jwwalker wrote: Sat Oct 23, 2021 11:49 pm Thanks. I'm not actually planning on changing a texture dynamically, I just wanted to be able to create a texture without using Ogre's resource loading system.
You could've started there!
I had Googled "ogre create texture at run time" and the first hit was the wiki page on creating dynamic textures, so I guess I thought it was the same thing.
dark_sylinc wrote: Sun Oct 24, 2021 12:04 am
jwwalker wrote: Sat Oct 23, 2021 11:49 pm Why is something expecting a 2D array?
Your code is fine, but HlmsPbs expects 2D Array textures.
OK. I had been looking at texture creation code in OgreFont.cpp, but presumably that texture doesn't get passed to HlmsPbs.
dark_sylinc wrote: Sun Oct 24, 2021 12:04 am If you don't want to use AutomaticBatching, then your texture must be of type 2DArray, not 2D. You can create a 2DArray of slice = 1, this is allowed; and your current code should work.
I tried just changing my code to use a 2DArray, and then I get the error:
failed assertion `Fragment Function(main_metal): The pixel format (MTLPixelFormatRGBA8Uint) of the texture (name:bill) bound at index 2 is incompatible with the data type (MTLDataTypeFloat) of the texture parameter (textureMaps0 [[texture(0)]]). MTLPixelFormatRGBA8Uint is compatible with the data type(s) (
uint,
ushort
).'
So I must use a floating-point pixel format?
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5436
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1343

Re: How to create a texture at run time?

Post by dark_sylinc »

It sounds like you requested PFG_RGBA8_UINT instead of PFG_RGBA8_UNORM (or PFG_RGBA8_UNORM_SRGB)

The former is raw unsigned integer. The latter is interpretting the range [0; 255] to the floating point range [0.0; 1.0] which is most common format
OK. I had been looking at texture creation code in OgreFont.cpp, but presumably that texture doesn't get passed to HlmsPbs.
That's right. The font stuff is passed to Unlit which does support regular 2D textures (due to being far more common, and also easier to support due to Unlit's simplicity)
jwwalker
Goblin
Posts: 248
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 18

Re: How to create a texture at run time?

Post by jwwalker »

Ah, I wondered about that UNORM business, thanks. I didn't see any explanation of it in the API docs or the header. When I change the pixel format to PFG_RGBA8_UNORM, then my code runs and my texture is recognizable but sort of grayed out. I don't quite understand how HlmsPbsDatablock::setTexture and HlmsPbsDatablock::setDiffuse interact. If I use PFG_RGBA8_UNORM_SRGB as the pixel format and also say

Code: Select all

litBlock->setDiffuse( Ogre::Vector3( M_PI, M_PI, M_PI ) );
then my textured object looks pretty much like I would have expected, but I'd like to understand why.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5436
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1343

Re: How to create a texture at run time?

Post by dark_sylinc »

It's by design. You'll often find these:

Code: Select all

light->setPowerScale( Ogre::Math::PI );  // Since we don't do HDR, counter the PBS' division by PI
It has to do with energy conservation which works fine in HDR, but in LDR it's too dim so just increasing the light to PI does the trick.
jwwalker
Goblin
Posts: 248
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 18

Re: How to create a texture at run time?

Post by jwwalker »

The other thing confusing me is that I would have thought that providing a texture of type PBSM_DIFFUSE would be instead of setting a diffuse color, rather than somehow combining with the diffuse color.
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5436
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1343

Re: How to create a texture at run time?

Post by dark_sylinc »

I'm not surr I understand? Could you rephrase or elaborate?
jwwalker
Goblin
Posts: 248
Joined: Thu Aug 12, 2021 10:06 pm
Location: San Diego, CA, USA
x 18

Re: How to create a texture at run time?

Post by jwwalker »

To rephrase, if I say something like

Code: Select all

litBlock->setTexture( Ogre::PBSM_DIFFUSE, "bill", &samplerblock );
I would expect that to totally determine how each bit of items using the block responds to diffuse light, so why does setDiffuse affect the brightness of the texture?
User avatar
dark_sylinc
OGRE Team Member
OGRE Team Member
Posts: 5436
Joined: Sat Jul 21, 2007 4:55 pm
Location: Buenos Aires, Argentina
x 1343

Re: How to create a texture at run time?

Post by dark_sylinc »

Mmm this is industry standard material behavior: the diffuse colour is multiplied against the one in the texture. So if it is above one it will obviously increase the brightness, and below one reduce it

Diffuse color has multiple uses, for example colour-coding objects for each player in an RTS game, or in FPS/TPS games where each team has a specific colour.

If you don't want to deal with it then leave it at 1.0
As for the PI value, embed it in the sun power if your pipeline is not HDR