Last active
April 10, 2024 03:51
-
-
Save Anruin/01ea3605ce689da6420861857d1a8306 to your computer and use it in GitHub Desktop.
CPU code used to generate dynamic mipmaps for UE4 dynamic UTexture2D objects.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.