How do you generate Mips at runtime?

Hi There,

My app allows users to add their own images - these are loaded from pngs / jpgs etc and mapped onto models in game.

The problem is they don’t have mips, nor would I expect users to create mips. I’d like to create them in app on load.

Any suggestions on approach? I don’t really want to build my own mipchain given that the engine must be able to do something for render targets (or will probably do something at some point…).

Spent some time looking but haven’t found anything outside the compression code which looked editor specific.

Any thoughts welcome!

Going to add this here as a starting point. Not nearly as good as the editor code that I believe we’re not allowed to use under EULA (it’s in the development folder, not runtime).

But posting this as a starting point for others. This is a blurry block averager. Doesn’t work wonderfully for my purposes but better than nothing.

auto *pNewTexture = UTexture2D::CreateTransient(ImageWrapper->GetWidth(), ImageWrapper->GetHeight(), PF_B8G8R8A8);
		if (pNewTexture != nullptr)
		{
			void* TextureData = pNewTexture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_WRITE);
			FMemory::Memcpy(TextureData, UncompressedBGRA->GetData(), UncompressedBGRA->Num());
			pNewTexture->PlatformData->Mips[0].BulkData.Unlock();

			if (sbGenerateMips)
			{
				//TMS: This doesn't do anything. Need a way to either 1) trigger mip loading or 2) generate them ourselves
				pNewTexture->MipGenSettings = TMGS_Sharpen4;

				//TMS: This does something but only when we add the mips below ourselves.
				pNewTexture->RequestedMips = 4;

				int mipsToAdd = pNewTexture->RequestedMips - 1;

				//Declaring buffers here to reduce reallocs
				//We double buffer mips, using the prior buffer to build the next buffer
				TArray<uint8> _mipRGBAs;
				TArray<uint8> _mipRGBBs;				

				//Let's try making one ourself
				auto *priorData = UncompressedBGRA->GetData();
				int priorwidth = ImageWrapper->GetWidth();
				int priorheight = ImageWrapper->GetHeight();

				while (mipsToAdd > 0)
				{
					auto *mipRGBAs = mipsToAdd & 1 ? &_mipRGBAs : &_mipRGBBs;

					int mipwidth = priorwidth >> 1;
					int mipheight = priorheight >> 1;
					if ((mipwidth == 0) || (mipheight == 0))
					{
						break;
					}
					//mipRGBAs.GetData()
					if ((mipwidth > 0) && (mipheight > 0))
					{
						mipRGBAs->Reset();
						mipRGBAs->AddUninitialized(mipwidth * mipheight * 4);

						int dataPerRow = priorwidth * 4;

						//Average out the values
						auto *dataOut = mipRGBAs->GetData();
						for (int y = 0; y < mipheight; y++)
						{
							auto *dataInRow0 = priorData + (dataPerRow * y * 2);
							auto *dataInRow1 = dataInRow0 + dataPerRow;
							for (int x = 0; x < mipwidth; x++)
							{
								int totalB = *dataInRow0++;
								int totalG = *dataInRow0++;
								int totalR = *dataInRow0++;
								int totalA = *dataInRow0++;
								totalB += *dataInRow0++;
								totalG += *dataInRow0++;
								totalR += *dataInRow0++;
								totalA += *dataInRow0++;

								totalB += *dataInRow1++;
								totalG += *dataInRow1++;
								totalR += *dataInRow1++;
								totalA += *dataInRow1++;
								totalB += *dataInRow1++;
								totalG += *dataInRow1++;
								totalR += *dataInRow1++;
								totalA += *dataInRow1++;

								totalB >>= 2;
								totalG >>= 2;
								totalR >>= 2;
								totalA >>= 2;

								*dataOut++ = (uint8)totalB;
								*dataOut++ = (uint8)totalG;
								*dataOut++ = (uint8)totalR;
								*dataOut++ = (uint8)totalA;
							}
							dataInRow0 += priorwidth * 2;
							dataInRow1 += priorwidth * 2;
						}

						// Allocate next mipmap.
						FTexture2DMipMap* Mip = new(pNewTexture->PlatformData->Mips) FTexture2DMipMap();
						Mip->SizeX = mipwidth;
						Mip->SizeY = mipheight;
						Mip->BulkData.Lock(LOCK_READ_WRITE);
						void* mipData = Mip->BulkData.Realloc(mipRGBAs->Num());
						FMemory::Memcpy(mipData, mipRGBAs->GetData(), mipRGBAs->Num());
						Mip->BulkData.Unlock();

						priorData = mipRGBAs->GetData();
						priorwidth = mipwidth;
						priorheight = mipheight;
					}
				}
			}

			pNewTexture->SRGB = false;
			pNewTexture->AddressX = TextureAddress::TA_Clamp;
			pNewTexture->AddressY = TextureAddress::TA_Clamp;
			pNewTexture->UpdateResource();
			return pNewTexture;
		}
2 Likes

DeAccepted this as it doesn’t work on iOS - I get mostly black textures. Might be a simple logic problem I’m not sure yet.

It does seem to work on iOS for power of two input textures.

So next question - if I pad my input texture to power of two sizes how do I change the UV address space to map 0->1 UV’s to the original data subset?

Answer. You don’t? Ending thread.

Hi , I’m trying to use your codes to solve my same problem. But the result is strange and the model doesn’t change look or change mips when I scale it. I wonder where can I find the editor’s code to help me find out how mipmaps calculated and used?

Yes, I have the same problem - with this approach the mip maps are simply not used by engine, they are indeed added to Mip array list but never used…