5853: Improve error handling in vulkan r=def- a=Jupeyy

so it doesn't assert & shows a message box with the error and some tips

Trying to get more useful information visible to the user on asserts
("dsadsa" ofc testing string)
![image](https://user-images.githubusercontent.com/6654924/190920609-161825a1-0184-44c4-af0c-1d6bcd343c93.png)

Haven't tested lot, it's lots of smaller changes to the code, hopefully didn't oversee smth^^

## Checklist

- [x] Tested the change ingame
- [ ] Provided screenshots if it is a visual change
- [ ] Tested in combination with possibly related configuration options
- [ ] Written a unit test (especially base/) or added coverage to integration test
- [ ] Considered possible null pointers and out of bounds array indexing
- [ ] Changed no physics that affect existing maps
- [ ] Tested the change with [ASan+UBSan or valgrind's memcheck](https://github.com/ddnet/ddnet/#using-addresssanitizer--undefinedbehavioursanitizer-or-valgrinds-memcheck) (optional)


Co-authored-by: Jupeyy <jupjopjap@gmail.com>
This commit is contained in:
bors[bot] 2022-12-13 17:42:53 +00:00 committed by GitHub
commit 62f7296a98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 1051 additions and 450 deletions

View file

@ -1950,6 +1950,7 @@ set_src(ENGINE_SHARED GLOB_RECURSE src/engine/shared
kernel.cpp
linereader.cpp
linereader.h
localization.h
map.cpp
map.h
masterserver.cpp

View file

@ -69,7 +69,7 @@ def decode(fileobj, elements_per_key):
def check_file(path):
with open(path, encoding="utf-8") as fileobj:
matches = re.findall(r"Localize\s*\(\s*\"([^\"]+)\"(?:\s*,\s*\"([^\"]+)\")?\s*\)", fileobj.read())
matches = re.findall(r"(Localize|Localizable)\s*\(\s*\"([^\"]+)\"(?:\s*,\s*\"([^\"]+)\")?\s*\)", fileobj.read())
return matches
@ -81,7 +81,7 @@ def check_folder(path):
if not any(f.endswith(x) for x in ".cpp .c .h".split()):
continue
for sentence in check_file(os.path.join(path2, f)):
englishlist[sentence] = None
englishlist[sentence[1:]] = None
return englishlist

View file

@ -8,8 +8,9 @@
#include <SDL_video.h>
#include <atomic>
#include <stddef.h>
#include <stdint.h>
#include <cstddef>
#include <cstdint>
#include <string>
#include <vector>
struct SBackendCapabilites;
@ -23,9 +24,55 @@ enum EDebugGFXModes
DEBUG_GFX_MODE_ALL,
};
enum ERunCommandReturnTypes
{
RUN_COMMAND_COMMAND_HANDLED = 0,
RUN_COMMAND_COMMAND_UNHANDLED,
RUN_COMMAND_COMMAND_WARNING,
RUN_COMMAND_COMMAND_ERROR,
};
enum EGFXErrorType
{
GFX_ERROR_TYPE_NONE = 0,
GFX_ERROR_TYPE_INIT,
GFX_ERROR_TYPE_OUT_OF_MEMORY_IMAGE,
GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER,
GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING,
GFX_ERROR_TYPE_RENDER_RECORDING,
GFX_ERROR_TYPE_RENDER_CMD_FAILED,
GFX_ERROR_TYPE_RENDER_SUBMIT_FAILED,
GFX_ERROR_TYPE_SWAP_FAILED,
GFX_ERROR_TYPE_UNKNOWN,
};
enum EGFXWarningType
{
GFX_WARNING_TYPE_NONE = 0,
GFX_WARNING_TYPE_INIT_FAILED,
GFX_WARNING_LOW_ON_MEMORY,
GFX_WARNING_MISSING_EXTENSION,
GFX_WARNING_TYPE_UNKNOWN,
};
struct SGFXErrorContainer
{
EGFXErrorType m_ErrorType = EGFXErrorType::GFX_ERROR_TYPE_NONE;
std::vector<std::string> m_vErrors;
};
struct SGFXWarningContainer
{
EGFXWarningType m_WarningType = EGFXWarningType::GFX_WARNING_TYPE_NONE;
std::vector<std::string> m_vWarnings;
};
class CCommandProcessorFragment_GLBase
{
protected:
SGFXErrorContainer m_Error;
SGFXWarningContainer m_Warning;
static size_t TexFormatToImageColorChannelCount(int TexFormat);
static void *Resize(const unsigned char *pData, int Width, int Height, int NewWidth, int NewHeight, int BPP);
@ -35,11 +82,16 @@ protected:
public:
virtual ~CCommandProcessorFragment_GLBase() = default;
virtual bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand) = 0;
virtual ERunCommandReturnTypes RunCommand(const CCommandBuffer::SCommand *pBaseCommand) = 0;
virtual void StartCommands(size_t CommandCount, size_t EstimatedRenderCallCount) {}
virtual void EndCommands() {}
const SGFXErrorContainer &GetError() { return m_Error; }
virtual void ErroneousCleanup() {}
const SGFXWarningContainer &GetWarning() { return m_Warning; }
enum
{
CMD_PRE_INIT = CCommandBuffer::CMDGROUP_PLATFORM_GL,

View file

@ -2,7 +2,7 @@
#include <engine/client/backend_sdl.h>
bool CCommandProcessorFragment_Null::RunCommand(const CCommandBuffer::SCommand *pBaseCommand)
ERunCommandReturnTypes CCommandProcessorFragment_Null::RunCommand(const CCommandBuffer::SCommand *pBaseCommand)
{
switch(pBaseCommand->m_Cmd)
{
@ -19,7 +19,7 @@ bool CCommandProcessorFragment_Null::RunCommand(const CCommandBuffer::SCommand *
Cmd_TextTexture_Update(static_cast<const CCommandBuffer::SCommand_TextTexture_Update *>(pBaseCommand));
break;
}
return true;
return ERunCommandReturnTypes::RUN_COMMAND_COMMAND_HANDLED;
}
bool CCommandProcessorFragment_Null::Cmd_Init(const SCommand_Init *pCommand)

View file

@ -6,7 +6,7 @@
class CCommandProcessorFragment_Null : public CCommandProcessorFragment_GLBase
{
bool GetPresentedImageData(uint32_t &Width, uint32_t &Height, uint32_t &Format, std::vector<uint8_t> &vDstData) override { return false; };
bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand) override;
ERunCommandReturnTypes RunCommand(const CCommandBuffer::SCommand *pBaseCommand) override;
bool Cmd_Init(const SCommand_Init *pCommand);
virtual void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand);
virtual void Cmd_TextTextures_Create(const CCommandBuffer::SCommand_TextTextures_Create *pCommand);

View file

@ -1059,7 +1059,7 @@ CCommandProcessorFragment_OpenGL::CCommandProcessorFragment_OpenGL()
m_HasShaders = false;
}
bool CCommandProcessorFragment_OpenGL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand)
ERunCommandReturnTypes CCommandProcessorFragment_OpenGL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand)
{
switch(pBaseCommand->m_Cmd)
{
@ -1125,10 +1125,10 @@ bool CCommandProcessorFragment_OpenGL::RunCommand(const CCommandBuffer::SCommand
case CCommandBuffer::CMD_RENDER_QUAD_CONTAINER: Cmd_RenderQuadContainer(static_cast<const CCommandBuffer::SCommand_RenderQuadContainer *>(pBaseCommand)); break;
case CCommandBuffer::CMD_RENDER_QUAD_CONTAINER_EX: Cmd_RenderQuadContainerEx(static_cast<const CCommandBuffer::SCommand_RenderQuadContainerEx *>(pBaseCommand)); break;
case CCommandBuffer::CMD_RENDER_QUAD_CONTAINER_SPRITE_MULTIPLE: Cmd_RenderQuadContainerAsSpriteMultiple(static_cast<const CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple *>(pBaseCommand)); break;
default: return false;
default: return ERunCommandReturnTypes::RUN_COMMAND_COMMAND_UNHANDLED;
}
return true;
return ERunCommandReturnTypes::RUN_COMMAND_COMMAND_HANDLED;
}
// ------------ CCommandProcessorFragment_OpenGL2

View file

@ -128,7 +128,7 @@ public:
CCommandProcessorFragment_OpenGL();
virtual ~CCommandProcessorFragment_OpenGL() = default;
bool RunCommand(const CCommandBuffer::SCommand *pBaseCommand) override;
ERunCommandReturnTypes RunCommand(const CCommandBuffer::SCommand *pBaseCommand) override;
};
class CCommandProcessorFragment_OpenGL2 : public CCommandProcessorFragment_OpenGL

File diff suppressed because it is too large Load diff

View file

@ -5,11 +5,13 @@
#endif
#include <SDL.h>
#include <SDL_messagebox.h>
#include <base/math.h>
#include <cstdlib>
#include <engine/shared/config.h>
#include <engine/shared/localization.h>
#include <base/tl/threading.h>
@ -72,7 +74,8 @@ void CGraphicsBackend_Threaded::ThreadFunc(void *pUser)
}
}
CGraphicsBackend_Threaded::CGraphicsBackend_Threaded()
CGraphicsBackend_Threaded::CGraphicsBackend_Threaded(TTranslateFunc &&TranslateFunc) :
m_TranslateFunc(std::move(TranslateFunc))
{
m_pBuffer = nullptr;
m_pProcessor = nullptr;
@ -97,6 +100,7 @@ void CGraphicsBackend_Threaded::StopProcessor()
m_Shutdown = true;
{
std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
m_Warning = m_pProcessor->GetWarning();
m_BufferSwapCond.notify_all();
}
thread_wait(m_pThread);
@ -106,13 +110,27 @@ void CGraphicsBackend_Threaded::RunBuffer(CCommandBuffer *pBuffer)
{
#ifdef CONF_WEBASM
// run everything single threaded for now, context binding in a thread seems to not work as of now
RunBufferSingleThreadedUnsafe(pBuffer);
if(!m_pProcessor->HasError())
{
RunBufferSingleThreadedUnsafe(pBuffer);
}
else
{
ProcessError();
}
#else
WaitForIdle();
std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
m_pBuffer = pBuffer;
m_BufferInProcess.store(true, std::memory_order_relaxed);
m_BufferSwapCond.notify_all();
if(!m_pProcessor->HasError())
{
m_pBuffer = pBuffer;
m_BufferInProcess.store(true, std::memory_order_relaxed);
m_BufferSwapCond.notify_all();
}
else
{
ProcessError();
}
#endif
}
@ -132,6 +150,28 @@ void CGraphicsBackend_Threaded::WaitForIdle()
m_BufferSwapCond.wait(Lock, [this]() { return m_pBuffer == nullptr; });
}
void CGraphicsBackend_Threaded::ProcessError()
{
const auto &Error = m_pProcessor->GetError();
std::string VerboseStr;
for(const auto &ErrStr : Error.m_vErrors)
VerboseStr.append(std::string(m_TranslateFunc(ErrStr.c_str(), "")) + "\n");
const auto CreatedMsgBox = TryCreateMsgBox(true, "Graphics Assertion", VerboseStr.c_str());
// check if error msg can be shown, then assert
dbg_assert(!CreatedMsgBox, VerboseStr.c_str());
}
bool CGraphicsBackend_Threaded::GetWarning(std::vector<std::string> &WarningStrings)
{
if(HasWarning())
{
m_Warning.m_WarningType = GFX_WARNING_TYPE_NONE;
WarningStrings = m_Warning.m_vWarnings;
return true;
}
return false;
}
// ------------ CCommandProcessorFragment_General
void CCommandProcessorFragment_General::Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand)
@ -223,14 +263,84 @@ bool CCommandProcessorFragment_SDL::RunCommand(const CCommandBuffer::SCommand *p
// ------------ CCommandProcessor_SDL_GL
void CCommandProcessor_SDL_GL::HandleError()
{
auto &Error = GetError();
switch(Error.m_ErrorType)
{
case GFX_ERROR_TYPE_INIT:
Error.m_vErrors.emplace_back(Localizable("Failed during initialization. Try to change gfx_backend to OpenGL or Vulkan from settings_ddnet.cfg in the config directory and try again."));
break;
case GFX_ERROR_TYPE_OUT_OF_MEMORY_IMAGE:
[[fallthrough]];
case GFX_ERROR_TYPE_OUT_OF_MEMORY_BUFFER:
[[fallthrough]];
case GFX_ERROR_TYPE_OUT_OF_MEMORY_STAGING:
Error.m_vErrors.emplace_back(Localizable("Out of VRAM. Try removing custom assets (skins, entities etc.), especially with high resolution."));
break;
case GFX_ERROR_TYPE_RENDER_RECORDING:
Error.m_vErrors.emplace_back(Localizable("An error during command recording occurred. Try to update your GPU drivers."));
break;
case GFX_ERROR_TYPE_RENDER_CMD_FAILED:
Error.m_vErrors.emplace_back(Localizable("A render command failed. Try to update your GPU drivers."));
break;
case GFX_ERROR_TYPE_RENDER_SUBMIT_FAILED:
Error.m_vErrors.emplace_back(Localizable("Submitting the render commands failed. Try to update your GPU drivers."));
break;
case GFX_ERROR_TYPE_SWAP_FAILED:
Error.m_vErrors.emplace_back(Localizable("Failed to swap framebuffers. Try to update your GPU drivers."));
break;
case GFX_ERROR_TYPE_UNKNOWN:
[[fallthrough]];
default:
Error.m_vErrors.emplace_back(Localizable("Unknown error. Try to change gfx_backend to OpenGL or Vulkan from settings_ddnet.cfg in the config directory and try again."));
break;
}
}
void CCommandProcessor_SDL_GL::HandleWarning()
{
auto &Warn = GetWarning();
switch(Warn.m_WarningType)
{
case GFX_WARNING_TYPE_INIT_FAILED:
Warn.m_vWarnings.emplace_back(Localizable("Could not initialize the given graphics backend, reverting to the default backend now."));
break;
case GFX_WARNING_MISSING_EXTENSION:
// ignore this warning for now
return;
case GFX_WARNING_LOW_ON_MEMORY:
// ignore this warning for now
return;
default:
dbg_msg("gfx", "unhandled warning %d", (int)Warn.m_WarningType);
break;
}
}
void CCommandProcessor_SDL_GL::RunBuffer(CCommandBuffer *pBuffer)
{
m_pGLBackend->StartCommands(pBuffer->m_CommandCount, pBuffer->m_RenderCallCount);
for(CCommandBuffer::SCommand *pCommand = pBuffer->Head(); pCommand; pCommand = pCommand->m_pNext)
{
if(m_pGLBackend->RunCommand(pCommand))
auto Res = m_pGLBackend->RunCommand(pCommand);
if(Res == ERunCommandReturnTypes::RUN_COMMAND_COMMAND_HANDLED)
{
continue;
}
else if(Res == ERunCommandReturnTypes::RUN_COMMAND_COMMAND_ERROR)
{
m_Error = m_pGLBackend->GetError();
HandleError();
return;
}
else if(Res == ERunCommandReturnTypes::RUN_COMMAND_COMMAND_WARNING)
{
m_Warning = m_pGLBackend->GetWarning();
HandleWarning();
return;
}
if(m_SDL.RunCommand(pCommand))
continue;
@ -299,6 +409,21 @@ CCommandProcessor_SDL_GL::~CCommandProcessor_SDL_GL()
delete m_pGLBackend;
}
SGFXErrorContainer &CCommandProcessor_SDL_GL::GetError()
{
return m_Error;
}
void CCommandProcessor_SDL_GL::ErroneousCleanup()
{
return m_pGLBackend->ErroneousCleanup();
}
SGFXWarningContainer &CCommandProcessor_SDL_GL::GetWarning()
{
return m_Warning;
}
// ------------ CGraphicsBackend_SDL_GL
static bool BackendInitGlew(EBackendType BackendType, int &GlewMajor, int &GlewMinor, int &GlewPatch)
@ -641,6 +766,14 @@ void CGraphicsBackend_SDL_GL::ClampDriverVersion(EBackendType BackendType)
}
}
bool CGraphicsBackend_SDL_GL::TryCreateMsgBox(bool AsError, const char *pTitle, const char *pMsg)
{
m_pProcessor->ErroneousCleanup();
SDL_DestroyWindow(m_pWindow);
SDL_ShowSimpleMessageBox(AsError ? SDL_MESSAGEBOX_ERROR : SDL_MESSAGEBOX_WARNING, pTitle, pMsg, nullptr);
return true;
}
bool CGraphicsBackend_SDL_GL::IsModernAPI(EBackendType BackendType)
{
if(BackendType == BACKEND_TYPE_OPENGL)
@ -838,7 +971,8 @@ void CGraphicsBackend_SDL_GL::GetCurrentVideoMode(CVideoMode &CurMode, int HiDPI
DisplayToVideoMode(&CurMode, &DPMode, HiDPIScale, DPMode.refresh_rate);
}
CGraphicsBackend_SDL_GL::CGraphicsBackend_SDL_GL()
CGraphicsBackend_SDL_GL::CGraphicsBackend_SDL_GL(TTranslateFunc &&TranslateFunc) :
CGraphicsBackend_Threaded(std::move(TranslateFunc))
{
mem_zero(m_aErrorString, std::size(m_aErrorString));
}
@ -1473,4 +1607,4 @@ TGLBackendReadPresentedImageData &CGraphicsBackend_SDL_GL::GetReadPresentedImage
return m_ReadPresentedImageDataFunc;
}
IGraphicsBackend *CreateGraphicsBackend() { return new CGraphicsBackend_SDL_GL; }
IGraphicsBackend *CreateGraphicsBackend(TTranslateFunc &&TranslateFunc) { return new CGraphicsBackend_SDL_GL(std::move(TranslateFunc)); }

