From abc7b602b29642e9fe43b77a293db0e906619403 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20M=C3=BCller?= Date: Fri, 3 Nov 2023 22:56:15 +0100 Subject: [PATCH] Avoid copying texture memory when possible Add separate `IGraphics::LoadTextureRawMove` function with non-`const` `void *pData` argument in addition to existing `LoadTextureRaw` function with `const void *pData` argument. The former function takes ownership of the data and avoids copying the texture data into an additional buffer, if the texture data is already in RGBA format. Non-RGBA texture data always needs to be converted and therefore also copied. The `LoadTextureRaw` function is split into smaller functions to share common code with the `LoadTextureRawMove` function. Alternatively to this, a flag `TEXLOAD_MOVE_DATA` could have been added to the existing `LoadTextureRaw` function, which would have required the use of `const_cast` to free the texture data. --- src/engine/client/graphics_threaded.cpp | 73 +++++++++++++------- src/engine/client/graphics_threaded.h | 1 + src/engine/graphics.h | 1 + src/game/client/components/countryflags.cpp | 3 +- src/game/client/components/mapimages.cpp | 5 +- src/game/client/components/menus.cpp | 3 +- src/game/client/components/menus_browser.cpp | 4 +- src/game/editor/editor.cpp | 4 +- 8 files changed, 58 insertions(+), 36 deletions(-) diff --git a/src/engine/client/graphics_threaded.cpp b/src/engine/client/graphics_threaded.cpp index 64d2885ee..597b1e520 100644 --- a/src/engine/client/graphics_threaded.cpp +++ b/src/engine/client/graphics_threaded.cpp @@ -290,10 +290,7 @@ void CGraphics_Threaded::FreeTextureIndex(CTextureHandle *pIndex) int CGraphics_Threaded::UnloadTexture(CTextureHandle *pIndex) { - if(pIndex->Id() == m_NullTexture.Id()) - return 0; - - if(!pIndex->IsValid()) + if(pIndex->IsNullTexture() || !pIndex->IsValid()) return 0; CCommandBuffer::SCommand_Texture_Destroy Cmd; @@ -416,7 +413,7 @@ bool CGraphics_Threaded::IsSpriteTextureFullyTransparent(CImageInfo &FromImageIn return IsImageSubFullyTransparent(FromImageInfo, x, y, w, h); } -IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData, int Flags, const char *pTexName) +static void LoadTextureAddWarning(size_t Width, size_t Height, int Flags, const char *pTexName, std::vector &vWarnings) { if((Flags & IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE) != 0 || (Flags & IGraphics::TEXLOAD_TO_3D_TEXTURE) != 0) { @@ -424,30 +421,22 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(size_t Width, size_ { SWarning NewWarning; char aText[128]; - aText[0] = '\0'; - if(pTexName) - { - str_format(aText, sizeof(aText), "\"%s\"", pTexName); - } + str_format(aText, sizeof(aText), "\"%s\"", pTexName ? pTexName : "(no name)"); str_format(NewWarning.m_aWarningMsg, sizeof(NewWarning.m_aWarningMsg), Localize("The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs."), aText, 16, 16); - - m_vWarnings.emplace_back(NewWarning); + vWarnings.emplace_back(NewWarning); } } +} - if(Width == 0 || Height == 0) - return IGraphics::CTextureHandle(); - - IGraphics::CTextureHandle TextureHandle = FindFreeTextureIndex(); - +static CCommandBuffer::SCommand_Texture_Create LoadTextureCreateCommand(int TextureId, size_t Width, size_t Height, int Flags) +{ CCommandBuffer::SCommand_Texture_Create Cmd; - Cmd.m_Slot = TextureHandle.Id(); + Cmd.m_Slot = TextureId; Cmd.m_Width = Width; Cmd.m_Height = Height; Cmd.m_Format = CCommandBuffer::TEXFORMAT_RGBA; Cmd.m_StoreFormat = CCommandBuffer::TEXFORMAT_RGBA; - // flags Cmd.m_Flags = 0; if(Flags & IGraphics::TEXLOAD_NOMIPMAPS) Cmd.m_Flags |= CCommandBuffer::TEXFLAG_NOMIPMAPS; @@ -458,14 +447,51 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(size_t Width, size_ if((Flags & IGraphics::TEXLOAD_NO_2D_TEXTURE) != 0) Cmd.m_Flags |= CCommandBuffer::TEXFLAG_NO_2D_TEXTURE; - // copy texture data + return Cmd; +} + +IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData, int Flags, const char *pTexName) +{ + LoadTextureAddWarning(Width, Height, Flags, pTexName, m_vWarnings); + + if(Width == 0 || Height == 0) + return IGraphics::CTextureHandle(); + + IGraphics::CTextureHandle TextureHandle = FindFreeTextureIndex(); + CCommandBuffer::SCommand_Texture_Create Cmd = LoadTextureCreateCommand(TextureHandle.Id(), Width, Height, Flags); + + // Copy texture data and convert if necessary const size_t MemSize = Width * Height * CImageInfo::PixelSize(CImageInfo::FORMAT_RGBA); void *pTmpData = malloc(MemSize); - if(!ConvertToRGBA((uint8_t *)pTmpData, (const uint8_t *)pData, Width, Height, Format)) + if(!ConvertToRGBA(static_cast(pTmpData), static_cast(pData), Width, Height, Format)) { - dbg_msg("graphics", "converted image %s to RGBA, consider making its file format RGBA", pTexName ? pTexName : "(no name)"); + dbg_msg("graphics", "converted image '%s' to RGBA, consider making its file format RGBA", pTexName ? pTexName : "(no name)"); } Cmd.m_pData = pTmpData; + + AddCmd(Cmd); + + return TextureHandle; +} + +IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRawMove(size_t Width, size_t Height, CImageInfo::EImageFormat Format, void *pData, int Flags, const char *pTexName) +{ + if(Format != CImageInfo::FORMAT_RGBA) + { + // Moving not possible, texture needs to be converted + return LoadTextureRaw(Width, Height, Format, pData, Flags, pTexName); + } + + LoadTextureAddWarning(Width, Height, Flags, pTexName, m_vWarnings); + + if(Width == 0 || Height == 0) + return IGraphics::CTextureHandle(); + + IGraphics::CTextureHandle TextureHandle = FindFreeTextureIndex(); + CCommandBuffer::SCommand_Texture_Create Cmd = LoadTextureCreateCommand(TextureHandle.Id(), Width, Height, Flags); + + Cmd.m_pData = pData; + AddCmd(Cmd); return TextureHandle; @@ -479,8 +505,7 @@ IGraphics::CTextureHandle CGraphics_Threaded::LoadTexture(const char *pFilename, CImageInfo Img; if(LoadPNG(&Img, pFilename, StorageType)) { - CTextureHandle ID = LoadTextureRaw(Img.m_Width, Img.m_Height, Img.m_Format, Img.m_pData, Flags, pFilename); - FreePNG(&Img); + CTextureHandle ID = LoadTextureRawMove(Img.m_Width, Img.m_Height, Img.m_Format, Img.m_pData, Flags, pFilename); if(ID.IsValid()) { if(g_Config.m_Debug) diff --git a/src/engine/client/graphics_threaded.h b/src/engine/client/graphics_threaded.h index 99892a495..dfcdc128e 100644 --- a/src/engine/client/graphics_threaded.h +++ b/src/engine/client/graphics_threaded.h @@ -951,6 +951,7 @@ public: void FreeTextureIndex(CTextureHandle *pIndex); int UnloadTexture(IGraphics::CTextureHandle *pIndex) override; IGraphics::CTextureHandle LoadTextureRaw(size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData, int Flags, const char *pTexName = nullptr) override; + IGraphics::CTextureHandle LoadTextureRawMove(size_t Width, size_t Height, CImageInfo::EImageFormat Format, void *pData, int Flags, const char *pTexName = nullptr) override; int LoadTextureRawSub(IGraphics::CTextureHandle TextureID, int x, int y, size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData) override; IGraphics::CTextureHandle NullTexture() const override; diff --git a/src/engine/graphics.h b/src/engine/graphics.h index 86d5fc9a2..323e8094a 100644 --- a/src/engine/graphics.h +++ b/src/engine/graphics.h @@ -335,6 +335,7 @@ public: virtual int UnloadTexture(CTextureHandle *pIndex) = 0; virtual CTextureHandle LoadTextureRaw(size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData, int Flags, const char *pTexName = nullptr) = 0; + virtual CTextureHandle LoadTextureRawMove(size_t Width, size_t Height, CImageInfo::EImageFormat Format, void *pData, int Flags, const char *pTexName = nullptr) = 0; virtual int LoadTextureRawSub(CTextureHandle TextureID, int x, int y, size_t Width, size_t Height, CImageInfo::EImageFormat Format, const void *pData) = 0; virtual CTextureHandle LoadTexture(const char *pFilename, int StorageType, int Flags = 0) = 0; virtual CTextureHandle NullTexture() const = 0; diff --git a/src/game/client/components/countryflags.cpp b/src/game/client/components/countryflags.cpp index 1f28a4bc5..059471fb4 100644 --- a/src/game/client/components/countryflags.cpp +++ b/src/game/client/components/countryflags.cpp @@ -75,8 +75,7 @@ void CCountryFlags::LoadCountryflagsIndexfile() CCountryFlag CountryFlag; CountryFlag.m_CountryCode = CountryCode; str_copy(CountryFlag.m_aCountryCodeString, aOrigin); - CountryFlag.m_Texture = Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, 0, aOrigin); - Graphics()->FreePNG(&Info); + CountryFlag.m_Texture = Graphics()->LoadTextureRawMove(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, 0, aOrigin); if(g_Config.m_Debug) { diff --git a/src/game/client/components/mapimages.cpp b/src/game/client/components/mapimages.cpp index 9a4733f16..0c05a6102 100644 --- a/src/game/client/components/mapimages.cpp +++ b/src/game/client/components/mapimages.cpp @@ -445,10 +445,7 @@ IGraphics::CTextureHandle CMapImages::UploadEntityLayerText(int TextureSize, int UpdateEntityLayerText(pMem, PixelSize, Width, Height, TextureSize, MaxWidth, YOffset, 2, 255); const int TextureLoadFlag = (Graphics()->Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE) | IGraphics::TEXLOAD_NO_2D_TEXTURE; - IGraphics::CTextureHandle Texture = Graphics()->LoadTextureRaw(Width, Height, CImageInfo::FORMAT_RGBA, pMem, TextureLoadFlag); - free(pMem); - - return Texture; + return Graphics()->LoadTextureRawMove(Width, Height, CImageInfo::FORMAT_RGBA, pMem, TextureLoadFlag); } void CMapImages::UpdateEntityLayerText(void *pTexBuffer, size_t PixelSize, size_t TexWidth, size_t TexHeight, int TextureSize, int MaxWidth, int YOffset, int NumbersPower, int MaxNumber) diff --git a/src/game/client/components/menus.cpp b/src/game/client/components/menus.cpp index 003d4c7a2..a2e6f65d8 100644 --- a/src/game/client/components/menus.cpp +++ b/src/game/client/components/menus.cpp @@ -2197,8 +2197,7 @@ int CMenus::MenuImageScan(const char *pName, int IsDir, int DirType, void *pUser pData[i * Step + 1] = v; pData[i * Step + 2] = v; } - MenuImage.m_GreyTexture = pSelf->Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, 0); - pSelf->Graphics()->FreePNG(&Info); + MenuImage.m_GreyTexture = pSelf->Graphics()->LoadTextureRawMove(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, 0); str_truncate(MenuImage.m_aName, sizeof(MenuImage.m_aName), pName, str_length(pName) - str_length(pExtension)); pSelf->m_vMenuImages.push_back(MenuImage); diff --git a/src/game/client/components/menus_browser.cpp b/src/game/client/components/menus_browser.cpp index 6bebd8b8b..c89a44dc5 100644 --- a/src/game/client/components/menus_browser.cpp +++ b/src/game/client/components/menus_browser.cpp @@ -1908,8 +1908,8 @@ void CMenus::LoadCommunityIconFinish(const char *pCommunityId, CImageInfo &&Info pData[i * Step + 1] = v; pData[i * Step + 2] = v; } - CommunityIcon.m_GreyTexture = Graphics()->LoadTextureRaw(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, 0); - Graphics()->FreePNG(&Info); + CommunityIcon.m_GreyTexture = Graphics()->LoadTextureRawMove(Info.m_Width, Info.m_Height, Info.m_Format, Info.m_pData, 0); + Info.m_pData = nullptr; auto ExistingIcon = std::find_if(m_vCommunityIcons.begin(), m_vCommunityIcons.end(), [pCommunityId](const SCommunityIcon &Element) { return str_comp(Element.m_aCommunityId, pCommunityId) == 0; diff --git a/src/game/editor/editor.cpp b/src/game/editor/editor.cpp index 8b39f8491..bbc000d30 100644 --- a/src/game/editor/editor.cpp +++ b/src/game/editor/editor.cpp @@ -5166,8 +5166,8 @@ void CEditor::RenderFileDialog() if(Graphics()->LoadPNG(&m_FilePreviewImageInfo, aBuffer, m_vpFilteredFileList[m_FilesSelectedIndex]->m_StorageType)) { Graphics()->UnloadTexture(&m_FilePreviewImage); - m_FilePreviewImage = Graphics()->LoadTextureRaw(m_FilePreviewImageInfo.m_Width, m_FilePreviewImageInfo.m_Height, m_FilePreviewImageInfo.m_Format, m_FilePreviewImageInfo.m_pData, 0); - Graphics()->FreePNG(&m_FilePreviewImageInfo); + m_FilePreviewImage = Graphics()->LoadTextureRawMove(m_FilePreviewImageInfo.m_Width, m_FilePreviewImageInfo.m_Height, m_FilePreviewImageInfo.m_Format, m_FilePreviewImageInfo.m_pData, 0); + m_FilePreviewImageInfo.m_pData = nullptr; m_FilePreviewState = PREVIEW_LOADED; } else