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

View file

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

View file

@ -2332,6 +2332,9 @@ void CMenus::OnStateChange(int NewState, int OldState)
// reset active item
UI()->SetActiveItem(nullptr);
if(OldState == IClient::STATE_ONLINE || OldState == IClient::STATE_OFFLINE)
TextRender()->DeleteTextContainer(m_MotdTextContainerIndex);
if(NewState == IClient::STATE_OFFLINE)
{
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()
{
UI()->StartCheck();

View file

@ -513,11 +513,13 @@ protected:
void RenderStartMenu(CUIRect MainView);
// found in menus_ingame.cpp
int m_MotdTextContainerIndex = -1;
void RenderGame(CUIRect MainView);
void PopupConfirmDisconnect();
void PopupConfirmDisconnectDummy();
void RenderPlayers(CUIRect MainView);
void RenderServerInfo(CUIRect MainView);
void RenderServerInfoMotd(CUIRect Motd);
void RenderServerControl(CUIRect MainView);
bool RenderServerControlKick(CUIRect MainView, bool FilterSpectators);
bool RenderServerControlServer(CUIRect MainView);
@ -590,6 +592,7 @@ public:
void OnConsoleInit() override;
virtual void OnStateChange(int NewState, int OldState) override;
virtual void OnWindowResize() override;
virtual void OnReset() override;
virtual void OnRender() 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);
}
// motd
RenderServerInfoMotd(Motd);
}
void CMenus::RenderServerInfoMotd(CUIRect Motd)
{
const float MotdFontSize = 16.0f;
Motd.HSplitTop(10.0f, nullptr, &Motd);
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);
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;
vec2 ScrollOffset(0.0f, 0.0f);
CScrollRegionParams ScrollParams;
@ -494,10 +501,24 @@ void CMenus::RenderServerInfo(CUIRect MainView)
s_ScrollRegion.Begin(&Motd, &ScrollOffset, &ScrollParams);
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;
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);
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();
}

View file

@ -10,12 +10,21 @@
#include "motd.h"
CMotd::CMotd()
{
m_aServerMotd[0] = '\0';
m_ServerMotdTime = 0;
m_ServerMotdUpdateTime = 0;
}
void CMotd::Clear()
{
m_ServerMotdTime = 0;
Graphics()->DeleteQuadContainer(m_RectQuadContainer);
TextRender()->DeleteTextContainer(m_TextContainerIndex);
}
bool CMotd::IsActive()
bool CMotd::IsActive() const
{
return time() < m_ServerMotdTime;
}
@ -26,24 +35,51 @@ void CMotd::OnStateChange(int NewState, int OldState)
Clear();
}
void CMotd::OnWindowResize()
{
Graphics()->DeleteQuadContainer(m_RectQuadContainer);
TextRender()->DeleteTextContainer(m_TextContainerIndex);
}
void CMotd::OnRender()
{
if(!IsActive())
return;
float Width = 400 * 3.0f * Graphics()->ScreenAspect();
float Height = 400 * 3.0f;
const float FontSize = 32.0f; // also the size of the margin and rect rounding
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;
float w = 650.0f;
float x = Width / 2 - w / 2;
float y = 150.0f;
if(m_RectQuadContainer == -1)
{
Graphics()->SetColor(0.0f, 0.0f, 0.0f, 0.5f);
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)
@ -53,13 +89,13 @@ void CMotd::OnMessage(int MsgType, void *pRawMsg)
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
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
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"
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')
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)
m_ServerMotdTime = time() + time_freq() * g_Config.m_ClMotdTime;
m_ServerMotdTime = m_ServerMotdUpdateTime + time_freq() * g_Config.m_ClMotdTime;
else
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. */
#ifndef GAME_CLIENT_COMPONENTS_MOTD_H
#define GAME_CLIENT_COMPONENTS_MOTD_H
#include <engine/shared/config.h>
#include <game/client/component.h>
class CMotd : public CComponent
{
// motd
char m_aServerMotd[std::size(g_Config.m_SvMotd)];
int64_t m_ServerMotdTime;
int64_t m_ServerMotdUpdateTime;
int m_RectQuadContainer = -1;
int m_TextContainerIndex = -1;
public:
char m_aServerMotd[900];
CMotd();
virtual int Sizeof() const override { return sizeof(*this); }
const char *ServerMotd() const { return m_aServerMotd; }
int64_t ServerMotdUpdateTime() const { return m_ServerMotdUpdateTime; }
void Clear();
bool IsActive();
bool IsActive() const;
virtual void OnRender() override;
virtual void OnStateChange(int NewState, int OldState) override;
virtual void OnWindowResize() override;
virtual void OnMessage(int MsgType, void *pRawMsg) override;
virtual bool OnInput(IInput::CEvent Event) override;
};