View file

@ -5,14 +5,18 @@
#include <base/detect.h>
#include "engine/graphics.h"
#include "graphics_threaded.h"
#include <engine/graphics.h>
#include <engine/client/graphics_threaded.h>
#include <engine/client/backend/backend_base.h>
#include <atomic>
#include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <mutex>
#include <stddef.h>
#include <stdint.h>
#include <vector>
#if defined(CONF_PLATFORM_MACOS)
#include <objc/objc-runtime.h>
@ -42,26 +46,55 @@ public:
// basic threaded backend, abstract, missing init and shutdown functions
class CGraphicsBackend_Threaded : public IGraphicsBackend
{
private:
TTranslateFunc m_TranslateFunc;
SGFXWarningContainer m_Warning;
public:
// constructed on the main thread, the rest of the functions is run on the render thread
class ICommandProcessor
{
public:
virtual ~ICommandProcessor() {}
virtual ~ICommandProcessor() = default;
virtual void RunBuffer(CCommandBuffer *pBuffer) = 0;
virtual SGFXErrorContainer &GetError() = 0;
virtual void ErroneousCleanup() = 0;
virtual SGFXWarningContainer &GetWarning() = 0;
bool HasError()
{
return GetError().m_ErrorType != GFX_ERROR_TYPE_NONE;
}
bool HasWarning()
{
return GetWarning().m_WarningType != GFX_WARNING_TYPE_NONE;
}
};
CGraphicsBackend_Threaded();
CGraphicsBackend_Threaded(TTranslateFunc &&TranslateFunc);
void RunBuffer(CCommandBuffer *pBuffer) override;
void RunBufferSingleThreadedUnsafe(CCommandBuffer *pBuffer) override;
bool IsIdle() const override;
void WaitForIdle() override;
void ProcessError();
protected:
void StartProcessor(ICommandProcessor *pProcessor);
void StopProcessor();
bool HasWarning()
{
return m_Warning.m_WarningType != GFX_WARNING_TYPE_NONE;
}
// returns true if the error msg was shown
virtual bool TryCreateMsgBox(bool AsError, const char *pTitle, const char *pMsg) = 0;
private:
ICommandProcessor *m_pProcessor;
std::mutex m_BufferSwapMutex;
@ -73,6 +106,9 @@ private:
void *m_pThread;
static void ThreadFunc(void *pUser);
public:
bool GetWarning(std::vector<std::string> &WarningStrings) override;
};
// takes care of implementation independent operations
@ -152,16 +188,27 @@ public:
// command processor implementation, uses the fragments to combine into one processor
class CCommandProcessor_SDL_GL : public CGraphicsBackend_Threaded::ICommandProcessor
{
class CCommandProcessorFragment_GLBase *m_pGLBackend;
CCommandProcessorFragment_GLBase *m_pGLBackend;
CCommandProcessorFragment_SDL m_SDL;
CCommandProcessorFragment_General m_General;
EBackendType m_BackendType;
SGFXErrorContainer m_Error;
SGFXWarningContainer m_Warning;
public:
CCommandProcessor_SDL_GL(EBackendType BackendType, int GLMajor, int GLMinor, int GLPatch);
virtual ~CCommandProcessor_SDL_GL();
void RunBuffer(CCommandBuffer *pBuffer) override;
SGFXErrorContainer &GetError() override;
void ErroneousCleanup() override;
SGFXWarningContainer &GetWarning() override;
void HandleError();
void HandleWarning();
};
static constexpr size_t gs_GPUInfoStringSize = 256;
@ -196,8 +243,11 @@ class CGraphicsBackend_SDL_GL : public CGraphicsBackend_Threaded
static EBackendType DetectBackend();
static void ClampDriverVersion(EBackendType BackendType);
protected:
bool TryCreateMsgBox(bool AsError, const char *pTitle, const char *pMsg) override;
public:
CGraphicsBackend_SDL_GL();
CGraphicsBackend_SDL_GL(TTranslateFunc &&TranslateFunc);
int Init(const char *pName, int *pScreen, int *pWidth, int *pHeight, int *pRefreshRate, int *pFsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight, int *pCurrentWidth, int *pCurrentHeight, class IStorage *pStorage) override;
int Shutdown() override;

View file

@ -794,6 +794,17 @@ void CGraphics_Threaded::KickCommandBuffer()
{
m_pBackend->RunBuffer(m_pCommandBuffer);
std::vector<std::string> WarningStrings;
if(m_pBackend->GetWarning(WarningStrings))
{
SWarning NewWarning;
std::string WarningStr;
for(const auto &WarnStr : WarningStrings)
WarningStr.append((WarnStr + "\n"));
str_copy(NewWarning.m_aWarningMsg, WarningStr.c_str());
m_vWarnings.emplace_back(NewWarning);
}
// swap buffer
m_CurrentCommandBuffer ^= 1;
m_pCommandBuffer = m_apCommandBuffers[m_CurrentCommandBuffer];
@ -2830,7 +2841,7 @@ int CGraphics_Threaded::Init()
m_FirstFreeBufferObjectIndex = -1;
m_FirstFreeQuadContainer = -1;
m_pBackend = CreateGraphicsBackend();
m_pBackend = CreateGraphicsBackend(Localize);
if(InitWindow() != 0)
return -1;

View file

@ -5,6 +5,7 @@
#include <engine/shared/config.h>
#include <cstddef>
#include <string>
#include <vector>
constexpr int CMD_BUFFER_DATA_BUFFER_SIZE = 1024 * 1024 * 2;
@ -714,7 +715,7 @@ public:
INITFLAG_DESKTOP_FULLSCREEN = 1 << 5,
};
virtual ~IGraphicsBackend() {}
virtual ~IGraphicsBackend() = default;
virtual int Init(const char *pName, int *pScreen, int *pWidth, int *pHeight, int *pRefreshRate, int *pFsaaSamples, int Flags, int *pDesktopWidth, int *pDesktopHeight, int *pCurrentWidth, int *pCurrentHeight, class IStorage *pStorage) = 0;
virtual int Shutdown() = 0;
@ -770,6 +771,8 @@ public:
// be aware that this function should only be called from the graphics thread, and even then you should really know what you are doing
virtual TGLBackendReadPresentedImageData &GetReadPresentedImageDataFuncUnsafe() = 0;
virtual bool GetWarning(std::vector<std::string> &WarningStrings) = 0;
};
class CGraphics_Threaded : public IEngineGraphics
@ -1313,6 +1316,7 @@ public:
TGLBackendReadPresentedImageData &GetReadPresentedImageDataFuncUnsafe() override;
};
extern IGraphicsBackend *CreateGraphicsBackend();
typedef std::function<const char *(const char *, const char *)> TTranslateFunc;
extern IGraphicsBackend *CreateGraphicsBackend(TTranslateFunc &&TranslateFunc);
#endif // ENGINE_CLIENT_GRAPHICS_THREADED_H

