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 kernel.cpp
linereader.cpp linereader.cpp
linereader.h linereader.h
localization.h
map.cpp map.cpp
map.h map.h
masterserver.cpp masterserver.cpp

View file

@ -69,7 +69,7 @@ def decode(fileobj, elements_per_key):
def check_file(path): def check_file(path):
with open(path, encoding="utf-8") as fileobj: 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 return matches
@ -81,7 +81,7 @@ def check_folder(path):
if not any(f.endswith(x) for x in ".cpp .c .h".split()): if not any(f.endswith(x) for x in ".cpp .c .h".split()):
continue continue
for sentence in check_file(os.path.join(path2, f)): for sentence in check_file(os.path.join(path2, f)):
englishlist[sentence] = None englishlist[sentence[1:]] = None
return englishlist return englishlist

View file

@ -8,8 +8,9 @@
#include <SDL_video.h> #include <SDL_video.h>
#include <atomic> #include <atomic>
#include <stddef.h> #include <cstddef>
#include <stdint.h> #include <cstdint>
#include <string>
#include <vector> #include <vector>
struct SBackendCapabilites; struct SBackendCapabilites;
@ -23,9 +24,55 @@ enum EDebugGFXModes
DEBUG_GFX_MODE_ALL, 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 class CCommandProcessorFragment_GLBase
{ {
protected: protected:
SGFXErrorContainer m_Error;
SGFXWarningContainer m_Warning;
static size_t TexFormatToImageColorChannelCount(int TexFormat); static size_t TexFormatToImageColorChannelCount(int TexFormat);
static void *Resize(const unsigned char *pData, int Width, int Height, int NewWidth, int NewHeight, int BPP); static void *Resize(const unsigned char *pData, int Width, int Height, int NewWidth, int NewHeight, int BPP);
@ -35,11 +82,16 @@ protected:
public: public:
virtual ~CCommandProcessorFragment_GLBase() = default; 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 StartCommands(size_t CommandCount, size_t EstimatedRenderCallCount) {}
virtual void EndCommands() {} virtual void EndCommands() {}
const SGFXErrorContainer &GetError() { return m_Error; }
virtual void ErroneousCleanup() {}
const SGFXWarningContainer &GetWarning() { return m_Warning; }
enum enum
{ {
CMD_PRE_INIT = CCommandBuffer::CMDGROUP_PLATFORM_GL, CMD_PRE_INIT = CCommandBuffer::CMDGROUP_PLATFORM_GL,

View file

@ -2,7 +2,7 @@
#include <engine/client/backend_sdl.h> #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) 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)); Cmd_TextTexture_Update(static_cast<const CCommandBuffer::SCommand_TextTexture_Update *>(pBaseCommand));
break; break;
} }
return true; return ERunCommandReturnTypes::RUN_COMMAND_COMMAND_HANDLED;
} }
bool CCommandProcessorFragment_Null::Cmd_Init(const SCommand_Init *pCommand) bool CCommandProcessorFragment_Null::Cmd_Init(const SCommand_Init *pCommand)

View file

@ -6,7 +6,7 @@
class CCommandProcessorFragment_Null : public CCommandProcessorFragment_GLBase 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 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); bool Cmd_Init(const SCommand_Init *pCommand);
virtual void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand); virtual void Cmd_Texture_Create(const CCommandBuffer::SCommand_Texture_Create *pCommand);
virtual void Cmd_TextTextures_Create(const CCommandBuffer::SCommand_TextTextures_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; m_HasShaders = false;
} }
bool CCommandProcessorFragment_OpenGL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand) ERunCommandReturnTypes CCommandProcessorFragment_OpenGL::RunCommand(const CCommandBuffer::SCommand *pBaseCommand)
{ {
switch(pBaseCommand->m_Cmd) 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: 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_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; 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 // ------------ CCommandProcessorFragment_OpenGL2

View file

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

File diff suppressed because it is too large Load diff

View file

@ -5,11 +5,13 @@
#endif #endif
#include <SDL.h> #include <SDL.h>
#include <SDL_messagebox.h>
#include <base/math.h> #include <base/math.h>
#include <cstdlib> #include <cstdlib>
#include <engine/shared/config.h> #include <engine/shared/config.h>
#include <engine/shared/localization.h>
#include <base/tl/threading.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_pBuffer = nullptr;
m_pProcessor = nullptr; m_pProcessor = nullptr;
@ -97,6 +100,7 @@ void CGraphicsBackend_Threaded::StopProcessor()
m_Shutdown = true; m_Shutdown = true;
{ {
std::unique_lock<std::mutex> Lock(m_BufferSwapMutex); std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
m_Warning = m_pProcessor->GetWarning();
m_BufferSwapCond.notify_all(); m_BufferSwapCond.notify_all();
} }
thread_wait(m_pThread); thread_wait(m_pThread);
@ -106,13 +110,27 @@ void CGraphicsBackend_Threaded::RunBuffer(CCommandBuffer *pBuffer)
{ {
#ifdef CONF_WEBASM #ifdef CONF_WEBASM
// run everything single threaded for now, context binding in a thread seems to not work as of now // 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 #else
WaitForIdle(); WaitForIdle();
std::unique_lock<std::mutex> Lock(m_BufferSwapMutex); std::unique_lock<std::mutex> Lock(m_BufferSwapMutex);
m_pBuffer = pBuffer; if(!m_pProcessor->HasError())
m_BufferInProcess.store(true, std::memory_order_relaxed); {
m_BufferSwapCond.notify_all(); m_pBuffer = pBuffer;
m_BufferInProcess.store(true, std::memory_order_relaxed);
m_BufferSwapCond.notify_all();
}
else
{
ProcessError();
}
#endif #endif
} }
@ -132,6 +150,28 @@ void CGraphicsBackend_Threaded::WaitForIdle()
m_BufferSwapCond.wait(Lock, [this]() { return m_pBuffer == nullptr; }); 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 // ------------ CCommandProcessorFragment_General
void CCommandProcessorFragment_General::Cmd_Signal(const CCommandBuffer::SCommand_Signal *pCommand) 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 // ------------ 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) void CCommandProcessor_SDL_GL::RunBuffer(CCommandBuffer *pBuffer)
{ {
m_pGLBackend->StartCommands(pBuffer->m_CommandCount, pBuffer->m_RenderCallCount); m_pGLBackend->StartCommands(pBuffer->m_CommandCount, pBuffer->m_RenderCallCount);
for(CCommandBuffer::SCommand *pCommand = pBuffer->Head(); pCommand; pCommand = pCommand->m_pNext) 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; 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)) if(m_SDL.RunCommand(pCommand))
continue; continue;
@ -299,6 +409,21 @@ CCommandProcessor_SDL_GL::~CCommandProcessor_SDL_GL()
delete m_pGLBackend; 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 // ------------ CGraphicsBackend_SDL_GL
static bool BackendInitGlew(EBackendType BackendType, int &GlewMajor, int &GlewMinor, int &GlewPatch) 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) bool CGraphicsBackend_SDL_GL::IsModernAPI(EBackendType BackendType)
{ {
if(BackendType == BACKEND_TYPE_OPENGL) 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); 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)); mem_zero(m_aErrorString, std::size(m_aErrorString));
} }
@ -1473,4 +1607,4 @@ TGLBackendReadPresentedImageData &CGraphicsBackend_SDL_GL::GetReadPresentedImage
return m_ReadPresentedImageDataFunc; 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 <base/detect.h>
#include "engine/graphics.h" #include <engine/graphics.h>
#include "graphics_threaded.h"
#include <engine/client/graphics_threaded.h>
#include <engine/client/backend/backend_base.h>
#include <atomic> #include <atomic>
#include <condition_variable> #include <condition_variable>
#include <cstddef>
#include <cstdint>
#include <mutex> #include <mutex>
#include <stddef.h> #include <vector>
#include <stdint.h>
#if defined(CONF_PLATFORM_MACOS) #if defined(CONF_PLATFORM_MACOS)
#include <objc/objc-runtime.h> #include <objc/objc-runtime.h>
@ -42,26 +46,55 @@ public:
// basic threaded backend, abstract, missing init and shutdown functions // basic threaded backend, abstract, missing init and shutdown functions
class CGraphicsBackend_Threaded : public IGraphicsBackend class CGraphicsBackend_Threaded : public IGraphicsBackend
{ {
private:
TTranslateFunc m_TranslateFunc;
SGFXWarningContainer m_Warning;
public: public:
// constructed on the main thread, the rest of the functions is run on the render thread // constructed on the main thread, the rest of the functions is run on the render thread
class ICommandProcessor class ICommandProcessor
{ {
public: public:
virtual ~ICommandProcessor() {} virtual ~ICommandProcessor() = default;
virtual void RunBuffer(CCommandBuffer *pBuffer) = 0; 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 RunBuffer(CCommandBuffer *pBuffer) override;
void RunBufferSingleThreadedUnsafe(CCommandBuffer *pBuffer) override; void RunBufferSingleThreadedUnsafe(CCommandBuffer *pBuffer) override;
bool IsIdle() const override; bool IsIdle() const override;
void WaitForIdle() override; void WaitForIdle() override;
void ProcessError();
protected: protected:
void StartProcessor(ICommandProcessor *pProcessor); void StartProcessor(ICommandProcessor *pProcessor);
void StopProcessor(); 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: private:
ICommandProcessor *m_pProcessor; ICommandProcessor *m_pProcessor;
std::mutex m_BufferSwapMutex; std::mutex m_BufferSwapMutex;
@ -73,6 +106,9 @@ private:
void *m_pThread; void *m_pThread;
static void ThreadFunc(void *pUser); static void ThreadFunc(void *pUser);
public:
bool GetWarning(std::vector<std::string> &WarningStrings) override;
}; };
// takes care of implementation independent operations // takes care of implementation independent operations
@ -152,16 +188,27 @@ public:
// command processor implementation, uses the fragments to combine into one processor // command processor implementation, uses the fragments to combine into one processor
class CCommandProcessor_SDL_GL : public CGraphicsBackend_Threaded::ICommandProcessor class CCommandProcessor_SDL_GL : public CGraphicsBackend_Threaded::ICommandProcessor
{ {
class CCommandProcessorFragment_GLBase *m_pGLBackend; CCommandProcessorFragment_GLBase *m_pGLBackend;
CCommandProcessorFragment_SDL m_SDL; CCommandProcessorFragment_SDL m_SDL;
CCommandProcessorFragment_General m_General; CCommandProcessorFragment_General m_General;
EBackendType m_BackendType; EBackendType m_BackendType;
SGFXErrorContainer m_Error;
SGFXWarningContainer m_Warning;
public: public:
CCommandProcessor_SDL_GL(EBackendType BackendType, int GLMajor, int GLMinor, int GLPatch); CCommandProcessor_SDL_GL(EBackendType BackendType, int GLMajor, int GLMinor, int GLPatch);
virtual ~CCommandProcessor_SDL_GL(); virtual ~CCommandProcessor_SDL_GL();
void RunBuffer(CCommandBuffer *pBuffer) override; 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; static constexpr size_t gs_GPUInfoStringSize = 256;
@ -196,8 +243,11 @@ class CGraphicsBackend_SDL_GL : public CGraphicsBackend_Threaded
static EBackendType DetectBackend(); static EBackendType DetectBackend();
static void ClampDriverVersion(EBackendType BackendType); static void ClampDriverVersion(EBackendType BackendType);
protected:
bool TryCreateMsgBox(bool AsError, const char *pTitle, const char *pMsg) override;
public: 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 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; int Shutdown() override;

View file

@ -794,6 +794,17 @@ void CGraphics_Threaded::KickCommandBuffer()
{ {
m_pBackend->RunBuffer(m_pCommandBuffer); 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 // swap buffer
m_CurrentCommandBuffer ^= 1; m_CurrentCommandBuffer ^= 1;
m_pCommandBuffer = m_apCommandBuffers[m_CurrentCommandBuffer]; m_pCommandBuffer = m_apCommandBuffers[m_CurrentCommandBuffer];
@ -2830,7 +2841,7 @@ int CGraphics_Threaded::Init()
m_FirstFreeBufferObjectIndex = -1; m_FirstFreeBufferObjectIndex = -1;
m_FirstFreeQuadContainer = -1; m_FirstFreeQuadContainer = -1;
m_pBackend = CreateGraphicsBackend(); m_pBackend = CreateGraphicsBackend(Localize);
if(InitWindow() != 0) if(InitWindow() != 0)
return -1; return -1;

View file

@ -5,6 +5,7 @@
#include <engine/shared/config.h> #include <engine/shared/config.h>
#include <cstddef> #include <cstddef>
#include <string>
#include <vector> #include <vector>
constexpr int CMD_BUFFER_DATA_BUFFER_SIZE = 1024 * 1024 * 2; constexpr int CMD_BUFFER_DATA_BUFFER_SIZE = 1024 * 1024 * 2;
@ -714,7 +715,7 @@ public:
INITFLAG_DESKTOP_FULLSCREEN = 1 << 5, 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 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; 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 // 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 TGLBackendReadPresentedImageData &GetReadPresentedImageDataFuncUnsafe() = 0;
virtual bool GetWarning(std::vector<std::string> &WarningStrings) = 0;
}; };
class CGraphics_Threaded : public IEngineGraphics class CGraphics_Threaded : public IEngineGraphics
@ -1313,6 +1316,7 @@ public:
TGLBackendReadPresentedImageData &GetReadPresentedImageDataFuncUnsafe() override; 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 #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) 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 // reset active item
UI()->SetActiveItem(nullptr); UI()->SetActiveItem(nullptr);

View file

@ -7,6 +7,7 @@
#include <engine/keys.h> #include <engine/keys.h>
#include <engine/serverbrowser.h> #include <engine/serverbrowser.h>
#include <engine/shared/config.h> #include <engine/shared/config.h>
#include <engine/shared/localization.h>
#include <engine/textrender.h> #include <engine/textrender.h>
#include <game/client/components/console.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] = { static const char *LOCATION_NAMES[CServerInfo::NUM_LOCS] = {
"", // LOC_UNKNOWN "", // LOC_UNKNOWN
"AFR", // LOC_AFRICA // Localize("AFR") Localizable("AFR"), // LOC_AFRICA
"ASI", // LOC_ASIA // Localize("ASI") Localizable("ASI"), // LOC_ASIA
"AUS", // LOC_AUSTRALIA // Localize("AUS") Localizable("AUS"), // LOC_AUSTRALIA
"EUR", // LOC_EUROPE // Localize("EUR") Localizable("EUR"), // LOC_EUROPE
"NA", // LOC_NORTH_AMERICA // Localize("NA") Localizable("NA"), // LOC_NORTH_AMERICA
"SA", // LOC_SOUTH_AMERICA // Localize("SA") Localizable("SA"), // LOC_SOUTH_AMERICA
"CHN", // LOC_CHINA // Localize("CHN") Localizable("CHN"), // LOC_CHINA
}; };
dbg_assert(0 <= pInfo->m_Location && pInfo->m_Location < CServerInfo::NUM_LOCS, "location out of range"); 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); 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_FAV, -1, " ", -1, 14.0f, {0}, {0}},
{COL_FLAG_OFFICIAL, -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_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_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}}, {COL_PLAYERS, IServerBrowser::SORT_NUMPLAYERS, "Players", 1, 85.0f, {0}, {0}},
{-1, -1, " ", 1, 10.0f, {0}, {0}}, {-1, -1, " ", 1, 10.0f, {0}, {0}},
{COL_PING, IServerBrowser::SORT_PING, "Ping", 1, 40.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); int NumCols = std::size(s_aCols);

View file

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