Skip to content

Instantly share code, notes, and snippets.

@Anruin
Last active April 10, 2024 03:51
Show Gist options
  • Save Anruin/01ea3605ce689da6420861857d1a8306 to your computer and use it in GitHub Desktop.
Save Anruin/01ea3605ce689da6420861857d1a8306 to your computer and use it in GitHub Desktop.
CPU code used to generate dynamic mipmaps for UE4 dynamic UTexture2D objects.
// Copyright 2021 Impossibility Labs Inc. https://github.com/ArtheonVR.
// Based on: https://answers.unrealengine.com/questions/607129/how-do-you-generate-mips-at-runtime.html
void FAsyncTaskDownloadTexture::GenerateMipmaps() const {
const int32 Width = Texture->GetSizeX();
const int32 Height = Texture->GetSizeY();
// Texture bytes.
TArray<uint8> TextureByteArray;
TextureByteArray.AddUninitialized(Texture->PlatformData->Mips[0].BulkData.GetElementCount());
FMemory::Memcpy(TextureByteArray.GetData(), Texture->PlatformData->Mips[0].BulkData.Lock(LOCK_READ_ONLY), Texture->PlatformData->Mips[0].BulkData.GetBulkDataSize());
Texture->PlatformData->Mips[0].BulkData.Unlock();
int32 MipsToGenerate = MipmapCount; // Class property
const int32 BytesPerPixel = 4; // By default use 32 bit.
TArray<uint8> MipDataA;
TArray<uint8> MipDataB;
uint8* LastMipData = TextureByteArray.GetData();
int32 LastMipWidth = Width;
int32 LastMipHeight = Height;
while (MipsToGenerate > 0) {
const int32 MipWidth = LastMipWidth >> 1;
const int32 MipHeight = LastMipHeight >> 1;
TArray<uint8>* MipBytes = MipsToGenerate & 1 ? &MipDataA : &MipDataB;
if (MipWidth <= 0 || MipHeight <= 0) {
break;
}
MipBytes->Reset();
MipBytes->AddUninitialized(MipWidth * MipHeight * BytesPerPixel);
const int32 RowDataLen = LastMipWidth * BytesPerPixel;
uint8* OutData = MipBytes->GetData();
for (int32 Y = 0; Y < MipHeight; Y++) {
auto* CurrentRowData = LastMipData + RowDataLen * Y * 2;
auto* NextRowData = CurrentRowData + RowDataLen;
for (int32 X = 0; X < MipWidth; X++) {
int32 TotalB = *CurrentRowData++;
int32 TotalG = *CurrentRowData++;
int32 TotalR = *CurrentRowData++;
int32 TotalA = *CurrentRowData++;
TotalB += *CurrentRowData++;
TotalG += *CurrentRowData++;
TotalR += *CurrentRowData++;
TotalA += *CurrentRowData++;
TotalB += *NextRowData++;
TotalG += *NextRowData++;
TotalR += *NextRowData++;
TotalA += *NextRowData++;
TotalB += *NextRowData++;
TotalG += *NextRowData++;
TotalR += *NextRowData++;
TotalA += *NextRowData++;
TotalB >>= 2;
TotalG >>= 2;
TotalR >>= 2;
TotalA >>= 2;
*OutData++ = static_cast<uint8>(TotalB);
*OutData++ = static_cast<uint8>(TotalG);
*OutData++ = static_cast<uint8>(TotalR);
*OutData++ = static_cast<uint8>(TotalA);
}
CurrentRowData += LastMipWidth * 2;
NextRowData += LastMipWidth * 2;
}
FTexture2DMipMap* Mip = new FTexture2DMipMap();
Texture->PlatformData->Mips.Add(Mip);
Mip->SizeX = MipWidth;
Mip->SizeY = MipHeight;
Mip->BulkData.Lock(LOCK_READ_WRITE);
void* MipData = Mip->BulkData.Realloc(MipBytes->Num());
FMemory::Memcpy(MipData, MipBytes->GetData(), MipBytes->Num());
Mip->BulkData.Unlock();
LastMipData = MipBytes->GetData();
LastMipWidth = MipWidth;
LastMipHeight = MipHeight;
MipsToGenerate--;
}
}
// If you find this helpful, consider to support our project at Patreon https://www.patreon.com/artheon
// Or you can send a tip to Bitcoin 1E6ixVkj5bG9MpBGXhZejEcGJuPQJMWV4V or Ethereum 0xAd3c93F3F82f4bE7366E0677005c106Eaf9120df
@Anruin
Copy link
Author

Anruin commented Jun 13, 2021

Possibly can be optimised a bit by saving time on allocations, etc. But it is used as a temporary hack and runs in async thread, so does its job for us just fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment