6404: Use text and quad containers for MOTD rendering, fix MOTD height calculation r=def- a=Robyt3

Optimize MOTD rendering by caching the round rect and the text.

Use the correct text height based on the aligned font size instead of the original font size, to fix the discrepancy between the scrollable height and the text height. Closes #6346.

Screenshots:

- Tutorial, ingame:
   - Before: 
![Tutorial ingame old](https://user-images.githubusercontent.com/23437060/224178255-5bac285f-0d0b-45ba-880a-10329c0075d1.png)
   - After:
![Tutorial ingame new](https://user-images.githubusercontent.com/23437060/224178246-d9348037-cc91-4456-893f-5041040575ff.png)
- Multeasymap, ingame:
   - Before: 
![Multeasymap ingame old](https://user-images.githubusercontent.com/23437060/224178443-f4aad8fb-f9a3-4cbc-a12b-ff0cc166b2e8.png)
   - After:
![Multeasymap ingame new](https://user-images.githubusercontent.com/23437060/224178458-d155ecf4-dfc4-496f-a2a5-ef5422d2cf4f.png)
- Test server, ingame:
   - Before: 
![Testing ingame new](https://user-images.githubusercontent.com/23437060/224178543-0cd794fa-ad89-453b-b255-2fcef0fd4cb0.png)
   - After: 
![Testing ingame old](https://user-images.githubusercontent.com/23437060/224178532-52d00a96-4d4d-4fc1-a9c1-94c7717f6f7b.png)
- Long, ingame:
   - Before: 
![Long ingame old](https://user-images.githubusercontent.com/23437060/224177971-5066aa03-b017-4a34-ab02-ba8b07968791.png)
   - After:
![Long ingame new](https://user-images.githubusercontent.com/23437060/224177991-52c6d391-9524-4013-b332-9a511a2cba75.png)
- Long, menu:
   - Before: 
![Long menu old](https://user-images.githubusercontent.com/23437060/224178139-6fb7b785-5b1f-4b22-acdb-514022c89181.png)
   - After:
![Long menu new](https://user-images.githubusercontent.com/23437060/224178137-d351fe22-8bc9-42e0-af5a-d414e551e4c9.png)

## Checklist

- [X] Tested the change ingame
- [X] 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: Robert Müller <robytemueller@gmail.com>
This commit is contained in:
bors[bot] 2023-03-09 23:39:49 +00:00 committed by GitHub
commit 010652c102
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 119 additions and 35 deletions

View file

@ -960,11 +960,11 @@ public:
float ScreenX0, ScreenY0, ScreenX1, ScreenY1; float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
const float FakeToScreenX = (Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0)); const float FakeToScreenX = Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0);
const float FakeToScreenY = (Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); const float FakeToScreenY = Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0);
const int ActualX = (int)((pCursor->m_X * FakeToScreenX) + 0.5f); const int ActualX = round_to_int(pCursor->m_X * FakeToScreenX);
const int ActualY = (int)((pCursor->m_Y * FakeToScreenY) + 0.5f); const int ActualY = round_to_int(pCursor->m_Y * FakeToScreenY);
TextContainer.m_AlignedStartX = ActualX / FakeToScreenX; TextContainer.m_AlignedStartX = ActualX / FakeToScreenX;
TextContainer.m_AlignedStartY = ActualY / FakeToScreenY; TextContainer.m_AlignedStartY = ActualY / FakeToScreenY;
@ -1024,11 +1024,11 @@ public:
float ScreenX0, ScreenY0, ScreenX1, ScreenY1; float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1); Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
const float FakeToScreenX = (Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0)); const float FakeToScreenX = Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0);
const float FakeToScreenY = (Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); const float FakeToScreenY = Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0);
const int ActualX = (int)((pCursor->m_X * FakeToScreenX) + 0.5f); const int ActualX = round_to_int(pCursor->m_X * FakeToScreenX);
const int ActualY = (int)((pCursor->m_Y * FakeToScreenY) + 0.5f); const int ActualY = round_to_int(pCursor->m_Y * FakeToScreenY);
const float CursorX = ActualX / FakeToScreenX; const float CursorX = ActualX / FakeToScreenX;
const float CursorY = ActualY / FakeToScreenY; const float CursorY = ActualY / FakeToScreenY;
@ -1142,8 +1142,8 @@ public:
DrawY += Size; DrawY += Size;
if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) if((RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0)
{ {
DrawX = (int)((DrawX * FakeToScreenX) + 0.5f) / FakeToScreenX; // realign DrawX = round_to_int(DrawX * FakeToScreenX) / FakeToScreenX; // realign
DrawY = (int)((DrawY * FakeToScreenY) + 0.5f) / FakeToScreenY; DrawY = round_to_int(DrawY * FakeToScreenY) / FakeToScreenY;
} }
LastSelX = DrawX; LastSelX = DrawX;
LastSelWidth = 0; LastSelWidth = 0;
@ -1677,10 +1677,10 @@ public:
if((TextContainer.m_RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0) if((TextContainer.m_RenderFlags & TEXT_RENDER_FLAG_NO_PIXEL_ALIGMENT) == 0)
{ {
const float FakeToScreenX = (Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0)); const float FakeToScreenX = Graphics()->ScreenWidth() / (ScreenX1 - ScreenX0);
const float FakeToScreenY = (Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0)); const float FakeToScreenY = Graphics()->ScreenHeight() / (ScreenY1 - ScreenY0);
const int ActualX = (int)(((TextContainer.m_X + X) * FakeToScreenX) + 0.5f); const int ActualX = round_to_int((TextContainer.m_X + X) * FakeToScreenX);
const int ActualY = (int)(((TextContainer.m_Y + Y) * FakeToScreenY) + 0.5f); const int ActualY = round_to_int((TextContainer.m_Y + Y) * FakeToScreenY);
const float AlignedX = ActualX / FakeToScreenX; const float AlignedX = ActualX / FakeToScreenX;
const float AlignedY = ActualY / FakeToScreenY; const float AlignedY = ActualY / FakeToScreenY;
X = AlignedX - TextContainer.m_AlignedStartX; X = AlignedX - TextContainer.m_AlignedStartX;

View file

@ -105,6 +105,11 @@ public:
ETextCursorCursorMode m_CursorMode; ETextCursorCursorMode m_CursorMode;
// note this is the decoded character offset // note this is the decoded character offset
int m_CursorCharacter; int m_CursorCharacter;
float Height() const
{
return m_LineCount * m_AlignedFontSize;
}
}; };
class ITextRender : public IInterface class ITextRender : public IInterface

View file

@ -2332,6 +2332,9 @@ void CMenus::OnStateChange(int NewState, int OldState)
// reset active item // reset active item
UI()->SetActiveItem(nullptr); UI()->SetActiveItem(nullptr);
if(OldState == IClient::STATE_ONLINE || OldState == IClient::STATE_OFFLINE)
TextRender()->DeleteTextContainer(m_MotdTextContainerIndex);
if(NewState == IClient::STATE_OFFLINE) if(NewState == IClient::STATE_OFFLINE)
{ {
if(OldState >= IClient::STATE_ONLINE && NewState < IClient::STATE_QUITTING) if(OldState >= IClient::STATE_ONLINE && NewState < IClient::STATE_QUITTING)
@ -2370,6 +2373,11 @@ void CMenus::OnStateChange(int NewState, int OldState)
} }
} }
void CMenus::OnWindowResize()
{
TextRender()->DeleteTextContainer(m_MotdTextContainerIndex);
}
void CMenus::OnRender() void CMenus::OnRender()
{ {
UI()->StartCheck(); UI()->StartCheck();

View file

@ -513,11 +513,13 @@ protected:
void RenderStartMenu(CUIRect MainView); void RenderStartMenu(CUIRect MainView);
// found in menus_ingame.cpp // found in menus_ingame.cpp
int m_MotdTextContainerIndex = -1;
void RenderGame(CUIRect MainView); void RenderGame(CUIRect MainView);
void PopupConfirmDisconnect(); void PopupConfirmDisconnect();
void PopupConfirmDisconnectDummy(); void PopupConfirmDisconnectDummy();
void RenderPlayers(CUIRect MainView); void RenderPlayers(CUIRect MainView);
void RenderServerInfo(CUIRect MainView); void RenderServerInfo(CUIRect MainView);
void RenderServerInfoMotd(CUIRect Motd);
void RenderServerControl(CUIRect MainView); void RenderServerControl(CUIRect MainView);
bool RenderServerControlKick(CUIRect MainView, bool FilterSpectators); bool RenderServerControlKick(CUIRect MainView, bool FilterSpectators);
bool RenderServerControlServer(CUIRect MainView); bool RenderServerControlServer(CUIRect MainView);
@ -590,6 +592,7 @@ public:
void OnConsoleInit() override; void OnConsoleInit() override;
virtual void OnStateChange(int NewState, int OldState) override; virtual void OnStateChange(int NewState, int OldState) override;
virtual void OnWindowResize() override;
virtual void OnReset() override; virtual void OnReset() override;
virtual void OnRender() override; virtual void OnRender() override;
virtual bool OnInput(IInput::CEvent Event) override; virtual bool OnInput(IInput::CEvent Event) override;

View file

@ -475,7 +475,11 @@ void CMenus::RenderServerInfo(CUIRect MainView)
TextRender()->Text(GameInfo.x + x, GameInfo.y + y, 20, aBuf, GameInfo.w - 10.0f); TextRender()->Text(GameInfo.x + x, GameInfo.y + y, 20, aBuf, GameInfo.w - 10.0f);
} }
// motd RenderServerInfoMotd(Motd);
}
void CMenus::RenderServerInfoMotd(CUIRect Motd)
{
const float MotdFontSize = 16.0f; const float MotdFontSize = 16.0f;
Motd.HSplitTop(10.0f, nullptr, &Motd); Motd.HSplitTop(10.0f, nullptr, &Motd);
Motd.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f); Motd.Draw(ColorRGBA(1, 1, 1, 0.25f), IGraphics::CORNER_ALL, 10.0f);
@ -487,6 +491,9 @@ void CMenus::RenderServerInfo(CUIRect MainView)
Motd.HSplitTop(5.0f, nullptr, &Motd); Motd.HSplitTop(5.0f, nullptr, &Motd);
TextRender()->Text(MotdHeader.x, MotdHeader.y, 2.0f * MotdFontSize, Localize("MOTD"), -1.0f); TextRender()->Text(MotdHeader.x, MotdHeader.y, 2.0f * MotdFontSize, Localize("MOTD"), -1.0f);
if(!m_pClient->m_Motd.ServerMotd()[0])
return;
static CScrollRegion s_ScrollRegion; static CScrollRegion s_ScrollRegion;
vec2 ScrollOffset(0.0f, 0.0f); vec2 ScrollOffset(0.0f, 0.0f);
CScrollRegionParams ScrollParams; CScrollRegionParams ScrollParams;
@ -494,10 +501,24 @@ void CMenus::RenderServerInfo(CUIRect MainView)
s_ScrollRegion.Begin(&Motd, &ScrollOffset, &ScrollParams); s_ScrollRegion.Begin(&Motd, &ScrollOffset, &ScrollParams);
Motd.y += ScrollOffset.y; Motd.y += ScrollOffset.y;
static float s_MotdHeight = 0.0f;
static int64_t s_MotdLastUpdateTime = -1;
if(m_MotdTextContainerIndex == -1 || s_MotdLastUpdateTime == -1 || s_MotdLastUpdateTime != m_pClient->m_Motd.ServerMotdUpdateTime())
{
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, 0.0f, 0.0f, MotdFontSize, TEXTFLAG_RENDER);
Cursor.m_LineWidth = Motd.w;
TextRender()->RecreateTextContainer(m_MotdTextContainerIndex, &Cursor, m_pClient->m_Motd.ServerMotd());
s_MotdHeight = Cursor.Height();
s_MotdLastUpdateTime = m_pClient->m_Motd.ServerMotdUpdateTime();
}
CUIRect MotdTextArea; CUIRect MotdTextArea;
Motd.HSplitTop((str_countchr(m_pClient->m_Motd.m_aServerMotd, '\n') + 1) * MotdFontSize, &MotdTextArea, &Motd); Motd.HSplitTop(s_MotdHeight, &MotdTextArea, &Motd);
s_ScrollRegion.AddRect(MotdTextArea); s_ScrollRegion.AddRect(MotdTextArea);
TextRender()->Text(MotdTextArea.x, MotdTextArea.y, MotdFontSize, m_pClient->m_Motd.m_aServerMotd, MotdTextArea.w);
if(m_MotdTextContainerIndex != -1)
TextRender()->RenderTextContainer(m_MotdTextContainerIndex, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor(), MotdTextArea.x, MotdTextArea.y);
s_ScrollRegion.End(); s_ScrollRegion.End();
} }

View file

@ -10,12 +10,21 @@
#include "motd.h" #include "motd.h"
CMotd::CMotd()
{
m_aServerMotd[0] = '\0';
m_ServerMotdTime = 0;
m_ServerMotdUpdateTime = 0;
}
void CMotd::Clear() void CMotd::Clear()
{ {
m_ServerMotdTime = 0; m_ServerMotdTime = 0;
Graphics()->DeleteQuadContainer(m_RectQuadContainer);
TextRender()->DeleteTextContainer(m_TextContainerIndex);
} }
bool CMotd::IsActive() bool CMotd::IsActive() const
{ {
return time() < m_ServerMotdTime; return time() < m_ServerMotdTime;
} }
@ -26,24 +35,51 @@ void CMotd::OnStateChange(int NewState, int OldState)
Clear(); Clear();
} }
void CMotd::OnWindowResize()
{
Graphics()->DeleteQuadContainer(m_RectQuadContainer);
TextRender()->DeleteTextContainer(m_TextContainerIndex);
}
void CMotd::OnRender() void CMotd::OnRender()
{ {
if(!IsActive()) if(!IsActive())
return; return;
float Width = 400 * 3.0f * Graphics()->ScreenAspect(); const float FontSize = 32.0f; // also the size of the margin and rect rounding
float Height = 400 * 3.0f; const float ScreenHeight = 40.0f * FontSize; // multiple of the font size to get perfect alignment
const float ScreenWidth = ScreenHeight * Graphics()->ScreenAspect();
Graphics()->MapScreen(0.0f, 0.0f, ScreenWidth, ScreenHeight);
Graphics()->MapScreen(0, 0, Width, Height); const float RectHeight = 26.0f * FontSize;
const float RectWidth = 630.0f + 2.0f * FontSize;
const float RectX = ScreenWidth / 2.0f - RectWidth / 2.0f;
const float RectY = 160.0f;
float h = 800.0f; if(m_RectQuadContainer == -1)
float w = 650.0f; {
float x = Width / 2 - w / 2; Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.5f);
float y = 150.0f; m_RectQuadContainer = Graphics()->CreateRectQuadContainer(RectX, RectY, RectWidth, RectHeight, FontSize, IGraphics::CORNER_ALL);
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
}
Graphics()->DrawRect(x, y, w, h, ColorRGBA(0.0f, 0.0f, 0.0f, 0.5f), IGraphics::CORNER_ALL, 40.0f); if(m_RectQuadContainer != -1)
Graphics()->RenderQuadContainer(m_RectQuadContainer, -1);
TextRender()->Text(x + 40.0f, y + 40.0f, 32.0f, m_aServerMotd, w - 80.0f); const float TextWidth = RectWidth - 2.0f * FontSize;
const float TextX = RectX + FontSize;
const float TextY = RectY + FontSize;
if(m_TextContainerIndex == -1)
{
CTextCursor Cursor;
TextRender()->SetCursor(&Cursor, TextX, TextY, FontSize, TEXTFLAG_RENDER);
Cursor.m_LineWidth = TextWidth;
TextRender()->CreateTextContainer(m_TextContainerIndex, &Cursor, ServerMotd());
}
if(m_TextContainerIndex != -1)
TextRender()->RenderTextContainer(m_TextContainerIndex, TextRender()->DefaultTextColor(), TextRender()->DefaultTextOutlineColor());
} }
void CMotd::OnMessage(int MsgType, void *pRawMsg) void CMotd::OnMessage(int MsgType, void *pRawMsg)
@ -53,13 +89,13 @@ void CMotd::OnMessage(int MsgType, void *pRawMsg)
if(MsgType == NETMSGTYPE_SV_MOTD) if(MsgType == NETMSGTYPE_SV_MOTD)
{ {
CNetMsg_Sv_Motd *pMsg = (CNetMsg_Sv_Motd *)pRawMsg; const CNetMsg_Sv_Motd *pMsg = static_cast<CNetMsg_Sv_Motd *>(pRawMsg);
// copy it manually to process all \n // copy it manually to process all \n
const char *pMsgStr = pMsg->m_pMessage; const char *pMsgStr = pMsg->m_pMessage;
int MotdLen = str_length(pMsgStr) + 1; const size_t MotdLen = str_length(pMsgStr) + 1;
const char *pLast = m_aServerMotd; // for console printing const char *pLast = m_aServerMotd; // for console printing
for(int i = 0, k = 0; i < MotdLen && k < (int)sizeof(m_aServerMotd); i++, k++) for(size_t i = 0, k = 0; i < MotdLen && k < sizeof(m_aServerMotd); i++, k++)
{ {
// handle incoming "\\n" // handle incoming "\\n"
if(pMsgStr[i] == '\\' && pMsgStr[i + 1] == 'n') if(pMsgStr[i] == '\\' && pMsgStr[i + 1] == 'n')
@ -83,10 +119,12 @@ void CMotd::OnMessage(int MsgType, void *pRawMsg)
if(g_Config.m_ClPrintMotd && *pLast != '\0') if(g_Config.m_ClPrintMotd && *pLast != '\0')
m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "motd", pLast, color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClMessageHighlightColor))); m_pClient->Console()->Print(IConsole::OUTPUT_LEVEL_STANDARD, "motd", pLast, color_cast<ColorRGBA>(ColorHSLA(g_Config.m_ClMessageHighlightColor)));
m_ServerMotdUpdateTime = time();
if(m_aServerMotd[0] && g_Config.m_ClMotdTime) if(m_aServerMotd[0] && g_Config.m_ClMotdTime)
m_ServerMotdTime = time() + time_freq() * g_Config.m_ClMotdTime; m_ServerMotdTime = m_ServerMotdUpdateTime + time_freq() * g_Config.m_ClMotdTime;
else else
m_ServerMotdTime = 0; m_ServerMotdTime = 0;
TextRender()->DeleteTextContainer(m_TextContainerIndex);
} }
} }

View file

@ -2,22 +2,31 @@
/* If you are missing that file, acquire a complete release at teeworlds.com. */ /* If you are missing that file, acquire a complete release at teeworlds.com. */
#ifndef GAME_CLIENT_COMPONENTS_MOTD_H #ifndef GAME_CLIENT_COMPONENTS_MOTD_H
#define GAME_CLIENT_COMPONENTS_MOTD_H #define GAME_CLIENT_COMPONENTS_MOTD_H
#include <engine/shared/config.h>
#include <game/client/component.h> #include <game/client/component.h>
class CMotd : public CComponent class CMotd : public CComponent
{ {
// motd char m_aServerMotd[std::size(g_Config.m_SvMotd)];
int64_t m_ServerMotdTime; int64_t m_ServerMotdTime;
int64_t m_ServerMotdUpdateTime;
int m_RectQuadContainer = -1;
int m_TextContainerIndex = -1;
public: public:
char m_aServerMotd[900]; CMotd();
virtual int Sizeof() const override { return sizeof(*this); } virtual int Sizeof() const override { return sizeof(*this); }
const char *ServerMotd() const { return m_aServerMotd; }
int64_t ServerMotdUpdateTime() const { return m_ServerMotdUpdateTime; }
void Clear(); void Clear();
bool IsActive(); bool IsActive() const;
virtual void OnRender() override; virtual void OnRender() override;
virtual void OnStateChange(int NewState, int OldState) override; virtual void OnStateChange(int NewState, int OldState) override;
virtual void OnWindowResize() override;
virtual void OnMessage(int MsgType, void *pRawMsg) override; virtual void OnMessage(int MsgType, void *pRawMsg) override;
virtual bool OnInput(IInput::CEvent Event) override; virtual bool OnInput(IInput::CEvent Event) override;
}; };