View file

@ -0,0 +1,12 @@
#ifndef ENGINE_SHARED_LOCALIZATION_H
#define ENGINE_SHARED_LOCALIZATION_H
/**
* An empty function that suits as a helper to identify strings that might get localized later
*/
static constexpr const char *Localizable(const char *pStr, const char *pContext = "")
{
return pStr;
}
#endif

View file

@ -1090,7 +1090,11 @@ void CMenus::PopupConfirm(const char *pTitle, const char *pMessage, const char *
void CMenus::PopupWarning(const char *pTopic, const char *pBody, const char *pButton, std::chrono::nanoseconds Duration)
{
dbg_msg(pTopic, "%s", pBody);
// no multiline support for console
std::string BodyStr = pBody;
while(BodyStr.find('\n') != std::string::npos)
BodyStr.replace(BodyStr.find('\n'), 1, " ");
dbg_msg(pTopic, "%s", BodyStr.c_str());
// reset active item
UI()->SetActiveItem(nullptr);

View file

@ -7,6 +7,7 @@
#include <engine/keys.h>
#include <engine/serverbrowser.h>
#include <engine/shared/config.h>
#include <engine/shared/localization.h>
#include <engine/textrender.h>
#include <game/client/components/console.h>
@ -38,13 +39,13 @@ void FormatServerbrowserPing(char *pBuffer, int BufferLength, const CServerInfo
}
static const char *LOCATION_NAMES[CServerInfo::NUM_LOCS] = {
"", // LOC_UNKNOWN
"AFR", // LOC_AFRICA // Localize("AFR")
"ASI", // LOC_ASIA // Localize("ASI")
"AUS", // LOC_AUSTRALIA // Localize("AUS")
"EUR", // LOC_EUROPE // Localize("EUR")
"NA", // LOC_NORTH_AMERICA // Localize("NA")
"SA", // LOC_SOUTH_AMERICA // Localize("SA")
"CHN", // LOC_CHINA // Localize("CHN")
Localizable("AFR"), // LOC_AFRICA
Localizable("ASI"), // LOC_ASIA
Localizable("AUS"), // LOC_AUSTRALIA
Localizable("EUR"), // LOC_EUROPE
Localizable("NA"), // LOC_NORTH_AMERICA
Localizable("SA"), // LOC_SOUTH_AMERICA
Localizable("CHN"), // LOC_CHINA
};
dbg_assert(0 <= pInfo->m_Location && pInfo->m_Location < CServerInfo::NUM_LOCS, "location out of range");
str_copy(pBuffer, Localize(LOCATION_NAMES[pInfo->m_Location]), BufferLength);
@ -92,14 +93,12 @@ void CMenus::RenderServerbrowserServerList(CUIRect View)
{COL_FLAG_FAV, -1, " ", -1, 14.0f, {0}, {0}},
{COL_FLAG_OFFICIAL, -1, " ", -1, 14.0f, {0}, {0}},
{COL_NAME, IServerBrowser::SORT_NAME, "Name", 0, 50.0f, {0}, {0}}, // Localize - these strings are localized within CLocConstString
{COL_GAMETYPE, IServerBrowser::SORT_GAMETYPE, "Type", 1, 50.0f, {0}, {0}},
{COL_GAMETYPE, IServerBrowser::SORT_GAMETYPE, Localizable("Type"), 1, 50.0f, {0}, {0}},
{COL_MAP, IServerBrowser::SORT_MAP, "Map", 1, 120.0f + (Headers.w - 480) / 8, {0}, {0}},
{COL_PLAYERS, IServerBrowser::SORT_NUMPLAYERS, "Players", 1, 85.0f, {0}, {0}},
{-1, -1, " ", 1, 10.0f, {0}, {0}},
{COL_PING, IServerBrowser::SORT_PING, "Ping", 1, 40.0f, {0}, {0}},
};
// This is just for scripts/update_localization.py to work correctly (all other strings are already Localize()'d somewhere else). Don't remove!
// Localize("Type");
int NumCols = std::size(s_aCols);

View file

@ -10,6 +10,7 @@
#include <engine/graphics.h>
#include <engine/serverbrowser.h>
#include <engine/shared/config.h>
#include <engine/shared/localization.h>
#include <engine/textrender.h>
#include <game/generated/client_data.h>
@ -985,8 +986,8 @@ void CMenus::RenderGhost(CUIRect MainView)
static CColumn s_aCols[] = {
{" ", -1, 2.0f, {0}, {0}},
{" ", COL_ACTIVE, 30.0f, {0}, {0}},
{"Name", COL_NAME, 300.0f, {0}, {0}}, // Localize("Name")
{"Time", COL_TIME, 200.0f, {0}, {0}}, // Localize("Time")
{Localizable("Name"), COL_NAME, 300.0f, {0}, {0}},
{Localizable("Time"), COL_TIME, 200.0f, {0}, {0}},
};
int NumCols = std::size(s_aCols);