1/* (c) Magnus Auvinen. See licence.txt in the root of the distribution for more information. */
2/* If you are missing that file, acquire a complete release at teeworlds.com. */
3
4#include <base/detect.h>
5#include <base/log.h>
6#include <base/math.h>
7#include <base/system.h>
8
9#include <engine/console.h>
10#include <engine/engine.h>
11#include <engine/gfx/image_loader.h>
12#include <engine/gfx/image_manipulation.h>
13#include <engine/graphics.h>
14#include <engine/shared/config.h>
15#include <engine/shared/jobs.h>
16#include <engine/storage.h>
17
18#include <generated/data_types.h>
19
20#include <game/localization.h>
21
22#if defined(CONF_VIDEORECORDER)
23#include <engine/shared/video.h>
24#endif
25
26#include "graphics_threaded.h"
27
28class CSemaphore;
29
30static CVideoMode g_aFakeModes[] = {
31 {.m_CanvasWidth: 8192, .m_CanvasHeight: 4320, .m_WindowWidth: 8192, .m_WindowHeight: 4320, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 7680, .m_CanvasHeight: 4320, .m_WindowWidth: 7680, .m_WindowHeight: 4320, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 5120, .m_CanvasHeight: 2880, .m_WindowWidth: 5120, .m_WindowHeight: 2880, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
32 {.m_CanvasWidth: 4096, .m_CanvasHeight: 2160, .m_WindowWidth: 4096, .m_WindowHeight: 2160, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 3840, .m_CanvasHeight: 2160, .m_WindowWidth: 3840, .m_WindowHeight: 2160, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 2560, .m_CanvasHeight: 1440, .m_WindowWidth: 2560, .m_WindowHeight: 1440, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
33 {.m_CanvasWidth: 2048, .m_CanvasHeight: 1536, .m_WindowWidth: 2048, .m_WindowHeight: 1536, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1920, .m_CanvasHeight: 2400, .m_WindowWidth: 1920, .m_WindowHeight: 2400, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1920, .m_CanvasHeight: 1440, .m_WindowWidth: 1920, .m_WindowHeight: 1440, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
34 {.m_CanvasWidth: 1920, .m_CanvasHeight: 1200, .m_WindowWidth: 1920, .m_WindowHeight: 1200, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1920, .m_CanvasHeight: 1080, .m_WindowWidth: 1920, .m_WindowHeight: 1080, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1856, .m_CanvasHeight: 1392, .m_WindowWidth: 1856, .m_WindowHeight: 1392, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
35 {.m_CanvasWidth: 1800, .m_CanvasHeight: 1440, .m_WindowWidth: 1800, .m_WindowHeight: 1440, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1792, .m_CanvasHeight: 1344, .m_WindowWidth: 1792, .m_WindowHeight: 1344, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1680, .m_CanvasHeight: 1050, .m_WindowWidth: 1680, .m_WindowHeight: 1050, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
36 {.m_CanvasWidth: 1600, .m_CanvasHeight: 1200, .m_WindowWidth: 1600, .m_WindowHeight: 1200, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1600, .m_CanvasHeight: 1000, .m_WindowWidth: 1600, .m_WindowHeight: 1000, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1440, .m_CanvasHeight: 1050, .m_WindowWidth: 1440, .m_WindowHeight: 1050, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
37 {.m_CanvasWidth: 1440, .m_CanvasHeight: 900, .m_WindowWidth: 1440, .m_WindowHeight: 900, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1400, .m_CanvasHeight: 1050, .m_WindowWidth: 1400, .m_WindowHeight: 1050, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1368, .m_CanvasHeight: 768, .m_WindowWidth: 1368, .m_WindowHeight: 768, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
38 {.m_CanvasWidth: 1280, .m_CanvasHeight: 1024, .m_WindowWidth: 1280, .m_WindowHeight: 1024, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1280, .m_CanvasHeight: 960, .m_WindowWidth: 1280, .m_WindowHeight: 960, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1280, .m_CanvasHeight: 800, .m_WindowWidth: 1280, .m_WindowHeight: 800, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
39 {.m_CanvasWidth: 1280, .m_CanvasHeight: 768, .m_WindowWidth: 1280, .m_WindowHeight: 768, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1152, .m_CanvasHeight: 864, .m_WindowWidth: 1152, .m_WindowHeight: 864, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 1024, .m_CanvasHeight: 768, .m_WindowWidth: 1024, .m_WindowHeight: 768, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
40 {.m_CanvasWidth: 1024, .m_CanvasHeight: 600, .m_WindowWidth: 1024, .m_WindowHeight: 600, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 800, .m_CanvasHeight: 600, .m_WindowWidth: 800, .m_WindowHeight: 600, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 768, .m_CanvasHeight: 576, .m_WindowWidth: 768, .m_WindowHeight: 576, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
41 {.m_CanvasWidth: 720, .m_CanvasHeight: 400, .m_WindowWidth: 720, .m_WindowHeight: 400, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 640, .m_CanvasHeight: 480, .m_WindowWidth: 640, .m_WindowHeight: 480, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0}, {.m_CanvasWidth: 400, .m_CanvasHeight: 300, .m_WindowWidth: 400, .m_WindowHeight: 300, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
42 {.m_CanvasWidth: 320, .m_CanvasHeight: 240, .m_WindowWidth: 320, .m_WindowHeight: 240, .m_RefreshRate: 0, .m_Red: 8, .m_Green: 8, .m_Blue: 8, .m_Format: 0},
43
44 {.m_CanvasWidth: 8192, .m_CanvasHeight: 4320, .m_WindowWidth: 8192, .m_WindowHeight: 4320, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 7680, .m_CanvasHeight: 4320, .m_WindowWidth: 7680, .m_WindowHeight: 4320, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 5120, .m_CanvasHeight: 2880, .m_WindowWidth: 5120, .m_WindowHeight: 2880, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
45 {.m_CanvasWidth: 4096, .m_CanvasHeight: 2160, .m_WindowWidth: 4096, .m_WindowHeight: 2160, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 3840, .m_CanvasHeight: 2160, .m_WindowWidth: 3840, .m_WindowHeight: 2160, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 2560, .m_CanvasHeight: 1440, .m_WindowWidth: 2560, .m_WindowHeight: 1440, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
46 {.m_CanvasWidth: 2048, .m_CanvasHeight: 1536, .m_WindowWidth: 2048, .m_WindowHeight: 1536, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1920, .m_CanvasHeight: 2400, .m_WindowWidth: 1920, .m_WindowHeight: 2400, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1920, .m_CanvasHeight: 1440, .m_WindowWidth: 1920, .m_WindowHeight: 1440, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
47 {.m_CanvasWidth: 1920, .m_CanvasHeight: 1200, .m_WindowWidth: 1920, .m_WindowHeight: 1200, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1920, .m_CanvasHeight: 1080, .m_WindowWidth: 1920, .m_WindowHeight: 1080, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1856, .m_CanvasHeight: 1392, .m_WindowWidth: 1856, .m_WindowHeight: 1392, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
48 {.m_CanvasWidth: 1800, .m_CanvasHeight: 1440, .m_WindowWidth: 1800, .m_WindowHeight: 1440, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1792, .m_CanvasHeight: 1344, .m_WindowWidth: 1792, .m_WindowHeight: 1344, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1680, .m_CanvasHeight: 1050, .m_WindowWidth: 1680, .m_WindowHeight: 1050, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
49 {.m_CanvasWidth: 1600, .m_CanvasHeight: 1200, .m_WindowWidth: 1600, .m_WindowHeight: 1200, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1600, .m_CanvasHeight: 1000, .m_WindowWidth: 1600, .m_WindowHeight: 1000, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1440, .m_CanvasHeight: 1050, .m_WindowWidth: 1440, .m_WindowHeight: 1050, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
50 {.m_CanvasWidth: 1440, .m_CanvasHeight: 900, .m_WindowWidth: 1440, .m_WindowHeight: 900, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1400, .m_CanvasHeight: 1050, .m_WindowWidth: 1400, .m_WindowHeight: 1050, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1368, .m_CanvasHeight: 768, .m_WindowWidth: 1368, .m_WindowHeight: 768, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
51 {.m_CanvasWidth: 1280, .m_CanvasHeight: 1024, .m_WindowWidth: 1280, .m_WindowHeight: 1024, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1280, .m_CanvasHeight: 960, .m_WindowWidth: 1280, .m_WindowHeight: 960, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1280, .m_CanvasHeight: 800, .m_WindowWidth: 1280, .m_WindowHeight: 800, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
52 {.m_CanvasWidth: 1280, .m_CanvasHeight: 768, .m_WindowWidth: 1280, .m_WindowHeight: 768, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1152, .m_CanvasHeight: 864, .m_WindowWidth: 1152, .m_WindowHeight: 864, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 1024, .m_CanvasHeight: 768, .m_WindowWidth: 1024, .m_WindowHeight: 768, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
53 {.m_CanvasWidth: 1024, .m_CanvasHeight: 600, .m_WindowWidth: 1024, .m_WindowHeight: 600, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 800, .m_CanvasHeight: 600, .m_WindowWidth: 800, .m_WindowHeight: 600, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 768, .m_CanvasHeight: 576, .m_WindowWidth: 768, .m_WindowHeight: 576, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
54 {.m_CanvasWidth: 720, .m_CanvasHeight: 400, .m_WindowWidth: 720, .m_WindowHeight: 400, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 640, .m_CanvasHeight: 480, .m_WindowWidth: 640, .m_WindowHeight: 480, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}, {.m_CanvasWidth: 400, .m_CanvasHeight: 300, .m_WindowWidth: 400, .m_WindowHeight: 300, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0},
55 {.m_CanvasWidth: 320, .m_CanvasHeight: 240, .m_WindowWidth: 320, .m_WindowHeight: 240, .m_RefreshRate: 0, .m_Red: 5, .m_Green: 6, .m_Blue: 5, .m_Format: 0}};
56
57void CGraphics_Threaded::FlushVertices(bool KeepVertices)
58{
59 CCommandBuffer::SCommand_Render Cmd;
60 EPrimitiveType PrimType;
61 size_t PrimCount, NumVerts;
62 FlushVerticesImpl(KeepVertices, PrimType, PrimCount, NumVerts, Command&: Cmd, VertSize: sizeof(CCommandBuffer::SVertex));
63
64 if(Cmd.m_pVertices != nullptr)
65 {
66 mem_copy(dest: Cmd.m_pVertices, source: m_aVertices, size: sizeof(CCommandBuffer::SVertex) * NumVerts);
67 }
68}
69
70void CGraphics_Threaded::FlushVerticesTex3D()
71{
72 CCommandBuffer::SCommand_RenderTex3D Cmd;
73 EPrimitiveType PrimType;
74 size_t PrimCount, NumVerts;
75 FlushVerticesImpl(KeepVertices: false, PrimType, PrimCount, NumVerts, Command&: Cmd, VertSize: sizeof(CCommandBuffer::SVertexTex3DStream));
76
77 if(Cmd.m_pVertices != nullptr)
78 {
79 mem_copy(dest: Cmd.m_pVertices, source: m_aVerticesTex3D, size: sizeof(CCommandBuffer::SVertexTex3DStream) * NumVerts);
80 }
81}
82
83void CGraphics_Threaded::AddVertices(int Count)
84{
85 m_NumVertices += Count;
86 if((m_NumVertices + Count) >= CCommandBuffer::MAX_VERTICES)
87 FlushVertices();
88}
89
90void CGraphics_Threaded::AddVertices(int Count, CCommandBuffer::SVertex *pVertices)
91{
92 AddVertices(Count);
93}
94
95void CGraphics_Threaded::AddVertices(int Count, CCommandBuffer::SVertexTex3DStream *pVertices)
96{
97 m_NumVertices += Count;
98 if((m_NumVertices + Count) >= CCommandBuffer::MAX_VERTICES)
99 FlushVerticesTex3D();
100}
101
102CGraphics_Threaded::CGraphics_Threaded()
103{
104 m_State.m_ScreenTL.x = 0;
105 m_State.m_ScreenTL.y = 0;
106 m_State.m_ScreenBR.x = 0;
107 m_State.m_ScreenBR.y = 0;
108 m_State.m_ClipEnable = false;
109 m_State.m_ClipX = 0;
110 m_State.m_ClipY = 0;
111 m_State.m_ClipW = 0;
112 m_State.m_ClipH = 0;
113 m_State.m_Texture = -1;
114 m_State.m_BlendMode = EBlendMode::NONE;
115 m_State.m_WrapMode = EWrapMode::REPEAT;
116
117 m_CurrentCommandBuffer = 0;
118 m_pCommandBuffer = nullptr;
119 m_apCommandBuffers[0] = nullptr;
120 m_apCommandBuffers[1] = nullptr;
121
122 m_NumVertices = 0;
123
124 m_ScreenWidth = -1;
125 m_ScreenHeight = -1;
126 m_ScreenRefreshRate = -1;
127
128 m_Rotation = 0;
129 m_Drawing = EDrawing::NONE;
130
131 m_TextureMemoryUsage = 0;
132
133 m_RenderEnable = true;
134 m_DoScreenshot = false;
135}
136
137void CGraphics_Threaded::ClipEnable(int x, int y, int w, int h)
138{
139 if(x < 0)
140 w += x;
141 if(y < 0)
142 h += y;
143
144 x = std::clamp(val: x, lo: 0, hi: ScreenWidth());
145 y = std::clamp(val: y, lo: 0, hi: ScreenHeight());
146 w = std::clamp(val: w, lo: 0, hi: ScreenWidth() - x);
147 h = std::clamp(val: h, lo: 0, hi: ScreenHeight() - y);
148
149 m_State.m_ClipEnable = true;
150 m_State.m_ClipX = x;
151 m_State.m_ClipY = ScreenHeight() - (y + h);
152 m_State.m_ClipW = w;
153 m_State.m_ClipH = h;
154}
155
156void CGraphics_Threaded::ClipDisable()
157{
158 m_State.m_ClipEnable = false;
159}
160
161void CGraphics_Threaded::BlendNone()
162{
163 m_State.m_BlendMode = EBlendMode::NONE;
164}
165
166void CGraphics_Threaded::BlendNormal()
167{
168 m_State.m_BlendMode = EBlendMode::ALPHA;
169}
170
171void CGraphics_Threaded::BlendAdditive()
172{
173 m_State.m_BlendMode = EBlendMode::ADDITIVE;
174}
175
176void CGraphics_Threaded::WrapNormal()
177{
178 m_State.m_WrapMode = EWrapMode::REPEAT;
179}
180
181void CGraphics_Threaded::WrapClamp()
182{
183 m_State.m_WrapMode = EWrapMode::CLAMP;
184}
185
186uint64_t CGraphics_Threaded::TextureMemoryUsage() const
187{
188 return m_pBackend->TextureMemoryUsage();
189}
190
191uint64_t CGraphics_Threaded::BufferMemoryUsage() const
192{
193 return m_pBackend->BufferMemoryUsage();
194}
195
196uint64_t CGraphics_Threaded::StreamedMemoryUsage() const
197{
198 return m_pBackend->StreamedMemoryUsage();
199}
200
201uint64_t CGraphics_Threaded::StagingMemoryUsage() const
202{
203 return m_pBackend->StagingMemoryUsage();
204}
205
206const TTwGraphicsGpuList &CGraphics_Threaded::GetGpus() const
207{
208 return m_pBackend->GetGpus();
209}
210
211void CGraphics_Threaded::MapScreen(float TopLeftX, float TopLeftY, float BottomRightX, float BottomRightY)
212{
213 m_State.m_ScreenTL.x = TopLeftX;
214 m_State.m_ScreenTL.y = TopLeftY;
215 m_State.m_ScreenBR.x = BottomRightX;
216 m_State.m_ScreenBR.y = BottomRightY;
217}
218
219void CGraphics_Threaded::GetScreen(float *pTopLeftX, float *pTopLeftY, float *pBottomRightX, float *pBottomRightY) const
220{
221 *pTopLeftX = m_State.m_ScreenTL.x;
222 *pTopLeftY = m_State.m_ScreenTL.y;
223 *pBottomRightX = m_State.m_ScreenBR.x;
224 *pBottomRightY = m_State.m_ScreenBR.y;
225}
226
227void CGraphics_Threaded::LinesBegin()
228{
229 dbg_assert(m_Drawing == EDrawing::NONE, "called Graphics()->LinesBegin twice");
230 m_Drawing = EDrawing::LINES;
231 SetColor(r: 1, g: 1, b: 1, a: 1);
232}
233
234void CGraphics_Threaded::LinesEnd()
235{
236 dbg_assert(m_Drawing == EDrawing::LINES, "called Graphics()->LinesEnd without begin");
237 FlushVertices();
238 m_Drawing = EDrawing::NONE;
239}
240
241void CGraphics_Threaded::LinesDraw(const CLineItem *pArray, size_t Num)
242{
243 dbg_assert(m_Drawing == EDrawing::LINES, "called Graphics()->LinesDraw without begin");
244
245 size_t VertexIndex = m_NumVertices;
246 for(size_t i = 0; i < Num; ++i)
247 {
248 m_aVertices[VertexIndex].m_Pos.x = pArray[i].m_X0;
249 m_aVertices[VertexIndex].m_Pos.y = pArray[i].m_Y0;
250 m_aVertices[VertexIndex].m_Tex = m_aTexture[0];
251 SetColor(pVertex: &m_aVertices[VertexIndex], ColorIndex: 0);
252 ++VertexIndex;
253
254 m_aVertices[VertexIndex].m_Pos.x = pArray[i].m_X1;
255 m_aVertices[VertexIndex].m_Pos.y = pArray[i].m_Y1;
256 m_aVertices[VertexIndex].m_Tex = m_aTexture[1];
257 SetColor(pVertex: &m_aVertices[VertexIndex], ColorIndex: 1);
258 ++VertexIndex;
259 }
260
261 AddVertices(Count: 2 * Num);
262}
263
264void CGraphics_Threaded::LinesBatchBegin(CLineItemBatch *pBatch)
265{
266 pBatch->m_NumItems = 0;
267 LinesBegin();
268}
269
270void CGraphics_Threaded::LinesBatchEnd(CLineItemBatch *pBatch)
271{
272 if(pBatch->m_NumItems > 0)
273 {
274 LinesDraw(pArray: pBatch->m_aItems, Num: pBatch->m_NumItems);
275 pBatch->m_NumItems = 0;
276 }
277 LinesEnd();
278}
279
280void CGraphics_Threaded::LinesBatchDraw(CLineItemBatch *pBatch, const CLineItem *pArray, size_t Num)
281{
282 if(pBatch->m_NumItems + Num >= std::size(pBatch->m_aItems))
283 {
284 LinesDraw(pArray: pBatch->m_aItems, Num: pBatch->m_NumItems);
285 pBatch->m_NumItems = 0;
286 }
287 if(Num >= std::size(pBatch->m_aItems))
288 {
289 LinesDraw(pArray, Num);
290 return;
291 }
292 std::copy(first: pArray, last: pArray + Num, result: pBatch->m_aItems + pBatch->m_NumItems);
293 pBatch->m_NumItems += Num;
294}
295
296IGraphics::CTextureHandle CGraphics_Threaded::FindFreeTextureIndex()
297{
298 const size_t CurSize = m_vTextureIndices.size();
299 if(m_FirstFreeTexture == CurSize)
300 {
301 m_vTextureIndices.resize(new_size: CurSize * 2);
302 for(size_t i = 0; i < CurSize; ++i)
303 m_vTextureIndices[CurSize + i] = CurSize + i + 1;
304 }
305 const size_t Tex = m_FirstFreeTexture;
306 m_FirstFreeTexture = m_vTextureIndices[Tex];
307 m_vTextureIndices[Tex] = -1;
308 return CreateTextureHandle(Index: Tex);
309}
310
311void CGraphics_Threaded::FreeTextureIndex(CTextureHandle *pIndex)
312{
313 dbg_assert(pIndex->IsValid(), "Cannot free invalid texture index");
314 dbg_assert(m_vTextureIndices[pIndex->Id()] == -1, "Cannot free already freed texture index");
315
316 m_vTextureIndices[pIndex->Id()] = m_FirstFreeTexture;
317 m_FirstFreeTexture = pIndex->Id();
318 pIndex->Invalidate();
319}
320
321void CGraphics_Threaded::UnloadTexture(CTextureHandle *pIndex)
322{
323 if(pIndex->IsNullTexture() || !pIndex->IsValid())
324 return;
325
326 CCommandBuffer::SCommand_Texture_Destroy Cmd;
327 Cmd.m_Slot = pIndex->Id();
328 AddCmd(Cmd);
329
330 FreeTextureIndex(pIndex);
331}
332
333IGraphics::CTextureHandle CGraphics_Threaded::LoadSpriteTexture(const CImageInfo &FromImageInfo, const CDataSprite *pSprite)
334{
335 int ImageGridX = FromImageInfo.m_Width / pSprite->m_pSet->m_Gridx;
336 int ImageGridY = FromImageInfo.m_Height / pSprite->m_pSet->m_Gridy;
337 int x = pSprite->m_X * ImageGridX;
338 int y = pSprite->m_Y * ImageGridY;
339 int w = pSprite->m_W * ImageGridX;
340 int h = pSprite->m_H * ImageGridY;
341
342 CImageInfo SpriteInfo;
343 SpriteInfo.m_Width = w;
344 SpriteInfo.m_Height = h;
345 SpriteInfo.m_Format = FromImageInfo.m_Format;
346 SpriteInfo.m_pData = static_cast<uint8_t *>(malloc(size: SpriteInfo.DataSize()));
347 SpriteInfo.CopyRectFrom(SrcImage: FromImageInfo, SrcX: x, SrcY: y, Width: w, Height: h, DestX: 0, DestY: 0);
348 return LoadTextureRawMove(Image&: SpriteInfo, Flags: 0, pTexName: pSprite->m_pName);
349}
350
351bool CGraphics_Threaded::IsImageSubFullyTransparent(const CImageInfo &FromImageInfo, int x, int y, int w, int h)
352{
353 if(FromImageInfo.m_Format == CImageInfo::FORMAT_R || FromImageInfo.m_Format == CImageInfo::FORMAT_RA || FromImageInfo.m_Format == CImageInfo::FORMAT_RGBA)
354 {
355 const uint8_t *pImgData = FromImageInfo.m_pData;
356 const size_t PixelSize = FromImageInfo.PixelSize();
357 for(int iy = 0; iy < h; ++iy)
358 {
359 for(int ix = 0; ix < w; ++ix)
360 {
361 const size_t RealOffset = (x + ix) * PixelSize + (y + iy) * PixelSize * FromImageInfo.m_Width;
362 if(pImgData[RealOffset + (PixelSize - 1)] > 0)
363 return false;
364 }
365 }
366
367 return true;
368 }
369 return false;
370}
371
372bool CGraphics_Threaded::IsSpriteTextureFullyTransparent(const CImageInfo &FromImageInfo, const CDataSprite *pSprite)
373{
374 int ImageGridX = FromImageInfo.m_Width / pSprite->m_pSet->m_Gridx;
375 int ImageGridY = FromImageInfo.m_Height / pSprite->m_pSet->m_Gridy;
376 int x = pSprite->m_X * ImageGridX;
377 int y = pSprite->m_Y * ImageGridY;
378 int w = pSprite->m_W * ImageGridX;
379 int h = pSprite->m_H * ImageGridY;
380 return IsImageSubFullyTransparent(FromImageInfo, x, y, w, h);
381}
382
383void CGraphics_Threaded::LoadTextureAddWarning(size_t Width, size_t Height, int Flags, const char *pTexName)
384{
385 if((Flags & IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE) != 0 || (Flags & IGraphics::TEXLOAD_TO_3D_TEXTURE) != 0)
386 {
387 if(Width == 0 || (Width % 16) != 0 || Height == 0 || (Height % 16) != 0)
388 {
389 SWarning NewWarning;
390 char aText[128];
391 str_format(buffer: aText, buffer_size: sizeof(aText), format: "\"%s\"", pTexName ? pTexName : "(no name)");
392 str_format(buffer: NewWarning.m_aWarningMsg, buffer_size: sizeof(NewWarning.m_aWarningMsg), format: Localize(pStr: "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);
393 AddWarning(Warning: NewWarning);
394 }
395 }
396}
397
398static CCommandBuffer::SCommand_Texture_Create LoadTextureCreateCommand(int TextureId, size_t Width, size_t Height, int Flags)
399{
400 CCommandBuffer::SCommand_Texture_Create Cmd;
401 Cmd.m_Slot = TextureId;
402 Cmd.m_Width = Width;
403 Cmd.m_Height = Height;
404
405 Cmd.m_Flags = 0;
406 if((Flags & IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE) != 0)
407 Cmd.m_Flags |= TextureFlag::TO_2D_ARRAY_TEXTURE;
408 if((Flags & IGraphics::TEXLOAD_TO_3D_TEXTURE) != 0)
409 Cmd.m_Flags |= TextureFlag::TO_3D_TEXTURE;
410 if((Flags & IGraphics::TEXLOAD_NO_2D_TEXTURE) != 0)
411 Cmd.m_Flags |= TextureFlag::NO_2D_TEXTURE;
412
413 return Cmd;
414}
415
416IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRaw(const CImageInfo &Image, int Flags, const char *pTexName)
417{
418 LoadTextureAddWarning(Width: Image.m_Width, Height: Image.m_Height, Flags, pTexName);
419
420 if(Image.m_Width == 0 || Image.m_Height == 0)
421 return IGraphics::CTextureHandle();
422
423 IGraphics::CTextureHandle TextureHandle = FindFreeTextureIndex();
424 CCommandBuffer::SCommand_Texture_Create Cmd = LoadTextureCreateCommand(TextureId: TextureHandle.Id(), Width: Image.m_Width, Height: Image.m_Height, Flags);
425
426 // Copy texture data and convert if necessary
427 uint8_t *pTmpData;
428 if(!ConvertToRgbaAlloc(pDest&: pTmpData, SourceImage: Image))
429 {
430 dbg_msg(sys: "graphics", fmt: "converted image '%s' to RGBA, consider making its file format RGBA", pTexName ? pTexName : "(no name)");
431 }
432 Cmd.m_pData = pTmpData;
433
434 AddCmd(Cmd);
435
436 return TextureHandle;
437}
438
439IGraphics::CTextureHandle CGraphics_Threaded::LoadTextureRawMove(CImageInfo &Image, int Flags, const char *pTexName)
440{
441 if(Image.m_Format != CImageInfo::FORMAT_RGBA)
442 {
443 // Moving not possible, texture needs to be converted
444 IGraphics::CTextureHandle TextureHandle = LoadTextureRaw(Image, Flags, pTexName);
445 Image.Free();
446 return TextureHandle;
447 }
448
449 LoadTextureAddWarning(Width: Image.m_Width, Height: Image.m_Height, Flags, pTexName);
450
451 if(Image.m_Width == 0 || Image.m_Height == 0)
452 return IGraphics::CTextureHandle();
453
454 IGraphics::CTextureHandle TextureHandle = FindFreeTextureIndex();
455 CCommandBuffer::SCommand_Texture_Create Cmd = LoadTextureCreateCommand(TextureId: TextureHandle.Id(), Width: Image.m_Width, Height: Image.m_Height, Flags);
456 Cmd.m_pData = Image.m_pData;
457 Image.m_pData = nullptr;
458 Image.Free();
459 AddCmd(Cmd);
460
461 return TextureHandle;
462}
463
464IGraphics::CTextureHandle CGraphics_Threaded::LoadTexture(const char *pFilename, int StorageType, int Flags)
465{
466 dbg_assert(pFilename[0] != '\0', "Cannot load texture from file with empty filename"); // would cause Valgrind to crash otherwise
467
468 CImageInfo Image;
469 if(LoadPng(Image, pFilename, StorageType))
470 {
471 CTextureHandle Id = LoadTextureRawMove(Image, Flags, pTexName: pFilename);
472 if(Id.IsValid())
473 {
474 if(g_Config.m_Debug)
475 dbg_msg(sys: "graphics/texture", fmt: "loaded %s", pFilename);
476 return Id;
477 }
478 }
479
480 return m_NullTexture;
481}
482
483bool CGraphics_Threaded::LoadTextTextures(size_t Width, size_t Height, CTextureHandle &TextTexture, CTextureHandle &TextOutlineTexture, uint8_t *pTextData, uint8_t *pTextOutlineData)
484{
485 if(Width == 0 || Height == 0)
486 return false;
487
488 TextTexture = FindFreeTextureIndex();
489 TextOutlineTexture = FindFreeTextureIndex();
490
491 CCommandBuffer::SCommand_TextTextures_Create Cmd;
492 Cmd.m_Slot = TextTexture.Id();
493 Cmd.m_SlotOutline = TextOutlineTexture.Id();
494 Cmd.m_Width = Width;
495 Cmd.m_Height = Height;
496 Cmd.m_pTextData = pTextData;
497 Cmd.m_pTextOutlineData = pTextOutlineData;
498 AddCmd(Cmd);
499
500 return true;
501}
502
503bool CGraphics_Threaded::UnloadTextTextures(CTextureHandle &TextTexture, CTextureHandle &TextOutlineTexture)
504{
505 CCommandBuffer::SCommand_TextTextures_Destroy Cmd;
506 Cmd.m_Slot = TextTexture.Id();
507 Cmd.m_SlotOutline = TextOutlineTexture.Id();
508 AddCmd(Cmd);
509
510 if(TextTexture.IsValid())
511 FreeTextureIndex(pIndex: &TextTexture);
512 if(TextOutlineTexture.IsValid())
513 FreeTextureIndex(pIndex: &TextOutlineTexture);
514 return true;
515}
516
517bool CGraphics_Threaded::UpdateTextTexture(CTextureHandle TextureId, int x, int y, size_t Width, size_t Height, uint8_t *pData, bool IsMovedPointer)
518{
519 CCommandBuffer::SCommand_TextTexture_Update Cmd;
520 Cmd.m_Slot = TextureId.Id();
521 Cmd.m_X = x;
522 Cmd.m_Y = y;
523 Cmd.m_Width = Width;
524 Cmd.m_Height = Height;
525
526 if(IsMovedPointer)
527 {
528 Cmd.m_pData = pData;
529 }
530 else
531 {
532 const size_t MemSize = Width * Height;
533 uint8_t *pTmpData = static_cast<uint8_t *>(malloc(size: MemSize));
534 mem_copy(dest: pTmpData, source: pData, size: MemSize);
535 Cmd.m_pData = pTmpData;
536 }
537 AddCmd(Cmd);
538
539 return true;
540}
541
542static SWarning FormatPngliteIncompatibilityWarning(int PngliteIncompatible, const char *pContextName)
543{
544 SWarning Warning;
545 str_format(buffer: Warning.m_aWarningMsg, buffer_size: sizeof(Warning.m_aWarningMsg), format: Localize(pStr: "\"%s\" is not compatible with pnglite and cannot be loaded by old DDNet versions:"), pContextName);
546 str_append(dst&: Warning.m_aWarningMsg, src: " ");
547 static const int FLAGS[] = {CImageLoader::PNGLITE_COLOR_TYPE, CImageLoader::PNGLITE_BIT_DEPTH, CImageLoader::PNGLITE_INTERLACE_TYPE, CImageLoader::PNGLITE_COMPRESSION_TYPE, CImageLoader::PNGLITE_FILTER_TYPE};
548 static const char *const EXPLANATION[] = {"color type", "bit depth", "interlace type", "compression type", "filter type"};
549
550 bool First = true;
551 for(size_t i = 0; i < std::size(FLAGS); ++i)
552 {
553 if((PngliteIncompatible & FLAGS[i]) != 0)
554 {
555 if(!First)
556 {
557 str_append(dst&: Warning.m_aWarningMsg, src: ", ");
558 }
559 str_append(dst&: Warning.m_aWarningMsg, src: EXPLANATION[i]);
560 First = false;
561 }
562 }
563 str_append(dst&: Warning.m_aWarningMsg, src: " unsupported");
564 return Warning;
565}
566
567bool CGraphics_Threaded::LoadPng(CImageInfo &Image, const char *pFilename, int StorageType)
568{
569 IOHANDLE File = m_pStorage->OpenFile(pFilename, Flags: IOFLAG_READ, Type: StorageType);
570
571 int PngliteIncompatible;
572 if(!CImageLoader::LoadPng(File, pFilename, Image, PngliteIncompatible))
573 return false;
574
575 if(m_WarnPngliteIncompatibleImages && PngliteIncompatible != 0)
576 {
577 AddWarning(Warning: FormatPngliteIncompatibilityWarning(PngliteIncompatible, pContextName: pFilename));
578 }
579
580 return true;
581}
582
583bool CGraphics_Threaded::LoadPng(CImageInfo &Image, const uint8_t *pData, size_t DataSize, const char *pContextName)
584{
585 CByteBufferReader Reader(pData, DataSize);
586 int PngliteIncompatible;
587 if(!CImageLoader::LoadPng(Reader, pContextName, Image, PngliteIncompatible))
588 return false;
589
590 if(m_WarnPngliteIncompatibleImages && PngliteIncompatible != 0)
591 {
592 AddWarning(Warning: FormatPngliteIncompatibilityWarning(PngliteIncompatible, pContextName));
593 }
594
595 return true;
596}
597
598bool CGraphics_Threaded::CheckImageDivisibility(const char *pContextName, CImageInfo &Image, int DivX, int DivY, bool AllowResize)
599{
600 dbg_assert(DivX != 0 && DivY != 0, "Passing 0 to this function is not allowed.");
601 bool ImageIsValid = true;
602 bool WidthBroken = Image.m_Width == 0 || (Image.m_Width % DivX) != 0;
603 bool HeightBroken = Image.m_Height == 0 || (Image.m_Height % DivY) != 0;
604 if(WidthBroken || HeightBroken)
605 {
606 SWarning NewWarning;
607 char aContextNameQuoted[128];
608 str_format(buffer: aContextNameQuoted, buffer_size: sizeof(aContextNameQuoted), format: "\"%s\"", pContextName);
609 str_format(buffer: NewWarning.m_aWarningMsg, buffer_size: sizeof(NewWarning.m_aWarningMsg),
610 format: Localize(pStr: "The width of texture %s is not divisible by %d, or the height is not divisible by %d, which might cause visual bugs."), aContextNameQuoted, DivX, DivY);
611 AddWarning(Warning: NewWarning);
612 ImageIsValid = false;
613 }
614
615 if(AllowResize && !ImageIsValid && Image.m_Width > 0 && Image.m_Height > 0)
616 {
617 int NewWidth = DivX;
618 int NewHeight = DivY;
619 if(WidthBroken)
620 {
621 NewWidth = maximum<int>(a: HighestBit(OfVar: Image.m_Width), b: DivX);
622 NewHeight = (NewWidth / DivX) * DivY;
623 }
624 else
625 {
626 NewHeight = maximum<int>(a: HighestBit(OfVar: Image.m_Height), b: DivY);
627 NewWidth = (NewHeight / DivY) * DivX;
628 }
629 ResizeImage(Image, NewWidth, NewHeight);
630 ImageIsValid = true;
631 }
632
633 return ImageIsValid;
634}
635
636bool CGraphics_Threaded::IsImageFormatRgba(const char *pContextName, const CImageInfo &Image)
637{
638 if(Image.m_Format != CImageInfo::FORMAT_RGBA)
639 {
640 SWarning NewWarning;
641 char aContextNameQuoted[128];
642 str_format(buffer: aContextNameQuoted, buffer_size: sizeof(aContextNameQuoted), format: "\"%s\"", pContextName);
643 str_format(buffer: NewWarning.m_aWarningMsg, buffer_size: sizeof(NewWarning.m_aWarningMsg),
644 format: Localize(pStr: "The format of texture %s is not RGBA which will cause visual bugs."), aContextNameQuoted);
645 AddWarning(Warning: NewWarning);
646 return false;
647 }
648 return true;
649}
650
651void CGraphics_Threaded::KickCommandBuffer()
652{
653 m_pBackend->RunBuffer(pBuffer: m_pCommandBuffer);
654
655 std::vector<std::string> WarningStrings;
656 if(m_pBackend->GetWarning(WarningStrings))
657 {
658 SWarning NewWarning;
659 std::string WarningStr;
660 for(const auto &WarnStr : WarningStrings)
661 WarningStr.append(str: (WarnStr + "\n"));
662 str_copy(dst&: NewWarning.m_aWarningMsg, src: WarningStr.c_str());
663 AddWarning(Warning: NewWarning);
664 }
665
666 // swap buffer
667 m_CurrentCommandBuffer ^= 1;
668 m_pCommandBuffer = m_apCommandBuffers[m_CurrentCommandBuffer];
669 m_pCommandBuffer->Reset();
670}
671
672class CScreenshotSaveJob : public IJob
673{
674 IStorage *m_pStorage;
675 IConsole *m_pConsole;
676 char m_aName[IO_MAX_PATH_LENGTH];
677 CImageInfo m_Image;
678
679 void Run() override
680 {
681 char aWholePath[IO_MAX_PATH_LENGTH];
682 char aBuf[64 + IO_MAX_PATH_LENGTH];
683 if(CImageLoader::SavePng(File: m_pStorage->OpenFile(pFilename: m_aName, Flags: IOFLAG_WRITE, Type: IStorage::TYPE_SAVE, pBuffer: aWholePath, BufferSize: sizeof(aWholePath)), pFilename: m_aName, Image: m_Image))
684 {
685 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "saved screenshot to '%s'", aWholePath);
686 }
687 else
688 {
689 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "failed to save screenshot to '%s'", aWholePath);
690 }
691 m_pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "client", pStr: aBuf, PrintColor: ColorRGBA(1.0f, 0.6f, 0.3f, 1.0f));
692 }
693
694public:
695 CScreenshotSaveJob(IStorage *pStorage, IConsole *pConsole, const char *pName, CImageInfo &&Image) :
696 m_pStorage(pStorage),
697 m_pConsole(pConsole),
698 m_Image(std::move(Image))
699 {
700 str_copy(dst&: m_aName, src: pName);
701 }
702
703 ~CScreenshotSaveJob() override
704 {
705 m_Image.Free();
706 }
707};
708
709void CGraphics_Threaded::ScreenshotDirect(bool *pSwapped)
710{
711 if(!m_DoScreenshot)
712 return;
713 m_DoScreenshot = false;
714 if(!WindowActive())
715 return;
716
717 CImageInfo Image;
718
719 CCommandBuffer::SCommand_TrySwapAndScreenshot Cmd;
720 Cmd.m_pImage = &Image;
721 Cmd.m_pSwapped = pSwapped;
722 AddCmd(Cmd);
723
724 KickCommandBuffer();
725 WaitForIdle();
726
727 if(Image.m_pData)
728 {
729 m_pEngine->AddJob(pJob: std::make_shared<CScreenshotSaveJob>(args&: m_pStorage, args&: m_pConsole, args&: m_aScreenshotName, args: std::move(Image)));
730 }
731}
732
733void CGraphics_Threaded::TextureSet(CTextureHandle TextureId)
734{
735 dbg_assert(m_Drawing == EDrawing::NONE, "called Graphics()->TextureSet within begin");
736 dbg_assert(!TextureId.IsValid() || m_vTextureIndices[TextureId.Id()] == -1, "Texture handle was not invalid, but also did not correlate to an existing texture.");
737 m_State.m_Texture = TextureId.Id();
738}
739
740void CGraphics_Threaded::Clear(float r, float g, float b, bool ForceClearNow)
741{
742 CCommandBuffer::SCommand_Clear Cmd;
743 Cmd.m_Color.r = r;
744 Cmd.m_Color.g = g;
745 Cmd.m_Color.b = b;
746 Cmd.m_Color.a = 0;
747 Cmd.m_ForceClear = ForceClearNow;
748 AddCmd(Cmd);
749}
750
751void CGraphics_Threaded::QuadsBegin()
752{
753 dbg_assert(m_Drawing == EDrawing::NONE, "called Graphics()->QuadsBegin twice");
754 m_Drawing = EDrawing::QUADS;
755
756 QuadsSetSubset(TlU: 0, TlV: 0, BrU: 1, BrV: 1);
757 QuadsSetRotation(Angle: 0);
758 SetColor(r: 1, g: 1, b: 1, a: 1);
759}
760
761void CGraphics_Threaded::QuadsEnd()
762{
763 dbg_assert(m_Drawing == EDrawing::QUADS, "called Graphics()->QuadsEnd without begin");
764 FlushVertices();
765 m_Drawing = EDrawing::NONE;
766}
767
768void CGraphics_Threaded::QuadsTex3DBegin()
769{
770 QuadsBegin();
771}
772
773void CGraphics_Threaded::QuadsTex3DEnd()
774{
775 dbg_assert(m_Drawing == EDrawing::QUADS, "called Graphics()->QuadsEnd without begin");
776 FlushVerticesTex3D();
777 m_Drawing = EDrawing::NONE;
778}
779
780void CGraphics_Threaded::TrianglesBegin()
781{
782 dbg_assert(m_Drawing == EDrawing::NONE, "called Graphics()->TrianglesBegin twice");
783 m_Drawing = EDrawing::TRIANGLES;
784
785 QuadsSetSubset(TlU: 0, TlV: 0, BrU: 1, BrV: 1);
786 QuadsSetRotation(Angle: 0);
787 SetColor(r: 1, g: 1, b: 1, a: 1);
788}
789
790void CGraphics_Threaded::TrianglesEnd()
791{
792 dbg_assert(m_Drawing == EDrawing::TRIANGLES, "called Graphics()->TrianglesEnd without begin");
793 FlushVertices();
794 m_Drawing = EDrawing::NONE;
795}
796
797void CGraphics_Threaded::QuadsEndKeepVertices()
798{
799 dbg_assert(m_Drawing == EDrawing::QUADS, "called Graphics()->QuadsEndKeepVertices without begin");
800 FlushVertices(KeepVertices: true);
801 m_Drawing = EDrawing::NONE;
802}
803
804void CGraphics_Threaded::QuadsDrawCurrentVertices(bool KeepVertices)
805{
806 m_Drawing = EDrawing::QUADS;
807 FlushVertices(KeepVertices);
808 m_Drawing = EDrawing::NONE;
809}
810
811void CGraphics_Threaded::QuadsSetRotation(float Angle)
812{
813 m_Rotation = Angle;
814}
815
816static unsigned char NormalizeColorComponent(float ColorComponent)
817{
818 return (unsigned char)(std::clamp(val: ColorComponent, lo: 0.0f, hi: 1.0f) * 255.0f + 0.5f); // +0.5f to round to nearest
819}
820
821void CGraphics_Threaded::SetColorVertex(const CColorVertex *pArray, size_t Num)
822{
823 dbg_assert(m_Drawing != EDrawing::NONE, "called Graphics()->SetColorVertex without begin");
824
825 for(size_t i = 0; i < Num; ++i)
826 {
827 const CColorVertex &Vertex = pArray[i];
828 CCommandBuffer::SColor &Color = m_aColor[Vertex.m_Index];
829 Color.r = NormalizeColorComponent(ColorComponent: Vertex.m_R);
830 Color.g = NormalizeColorComponent(ColorComponent: Vertex.m_G);
831 Color.b = NormalizeColorComponent(ColorComponent: Vertex.m_B);
832 Color.a = NormalizeColorComponent(ColorComponent: Vertex.m_A);
833 }
834}
835
836void CGraphics_Threaded::SetColor(float r, float g, float b, float a)
837{
838 CCommandBuffer::SColor NewColor;
839 NewColor.r = NormalizeColorComponent(ColorComponent: r);
840 NewColor.g = NormalizeColorComponent(ColorComponent: g);
841 NewColor.b = NormalizeColorComponent(ColorComponent: b);
842 NewColor.a = NormalizeColorComponent(ColorComponent: a);
843 std::fill(first: std::begin(arr&: m_aColor), last: std::end(arr&: m_aColor), value: NewColor);
844}
845
846void CGraphics_Threaded::SetColor(ColorRGBA Color)
847{
848 SetColor(r: Color.r, g: Color.g, b: Color.b, a: Color.a);
849}
850
851void CGraphics_Threaded::SetColor4(ColorRGBA TopLeft, ColorRGBA TopRight, ColorRGBA BottomLeft, ColorRGBA BottomRight)
852{
853 CColorVertex aArray[] = {
854 CColorVertex(0, TopLeft),
855 CColorVertex(1, TopRight),
856 CColorVertex(2, BottomRight),
857 CColorVertex(3, BottomLeft)};
858 SetColorVertex(pArray: aArray, Num: std::size(aArray));
859}
860
861void CGraphics_Threaded::ChangeColorOfCurrentQuadVertices(float r, float g, float b, float a)
862{
863 m_aColor[0].r = NormalizeColorComponent(ColorComponent: r);
864 m_aColor[0].g = NormalizeColorComponent(ColorComponent: g);
865 m_aColor[0].b = NormalizeColorComponent(ColorComponent: b);
866 m_aColor[0].a = NormalizeColorComponent(ColorComponent: a);
867
868 for(int i = 0; i < m_NumVertices; ++i)
869 {
870 SetColor(pVertex: &m_aVertices[i], ColorIndex: 0);
871 }
872}
873
874void CGraphics_Threaded::ChangeColorOfQuadVertices(size_t QuadOffset, unsigned char r, unsigned char g, unsigned char b, unsigned char a)
875{
876 const CCommandBuffer::SColor Color(r, g, b, a);
877 const size_t VertNum = g_Config.m_GfxQuadAsTriangle && !m_GLUseTrianglesAsQuad ? 6 : 4;
878 for(size_t i = 0; i < VertNum; ++i)
879 {
880 m_aVertices[QuadOffset * VertNum + i].m_Color = Color;
881 }
882}
883
884void CGraphics_Threaded::QuadsSetSubset(float TlU, float TlV, float BrU, float BrV)
885{
886 m_aTexture[0].u = TlU;
887 m_aTexture[1].u = BrU;
888 m_aTexture[0].v = TlV;
889 m_aTexture[1].v = TlV;
890
891 m_aTexture[3].u = TlU;
892 m_aTexture[2].u = BrU;
893 m_aTexture[3].v = BrV;
894 m_aTexture[2].v = BrV;
895}
896
897void CGraphics_Threaded::QuadsSetSubsetFree(
898 float x0, float y0, float x1, float y1,
899 float x2, float y2, float x3, float y3, int Index)
900{
901 m_aTexture[0].u = x0;
902 m_aTexture[0].v = y0;
903 m_aTexture[1].u = x1;
904 m_aTexture[1].v = y1;
905 m_aTexture[2].u = x2;
906 m_aTexture[2].v = y2;
907 m_aTexture[3].u = x3;
908 m_aTexture[3].v = y3;
909 m_CurIndex = Index;
910}
911
912void CGraphics_Threaded::QuadsDraw(CQuadItem *pArray, int Num)
913{
914 for(int i = 0; i < Num; ++i)
915 {
916 pArray[i].m_X -= pArray[i].m_Width / 2;
917 pArray[i].m_Y -= pArray[i].m_Height / 2;
918 }
919
920 QuadsDrawTL(pArray, Num);
921}
922
923void CGraphics_Threaded::QuadsDrawTL(const CQuadItem *pArray, int Num)
924{
925 QuadsDrawTLImpl(pVertices: m_aVertices, pArray, Num);
926}
927
928void CGraphics_Threaded::QuadsTex3DDrawTL(const CQuadItem *pArray, int Num)
929{
930 const int VertNum = g_Config.m_GfxQuadAsTriangle && !m_GLUseTrianglesAsQuad ? 6 : 4;
931 const float CurIndex = Uses2DTextureArrays() ? m_CurIndex : (m_CurIndex + 0.5f) / 256.0f;
932
933 for(int i = 0; i < Num; ++i)
934 {
935 for(int n = 0; n < VertNum; ++n)
936 {
937 m_aVerticesTex3D[m_NumVertices + VertNum * i + n].m_Tex.w = CurIndex;
938 }
939 }
940
941 QuadsDrawTLImpl(pVertices: m_aVerticesTex3D, pArray, Num);
942}
943
944void CGraphics_Threaded::QuadsDrawFreeform(const CFreeformItem *pArray, int Num)
945{
946 dbg_assert(m_Drawing == EDrawing::QUADS || m_Drawing == EDrawing::TRIANGLES, "called Graphics()->QuadsDrawFreeform without begin");
947
948 if((g_Config.m_GfxQuadAsTriangle && !m_GLUseTrianglesAsQuad) || m_Drawing == EDrawing::TRIANGLES)
949 {
950 for(int i = 0; i < Num; ++i)
951 {
952 m_aVertices[m_NumVertices + 6 * i].m_Pos.x = pArray[i].m_X0;
953 m_aVertices[m_NumVertices + 6 * i].m_Pos.y = pArray[i].m_Y0;
954 m_aVertices[m_NumVertices + 6 * i].m_Tex = m_aTexture[0];
955 SetColor(pVertex: &m_aVertices[m_NumVertices + 6 * i], ColorIndex: 0);
956
957 m_aVertices[m_NumVertices + 6 * i + 1].m_Pos.x = pArray[i].m_X1;
958 m_aVertices[m_NumVertices + 6 * i + 1].m_Pos.y = pArray[i].m_Y1;
959 m_aVertices[m_NumVertices + 6 * i + 1].m_Tex = m_aTexture[1];
960 SetColor(pVertex: &m_aVertices[m_NumVertices + 6 * i + 1], ColorIndex: 1);
961
962 m_aVertices[m_NumVertices + 6 * i + 2].m_Pos.x = pArray[i].m_X3;
963 m_aVertices[m_NumVertices + 6 * i + 2].m_Pos.y = pArray[i].m_Y3;
964 m_aVertices[m_NumVertices + 6 * i + 2].m_Tex = m_aTexture[3];
965 SetColor(pVertex: &m_aVertices[m_NumVertices + 6 * i + 2], ColorIndex: 3);
966
967 m_aVertices[m_NumVertices + 6 * i + 3].m_Pos.x = pArray[i].m_X0;
968 m_aVertices[m_NumVertices + 6 * i + 3].m_Pos.y = pArray[i].m_Y0;
969 m_aVertices[m_NumVertices + 6 * i + 3].m_Tex = m_aTexture[0];
970 SetColor(pVertex: &m_aVertices[m_NumVertices + 6 * i + 3], ColorIndex: 0);
971
972 m_aVertices[m_NumVertices + 6 * i + 4].m_Pos.x = pArray[i].m_X3;
973 m_aVertices[m_NumVertices + 6 * i + 4].m_Pos.y = pArray[i].m_Y3;
974 m_aVertices[m_NumVertices + 6 * i + 4].m_Tex = m_aTexture[3];
975 SetColor(pVertex: &m_aVertices[m_NumVertices + 6 * i + 4], ColorIndex: 3);
976
977 m_aVertices[m_NumVertices + 6 * i + 5].m_Pos.x = pArray[i].m_X2;
978 m_aVertices[m_NumVertices + 6 * i + 5].m_Pos.y = pArray[i].m_Y2;
979 m_aVertices[m_NumVertices + 6 * i + 5].m_Tex = m_aTexture[2];
980 SetColor(pVertex: &m_aVertices[m_NumVertices + 6 * i + 5], ColorIndex: 2);
981 }
982
983 AddVertices(Count: 3 * 2 * Num);
984 }
985 else
986 {
987 for(int i = 0; i < Num; ++i)
988 {
989 m_aVertices[m_NumVertices + 4 * i].m_Pos.x = pArray[i].m_X0;
990 m_aVertices[m_NumVertices + 4 * i].m_Pos.y = pArray[i].m_Y0;
991 m_aVertices[m_NumVertices + 4 * i].m_Tex = m_aTexture[0];
992 SetColor(pVertex: &m_aVertices[m_NumVertices + 4 * i], ColorIndex: 0);
993
994 m_aVertices[m_NumVertices + 4 * i + 1].m_Pos.x = pArray[i].m_X1;
995 m_aVertices[m_NumVertices + 4 * i + 1].m_Pos.y = pArray[i].m_Y1;
996 m_aVertices[m_NumVertices + 4 * i + 1].m_Tex = m_aTexture[1];
997 SetColor(pVertex: &m_aVertices[m_NumVertices + 4 * i + 1], ColorIndex: 1);
998
999 m_aVertices[m_NumVertices + 4 * i + 2].m_Pos.x = pArray[i].m_X3;
1000 m_aVertices[m_NumVertices + 4 * i + 2].m_Pos.y = pArray[i].m_Y3;
1001 m_aVertices[m_NumVertices + 4 * i + 2].m_Tex = m_aTexture[3];
1002 SetColor(pVertex: &m_aVertices[m_NumVertices + 4 * i + 2], ColorIndex: 3);
1003
1004 m_aVertices[m_NumVertices + 4 * i + 3].m_Pos.x = pArray[i].m_X2;
1005 m_aVertices[m_NumVertices + 4 * i + 3].m_Pos.y = pArray[i].m_Y2;
1006 m_aVertices[m_NumVertices + 4 * i + 3].m_Tex = m_aTexture[2];
1007 SetColor(pVertex: &m_aVertices[m_NumVertices + 4 * i + 3], ColorIndex: 2);
1008 }
1009
1010 AddVertices(Count: 4 * Num);
1011 }
1012}
1013
1014void CGraphics_Threaded::QuadsText(float x, float y, float Size, const char *pText)
1015{
1016 float StartX = x;
1017
1018 while(*pText)
1019 {
1020 char c = *pText;
1021 pText++;
1022
1023 if(c == '\n')
1024 {
1025 x = StartX;
1026 y += Size;
1027 }
1028 else
1029 {
1030 QuadsSetSubset(
1031 TlU: (c % 16) / 16.0f,
1032 TlV: (c / 16) / 16.0f,
1033 BrU: (c % 16) / 16.0f + 1.0f / 16.0f,
1034 BrV: (c / 16) / 16.0f + 1.0f / 16.0f);
1035
1036 CQuadItem QuadItem(x, y, Size, Size);
1037 QuadsDrawTL(pArray: &QuadItem, Num: 1);
1038 x += Size / 2;
1039 }
1040 }
1041}
1042
1043void CGraphics_Threaded::DrawRectExt(float x, float y, float w, float h, float r, int Corners)
1044{
1045 const int NumSegments = 8;
1046 const float SegmentsAngle = pi / 2 / NumSegments;
1047 IGraphics::CFreeformItem aFreeform[NumSegments * 4];
1048 size_t NumItems = 0;
1049
1050 for(int i = 0; i < NumSegments; i += 2)
1051 {
1052 float a1 = i * SegmentsAngle;
1053 float a2 = (i + 1) * SegmentsAngle;
1054 float a3 = (i + 2) * SegmentsAngle;
1055 float Ca1 = std::cos(x: a1);
1056 float Ca2 = std::cos(x: a2);
1057 float Ca3 = std::cos(x: a3);
1058 float Sa1 = std::sin(x: a1);
1059 float Sa2 = std::sin(x: a2);
1060 float Sa3 = std::sin(x: a3);
1061
1062 if(Corners & CORNER_TL)
1063 aFreeform[NumItems++] = IGraphics::CFreeformItem(
1064 x + r, y + r,
1065 x + (1 - Ca1) * r, y + (1 - Sa1) * r,
1066 x + (1 - Ca3) * r, y + (1 - Sa3) * r,
1067 x + (1 - Ca2) * r, y + (1 - Sa2) * r);
1068
1069 if(Corners & CORNER_TR)
1070 aFreeform[NumItems++] = IGraphics::CFreeformItem(
1071 x + w - r, y + r,
1072 x + w - r + Ca1 * r, y + (1 - Sa1) * r,
1073 x + w - r + Ca3 * r, y + (1 - Sa3) * r,
1074 x + w - r + Ca2 * r, y + (1 - Sa2) * r);
1075
1076 if(Corners & CORNER_BL)
1077 aFreeform[NumItems++] = IGraphics::CFreeformItem(
1078 x + r, y + h - r,
1079 x + (1 - Ca1) * r, y + h - r + Sa1 * r,
1080 x + (1 - Ca3) * r, y + h - r + Sa3 * r,
1081 x + (1 - Ca2) * r, y + h - r + Sa2 * r);
1082
1083 if(Corners & CORNER_BR)
1084 aFreeform[NumItems++] = IGraphics::CFreeformItem(
1085 x + w - r, y + h - r,
1086 x + w - r + Ca1 * r, y + h - r + Sa1 * r,
1087 x + w - r + Ca3 * r, y + h - r + Sa3 * r,
1088 x + w - r + Ca2 * r, y + h - r + Sa2 * r);
1089 }
1090 QuadsDrawFreeform(pArray: aFreeform, Num: NumItems);
1091
1092 CQuadItem aQuads[9];
1093 NumItems = 0;
1094 aQuads[NumItems++] = CQuadItem(x + r, y + r, w - r * 2, h - r * 2); // center
1095 aQuads[NumItems++] = CQuadItem(x + r, y, w - r * 2, r); // top
1096 aQuads[NumItems++] = CQuadItem(x + r, y + h - r, w - r * 2, r); // bottom
1097 aQuads[NumItems++] = CQuadItem(x, y + r, r, h - r * 2); // left
1098 aQuads[NumItems++] = CQuadItem(x + w - r, y + r, r, h - r * 2); // right
1099
1100 if(!(Corners & CORNER_TL))
1101 aQuads[NumItems++] = CQuadItem(x, y, r, r);
1102 if(!(Corners & CORNER_TR))
1103 aQuads[NumItems++] = CQuadItem(x + w, y, -r, r);
1104 if(!(Corners & CORNER_BL))
1105 aQuads[NumItems++] = CQuadItem(x, y + h, r, -r);
1106 if(!(Corners & CORNER_BR))
1107 aQuads[NumItems++] = CQuadItem(x + w, y + h, -r, -r);
1108
1109 QuadsDrawTL(pArray: aQuads, Num: NumItems);
1110}
1111
1112void CGraphics_Threaded::DrawRectExt4(float x, float y, float w, float h, ColorRGBA ColorTopLeft, ColorRGBA ColorTopRight, ColorRGBA ColorBottomLeft, ColorRGBA ColorBottomRight, float r, int Corners)
1113{
1114 if(Corners == 0 || r == 0.0f)
1115 {
1116 SetColor4(TopLeft: ColorTopLeft, TopRight: ColorTopRight, BottomLeft: ColorBottomLeft, BottomRight: ColorBottomRight);
1117 CQuadItem ItemQ = CQuadItem(x, y, w, h);
1118 QuadsDrawTL(pArray: &ItemQ, Num: 1);
1119 return;
1120 }
1121
1122 const int NumSegments = 8;
1123 const float SegmentsAngle = pi / 2 / NumSegments;
1124 for(int i = 0; i < NumSegments; i += 2)
1125 {
1126 float a1 = i * SegmentsAngle;
1127 float a2 = (i + 1) * SegmentsAngle;
1128 float a3 = (i + 2) * SegmentsAngle;
1129 float Ca1 = std::cos(x: a1);
1130 float Ca2 = std::cos(x: a2);
1131 float Ca3 = std::cos(x: a3);
1132 float Sa1 = std::sin(x: a1);
1133 float Sa2 = std::sin(x: a2);
1134 float Sa3 = std::sin(x: a3);
1135
1136 if(Corners & CORNER_TL)
1137 {
1138 SetColor(ColorTopLeft);
1139 IGraphics::CFreeformItem ItemF = IGraphics::CFreeformItem(
1140 x + r, y + r,
1141 x + (1 - Ca1) * r, y + (1 - Sa1) * r,
1142 x + (1 - Ca3) * r, y + (1 - Sa3) * r,
1143 x + (1 - Ca2) * r, y + (1 - Sa2) * r);
1144 QuadsDrawFreeform(pArray: &ItemF, Num: 1);
1145 }
1146
1147 if(Corners & CORNER_TR)
1148 {
1149 SetColor(ColorTopRight);
1150 IGraphics::CFreeformItem ItemF = IGraphics::CFreeformItem(
1151 x + w - r, y + r,
1152 x + w - r + Ca1 * r, y + (1 - Sa1) * r,
1153 x + w - r + Ca3 * r, y + (1 - Sa3) * r,
1154 x + w - r + Ca2 * r, y + (1 - Sa2) * r);
1155 QuadsDrawFreeform(pArray: &ItemF, Num: 1);
1156 }
1157
1158 if(Corners & CORNER_BL)
1159 {
1160 SetColor(ColorBottomLeft);
1161 IGraphics::CFreeformItem ItemF = IGraphics::CFreeformItem(
1162 x + r, y + h - r,
1163 x + (1 - Ca1) * r, y + h - r + Sa1 * r,
1164 x + (1 - Ca3) * r, y + h - r + Sa3 * r,
1165 x + (1 - Ca2) * r, y + h - r + Sa2 * r);
1166 QuadsDrawFreeform(pArray: &ItemF, Num: 1);
1167 }
1168
1169 if(Corners & CORNER_BR)
1170 {
1171 SetColor(ColorBottomRight);
1172 IGraphics::CFreeformItem ItemF = IGraphics::CFreeformItem(
1173 x + w - r, y + h - r,
1174 x + w - r + Ca1 * r, y + h - r + Sa1 * r,
1175 x + w - r + Ca3 * r, y + h - r + Sa3 * r,
1176 x + w - r + Ca2 * r, y + h - r + Sa2 * r);
1177 QuadsDrawFreeform(pArray: &ItemF, Num: 1);
1178 }
1179 }
1180
1181 SetColor4(TopLeft: ColorTopLeft, TopRight: ColorTopRight, BottomLeft: ColorBottomLeft, BottomRight: ColorBottomRight);
1182 CQuadItem ItemQ = CQuadItem(x + r, y + r, w - r * 2, h - r * 2); // center
1183 QuadsDrawTL(pArray: &ItemQ, Num: 1);
1184
1185 SetColor4(TopLeft: ColorTopLeft, TopRight: ColorTopRight, BottomLeft: ColorTopLeft, BottomRight: ColorTopRight);
1186 ItemQ = CQuadItem(x + r, y, w - r * 2, r); // top
1187 QuadsDrawTL(pArray: &ItemQ, Num: 1);
1188
1189 SetColor4(TopLeft: ColorBottomLeft, TopRight: ColorBottomRight, BottomLeft: ColorBottomLeft, BottomRight: ColorBottomRight);
1190 ItemQ = CQuadItem(x + r, y + h - r, w - r * 2, r); // bottom
1191 QuadsDrawTL(pArray: &ItemQ, Num: 1);
1192
1193 SetColor4(TopLeft: ColorTopLeft, TopRight: ColorTopLeft, BottomLeft: ColorBottomLeft, BottomRight: ColorBottomLeft);
1194 ItemQ = CQuadItem(x, y + r, r, h - r * 2); // left
1195 QuadsDrawTL(pArray: &ItemQ, Num: 1);
1196
1197 SetColor4(TopLeft: ColorTopRight, TopRight: ColorTopRight, BottomLeft: ColorBottomRight, BottomRight: ColorBottomRight);
1198 ItemQ = CQuadItem(x + w - r, y + r, r, h - r * 2); // right
1199 QuadsDrawTL(pArray: &ItemQ, Num: 1);
1200
1201 if(!(Corners & CORNER_TL))
1202 {
1203 SetColor(ColorTopLeft);
1204 ItemQ = CQuadItem(x, y, r, r);
1205 QuadsDrawTL(pArray: &ItemQ, Num: 1);
1206 }
1207
1208 if(!(Corners & CORNER_TR))
1209 {
1210 SetColor(ColorTopRight);
1211 ItemQ = CQuadItem(x + w, y, -r, r);
1212 QuadsDrawTL(pArray: &ItemQ, Num: 1);
1213 }
1214
1215 if(!(Corners & CORNER_BL))
1216 {
1217 SetColor(ColorBottomLeft);
1218 ItemQ = CQuadItem(x, y + h, r, -r);
1219 QuadsDrawTL(pArray: &ItemQ, Num: 1);
1220 }
1221
1222 if(!(Corners & CORNER_BR))
1223 {
1224 SetColor(ColorBottomRight);
1225 ItemQ = CQuadItem(x + w, y + h, -r, -r);
1226 QuadsDrawTL(pArray: &ItemQ, Num: 1);
1227 }
1228}
1229
1230int CGraphics_Threaded::CreateRectQuadContainer(float x, float y, float w, float h, float r, int Corners)
1231{
1232 int ContainerIndex = CreateQuadContainer(AutomaticUpload: false);
1233
1234 if(Corners == 0 || r == 0.0f)
1235 {
1236 CQuadItem ItemQ = CQuadItem(x, y, w, h);
1237 QuadContainerAddQuads(ContainerIndex, pArray: &ItemQ, Num: 1);
1238 QuadContainerUpload(ContainerIndex);
1239 QuadContainerChangeAutomaticUpload(ContainerIndex, AutomaticUpload: true);
1240 return ContainerIndex;
1241 }
1242
1243 const int NumSegments = 8;
1244 const float SegmentsAngle = pi / 2 / NumSegments;
1245 IGraphics::CFreeformItem aFreeform[NumSegments * 4];
1246 size_t NumItems = 0;
1247
1248 for(int i = 0; i < NumSegments; i += 2)
1249 {
1250 float a1 = i * SegmentsAngle;
1251 float a2 = (i + 1) * SegmentsAngle;
1252 float a3 = (i + 2) * SegmentsAngle;
1253 float Ca1 = std::cos(x: a1);
1254 float Ca2 = std::cos(x: a2);
1255 float Ca3 = std::cos(x: a3);
1256 float Sa1 = std::sin(x: a1);
1257 float Sa2 = std::sin(x: a2);
1258 float Sa3 = std::sin(x: a3);
1259
1260 if(Corners & CORNER_TL)
1261 aFreeform[NumItems++] = IGraphics::CFreeformItem(
1262 x + r, y + r,
1263 x + (1 - Ca1) * r, y + (1 - Sa1) * r,
1264 x + (1 - Ca3) * r, y + (1 - Sa3) * r,
1265 x + (1 - Ca2) * r, y + (1 - Sa2) * r);
1266
1267 if(Corners & CORNER_TR)
1268 aFreeform[NumItems++] = IGraphics::CFreeformItem(
1269 x + w - r, y + r,
1270 x + w - r + Ca1 * r, y + (1 - Sa1) * r,
1271 x + w - r + Ca3 * r, y + (1 - Sa3) * r,
1272 x + w - r + Ca2 * r, y + (1 - Sa2) * r);
1273
1274 if(Corners & CORNER_BL)
1275 aFreeform[NumItems++] = IGraphics::CFreeformItem(
1276 x + r, y + h - r,
1277 x + (1 - Ca1) * r, y + h - r + Sa1 * r,
1278 x + (1 - Ca3) * r, y + h - r + Sa3 * r,
1279 x + (1 - Ca2) * r, y + h - r + Sa2 * r);
1280
1281 if(Corners & CORNER_BR)
1282 aFreeform[NumItems++] = IGraphics::CFreeformItem(
1283 x + w - r, y + h - r,
1284 x + w - r + Ca1 * r, y + h - r + Sa1 * r,
1285 x + w - r + Ca3 * r, y + h - r + Sa3 * r,
1286 x + w - r + Ca2 * r, y + h - r + Sa2 * r);
1287 }
1288
1289 if(NumItems > 0)
1290 QuadContainerAddQuads(ContainerIndex, pArray: aFreeform, Num: NumItems);
1291
1292 CQuadItem aQuads[9];
1293 NumItems = 0;
1294 aQuads[NumItems++] = CQuadItem(x + r, y + r, w - r * 2, h - r * 2); // center
1295 aQuads[NumItems++] = CQuadItem(x + r, y, w - r * 2, r); // top
1296 aQuads[NumItems++] = CQuadItem(x + r, y + h - r, w - r * 2, r); // bottom
1297 aQuads[NumItems++] = CQuadItem(x, y + r, r, h - r * 2); // left
1298 aQuads[NumItems++] = CQuadItem(x + w - r, y + r, r, h - r * 2); // right
1299
1300 if(!(Corners & CORNER_TL))
1301 aQuads[NumItems++] = CQuadItem(x, y, r, r);
1302 if(!(Corners & CORNER_TR))
1303 aQuads[NumItems++] = CQuadItem(x + w, y, -r, r);
1304 if(!(Corners & CORNER_BL))
1305 aQuads[NumItems++] = CQuadItem(x, y + h, r, -r);
1306 if(!(Corners & CORNER_BR))
1307 aQuads[NumItems++] = CQuadItem(x + w, y + h, -r, -r);
1308
1309 if(NumItems > 0)
1310 QuadContainerAddQuads(ContainerIndex, pArray: aQuads, Num: NumItems);
1311
1312 QuadContainerUpload(ContainerIndex);
1313 QuadContainerChangeAutomaticUpload(ContainerIndex, AutomaticUpload: true);
1314
1315 return ContainerIndex;
1316}
1317
1318void CGraphics_Threaded::DrawRect(float x, float y, float w, float h, ColorRGBA Color, int Corners, float Rounding)
1319{
1320 TextureClear();
1321 QuadsBegin();
1322 SetColor(Color);
1323 DrawRectExt(x, y, w, h, r: Rounding, Corners);
1324 QuadsEnd();
1325}
1326
1327void CGraphics_Threaded::DrawRect4(float x, float y, float w, float h, ColorRGBA ColorTopLeft, ColorRGBA ColorTopRight, ColorRGBA ColorBottomLeft, ColorRGBA ColorBottomRight, int Corners, float Rounding)
1328{
1329 TextureClear();
1330 QuadsBegin();
1331 DrawRectExt4(x, y, w, h, ColorTopLeft, ColorTopRight, ColorBottomLeft, ColorBottomRight, r: Rounding, Corners);
1332 QuadsEnd();
1333}
1334
1335void CGraphics_Threaded::DrawCircle(float CenterX, float CenterY, float Radius, int Segments)
1336{
1337 IGraphics::CFreeformItem aItems[32];
1338 size_t NumItems = 0;
1339 const float SegmentsAngle = 2 * pi / Segments;
1340 for(int i = 0; i < Segments; i += 2)
1341 {
1342 const float a1 = i * SegmentsAngle;
1343 const float a2 = (i + 1) * SegmentsAngle;
1344 const float a3 = (i + 2) * SegmentsAngle;
1345 aItems[NumItems++] = IGraphics::CFreeformItem(
1346 CenterX, CenterY,
1347 CenterX + std::cos(x: a1) * Radius, CenterY + std::sin(x: a1) * Radius,
1348 CenterX + std::cos(x: a3) * Radius, CenterY + std::sin(x: a3) * Radius,
1349 CenterX + std::cos(x: a2) * Radius, CenterY + std::sin(x: a2) * Radius);
1350 if(NumItems == std::size(aItems))
1351 {
1352 QuadsDrawFreeform(pArray: aItems, Num: std::size(aItems));
1353 NumItems = 0;
1354 }
1355 }
1356 if(NumItems)
1357 QuadsDrawFreeform(pArray: aItems, Num: NumItems);
1358}
1359
1360void CGraphics_Threaded::RenderTileLayer(int BufferContainerIndex, const ColorRGBA &Color, char **pOffsets, unsigned int *pIndicedVertexDrawNum, size_t NumIndicesOffset)
1361{
1362 if(NumIndicesOffset == 0)
1363 return;
1364
1365 // add the VertexArrays and draw
1366 CCommandBuffer::SCommand_RenderTileLayer Cmd;
1367 Cmd.m_State = m_State;
1368 Cmd.m_IndicesDrawNum = NumIndicesOffset;
1369 Cmd.m_BufferContainerIndex = BufferContainerIndex;
1370 Cmd.m_Color = Color;
1371
1372 void *pData = m_pCommandBuffer->AllocData(WantedSize: (sizeof(char *) + sizeof(unsigned int)) * NumIndicesOffset);
1373 if(pData == nullptr)
1374 {
1375 // kick command buffer and try again
1376 KickCommandBuffer();
1377
1378 pData = m_pCommandBuffer->AllocData(WantedSize: (sizeof(char *) + sizeof(unsigned int)) * NumIndicesOffset);
1379 if(pData == nullptr)
1380 {
1381 dbg_msg(sys: "graphics", fmt: "failed to allocate data for vertices");
1382 return;
1383 }
1384 }
1385 Cmd.m_pIndicesOffsets = (char **)pData;
1386 Cmd.m_pDrawCount = (unsigned int *)(((char *)pData) + (sizeof(char *) * NumIndicesOffset));
1387
1388 AddCmd(Cmd, FailFunc: [&] {
1389 pData = m_pCommandBuffer->AllocData(WantedSize: (sizeof(char *) + sizeof(unsigned int)) * NumIndicesOffset);
1390 if(pData == nullptr)
1391 return false;
1392 Cmd.m_pIndicesOffsets = (char **)pData;
1393 Cmd.m_pDrawCount = (unsigned int *)(((char *)pData) + (sizeof(char *) * NumIndicesOffset));
1394 return true;
1395 });
1396
1397 mem_copy(dest: Cmd.m_pIndicesOffsets, source: pOffsets, size: sizeof(char *) * NumIndicesOffset);
1398 mem_copy(dest: Cmd.m_pDrawCount, source: pIndicedVertexDrawNum, size: sizeof(unsigned int) * NumIndicesOffset);
1399
1400 m_pCommandBuffer->AddRenderCalls(RenderCallCountToAdd: NumIndicesOffset);
1401 // todo max indices group check!!
1402}
1403
1404void CGraphics_Threaded::RenderBorderTiles(int BufferContainerIndex, const ColorRGBA &Color, char *pIndexBufferOffset, const vec2 &Offset, const vec2 &Scale, uint32_t DrawNum)
1405{
1406 if(DrawNum == 0)
1407 return;
1408 // Draw a border tile a lot of times
1409 CCommandBuffer::SCommand_RenderBorderTile Cmd;
1410 Cmd.m_State = m_State;
1411 Cmd.m_DrawNum = DrawNum;
1412 Cmd.m_BufferContainerIndex = BufferContainerIndex;
1413 Cmd.m_Color = Color;
1414
1415 Cmd.m_pIndicesOffset = pIndexBufferOffset;
1416
1417 Cmd.m_Offset = Offset;
1418 Cmd.m_Scale = Scale;
1419
1420 AddCmd(Cmd);
1421
1422 m_pCommandBuffer->AddRenderCalls(RenderCallCountToAdd: 1);
1423}
1424
1425void CGraphics_Threaded::RenderQuadLayer(int BufferContainerIndex, SQuadRenderInfo *pQuadInfo, size_t QuadNum, int QuadOffset, bool Grouped)
1426{
1427 if(QuadNum == 0)
1428 return;
1429
1430 // add the VertexArrays and draw
1431 CCommandBuffer::SCommand_RenderQuadLayer Cmd(Grouped);
1432 Cmd.m_State = m_State;
1433 Cmd.m_QuadNum = QuadNum;
1434 Cmd.m_QuadOffset = QuadOffset;
1435 Cmd.m_BufferContainerIndex = BufferContainerIndex;
1436 if(!Grouped)
1437 {
1438 Cmd.m_pQuadInfo = (SQuadRenderInfo *)AllocCommandBufferData(AllocSize: Cmd.m_QuadNum * sizeof(SQuadRenderInfo));
1439 AddCmd(Cmd, FailFunc: [&] {
1440 Cmd.m_pQuadInfo = (SQuadRenderInfo *)m_pCommandBuffer->AllocData(WantedSize: QuadNum * sizeof(SQuadRenderInfo));
1441 return Cmd.m_pQuadInfo != nullptr;
1442 });
1443
1444 mem_copy(dest: Cmd.m_pQuadInfo, source: pQuadInfo, size: sizeof(SQuadRenderInfo) * QuadNum);
1445 m_pCommandBuffer->AddRenderCalls(RenderCallCountToAdd: ((QuadNum - 1) / gs_GraphicsMaxQuadsRenderCount) + 1);
1446 }
1447 else
1448 {
1449 Cmd.m_pQuadInfo = (SQuadRenderInfo *)AllocCommandBufferData(AllocSize: sizeof(SQuadRenderInfo));
1450 AddCmd(Cmd, FailFunc: [&] {
1451 Cmd.m_pQuadInfo = (SQuadRenderInfo *)m_pCommandBuffer->AllocData(WantedSize: sizeof(SQuadRenderInfo));
1452 return Cmd.m_pQuadInfo != nullptr;
1453 });
1454
1455 *Cmd.m_pQuadInfo = *pQuadInfo;
1456 m_pCommandBuffer->AddRenderCalls(RenderCallCountToAdd: 1);
1457 }
1458}
1459
1460void CGraphics_Threaded::RenderText(int BufferContainerIndex, int TextQuadNum, int TextureSize, int TextureTextIndex, int TextureTextOutlineIndex, const ColorRGBA &TextColor, const ColorRGBA &TextOutlineColor)
1461{
1462 if(BufferContainerIndex == -1)
1463 return;
1464
1465 CCommandBuffer::SCommand_RenderText Cmd;
1466 Cmd.m_State = m_State;
1467 Cmd.m_BufferContainerIndex = BufferContainerIndex;
1468 Cmd.m_DrawNum = TextQuadNum * 6;
1469 Cmd.m_TextureSize = TextureSize;
1470 Cmd.m_TextTextureIndex = TextureTextIndex;
1471 Cmd.m_TextOutlineTextureIndex = TextureTextOutlineIndex;
1472 Cmd.m_TextColor = TextColor;
1473 Cmd.m_TextOutlineColor = TextOutlineColor;
1474
1475 AddCmd(Cmd);
1476
1477 m_pCommandBuffer->AddRenderCalls(RenderCallCountToAdd: 1);
1478}
1479
1480int CGraphics_Threaded::CreateQuadContainer(bool AutomaticUpload)
1481{
1482 int Index = -1;
1483 if(m_FirstFreeQuadContainer == -1)
1484 {
1485 Index = m_vQuadContainers.size();
1486 m_vQuadContainers.emplace_back(args&: AutomaticUpload);
1487 }
1488 else
1489 {
1490 Index = m_FirstFreeQuadContainer;
1491 m_FirstFreeQuadContainer = m_vQuadContainers[Index].m_FreeIndex;
1492 m_vQuadContainers[Index].m_FreeIndex = Index;
1493 }
1494
1495 return Index;
1496}
1497
1498void CGraphics_Threaded::QuadContainerChangeAutomaticUpload(int ContainerIndex, bool AutomaticUpload)
1499{
1500 SQuadContainer &Container = m_vQuadContainers[ContainerIndex];
1501 Container.m_AutomaticUpload = AutomaticUpload;
1502}
1503
1504void CGraphics_Threaded::QuadContainerUpload(int ContainerIndex)
1505{
1506 if(IsQuadContainerBufferingEnabled())
1507 {
1508 SQuadContainer &Container = m_vQuadContainers[ContainerIndex];
1509 if(!Container.m_vQuads.empty())
1510 {
1511 if(Container.m_QuadBufferObjectIndex == -1)
1512 {
1513 size_t UploadDataSize = Container.m_vQuads.size() * sizeof(SQuadContainer::SQuad);
1514 Container.m_QuadBufferObjectIndex = CreateBufferObject(UploadDataSize, pUploadData: Container.m_vQuads.data(), CreateFlags: 0);
1515 }
1516 else
1517 {
1518 size_t UploadDataSize = Container.m_vQuads.size() * sizeof(SQuadContainer::SQuad);
1519 RecreateBufferObject(BufferIndex: Container.m_QuadBufferObjectIndex, UploadDataSize, pUploadData: Container.m_vQuads.data(), CreateFlags: 0);
1520 }
1521
1522 if(Container.m_QuadBufferContainerIndex == -1)
1523 {
1524 SBufferContainerInfo Info;
1525 Info.m_Stride = sizeof(CCommandBuffer::SVertex);
1526 Info.m_VertBufferBindingIndex = Container.m_QuadBufferObjectIndex;
1527
1528 Info.m_vAttributes.emplace_back();
1529 SBufferContainerInfo::SAttribute *pAttr = &Info.m_vAttributes.back();
1530 pAttr->m_DataTypeCount = 2;
1531 pAttr->m_FuncType = 0;
1532 pAttr->m_Normalized = false;
1533 pAttr->m_pOffset = nullptr;
1534 pAttr->m_Type = GRAPHICS_TYPE_FLOAT;
1535 Info.m_vAttributes.emplace_back();
1536 pAttr = &Info.m_vAttributes.back();
1537 pAttr->m_DataTypeCount = 2;
1538 pAttr->m_FuncType = 0;
1539 pAttr->m_Normalized = false;
1540 pAttr->m_pOffset = (void *)(sizeof(float) * 2);
1541 pAttr->m_Type = GRAPHICS_TYPE_FLOAT;
1542 Info.m_vAttributes.emplace_back();
1543 pAttr = &Info.m_vAttributes.back();
1544 pAttr->m_DataTypeCount = 4;
1545 pAttr->m_FuncType = 0;
1546 pAttr->m_Normalized = true;
1547 pAttr->m_pOffset = (void *)(sizeof(float) * 2 + sizeof(float) * 2);
1548 pAttr->m_Type = GRAPHICS_TYPE_UNSIGNED_BYTE;
1549
1550 Container.m_QuadBufferContainerIndex = CreateBufferContainer(pContainerInfo: &Info);
1551 }
1552 }
1553 }
1554}
1555
1556int CGraphics_Threaded::QuadContainerAddQuads(int ContainerIndex, CQuadItem *pArray, int Num)
1557{
1558 SQuadContainer &Container = m_vQuadContainers[ContainerIndex];
1559
1560 if((int)Container.m_vQuads.size() > Num + CCommandBuffer::CCommandBuffer::MAX_VERTICES)
1561 return -1;
1562
1563 int RetOff = (int)Container.m_vQuads.size();
1564
1565 for(int i = 0; i < Num; ++i)
1566 {
1567 Container.m_vQuads.emplace_back();
1568 SQuadContainer::SQuad &Quad = Container.m_vQuads.back();
1569
1570 Quad.m_aVertices[0].m_Pos.x = pArray[i].m_X;
1571 Quad.m_aVertices[0].m_Pos.y = pArray[i].m_Y;
1572 Quad.m_aVertices[0].m_Tex = m_aTexture[0];
1573 SetColor(pVertex: &Quad.m_aVertices[0], ColorIndex: 0);
1574
1575 Quad.m_aVertices[1].m_Pos.x = pArray[i].m_X + pArray[i].m_Width;
1576 Quad.m_aVertices[1].m_Pos.y = pArray[i].m_Y;
1577 Quad.m_aVertices[1].m_Tex = m_aTexture[1];
1578 SetColor(pVertex: &Quad.m_aVertices[1], ColorIndex: 1);
1579
1580 Quad.m_aVertices[2].m_Pos.x = pArray[i].m_X + pArray[i].m_Width;
1581 Quad.m_aVertices[2].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height;
1582 Quad.m_aVertices[2].m_Tex = m_aTexture[2];
1583 SetColor(pVertex: &Quad.m_aVertices[2], ColorIndex: 2);
1584
1585 Quad.m_aVertices[3].m_Pos.x = pArray[i].m_X;
1586 Quad.m_aVertices[3].m_Pos.y = pArray[i].m_Y + pArray[i].m_Height;
1587 Quad.m_aVertices[3].m_Tex = m_aTexture[3];
1588 SetColor(pVertex: &Quad.m_aVertices[3], ColorIndex: 3);
1589
1590 if(m_Rotation != 0)
1591 {
1592 CCommandBuffer::SPoint Center;
1593 Center.x = pArray[i].m_X + pArray[i].m_Width / 2;
1594 Center.y = pArray[i].m_Y + pArray[i].m_Height / 2;
1595
1596 Rotate(Center, pPoints: Quad.m_aVertices, NumPoints: 4);
1597 }
1598 }
1599
1600 if(Container.m_AutomaticUpload)
1601 QuadContainerUpload(ContainerIndex);
1602
1603 return RetOff;
1604}
1605
1606int CGraphics_Threaded::QuadContainerAddQuads(int ContainerIndex, CFreeformItem *pArray, int Num)
1607{
1608 SQuadContainer &Container = m_vQuadContainers[ContainerIndex];
1609
1610 if((int)Container.m_vQuads.size() > Num + CCommandBuffer::CCommandBuffer::MAX_VERTICES)
1611 return -1;
1612
1613 int RetOff = (int)Container.m_vQuads.size();
1614
1615 for(int i = 0; i < Num; ++i)
1616 {
1617 Container.m_vQuads.emplace_back();
1618 SQuadContainer::SQuad &Quad = Container.m_vQuads.back();
1619
1620 Quad.m_aVertices[0].m_Pos.x = pArray[i].m_X0;
1621 Quad.m_aVertices[0].m_Pos.y = pArray[i].m_Y0;
1622 Quad.m_aVertices[0].m_Tex = m_aTexture[0];
1623 SetColor(pVertex: &Quad.m_aVertices[0], ColorIndex: 0);
1624
1625 Quad.m_aVertices[1].m_Pos.x = pArray[i].m_X1;
1626 Quad.m_aVertices[1].m_Pos.y = pArray[i].m_Y1;
1627 Quad.m_aVertices[1].m_Tex = m_aTexture[1];
1628 SetColor(pVertex: &Quad.m_aVertices[1], ColorIndex: 1);
1629
1630 Quad.m_aVertices[2].m_Pos.x = pArray[i].m_X3;
1631 Quad.m_aVertices[2].m_Pos.y = pArray[i].m_Y3;
1632 Quad.m_aVertices[2].m_Tex = m_aTexture[3];
1633 SetColor(pVertex: &Quad.m_aVertices[2], ColorIndex: 3);
1634
1635 Quad.m_aVertices[3].m_Pos.x = pArray[i].m_X2;
1636 Quad.m_aVertices[3].m_Pos.y = pArray[i].m_Y2;
1637 Quad.m_aVertices[3].m_Tex = m_aTexture[2];
1638 SetColor(pVertex: &Quad.m_aVertices[3], ColorIndex: 2);
1639 }
1640
1641 if(Container.m_AutomaticUpload)
1642 QuadContainerUpload(ContainerIndex);
1643
1644 return RetOff;
1645}
1646
1647void CGraphics_Threaded::QuadContainerReset(int ContainerIndex)
1648{
1649 if(ContainerIndex == -1)
1650 return;
1651
1652 SQuadContainer &Container = m_vQuadContainers[ContainerIndex];
1653 if(IsQuadContainerBufferingEnabled())
1654 DeleteBufferContainer(ContainerIndex&: Container.m_QuadBufferContainerIndex, DestroyAllBO: true);
1655 Container.m_vQuads.clear();
1656 Container.m_QuadBufferObjectIndex = -1;
1657}
1658
1659void CGraphics_Threaded::DeleteQuadContainer(int &ContainerIndex)
1660{
1661 if(ContainerIndex == -1)
1662 return;
1663
1664 QuadContainerReset(ContainerIndex);
1665
1666 // also clear the container index
1667 m_vQuadContainers[ContainerIndex].m_FreeIndex = m_FirstFreeQuadContainer;
1668 m_FirstFreeQuadContainer = ContainerIndex;
1669 ContainerIndex = -1;
1670}
1671
1672void CGraphics_Threaded::RenderQuadContainer(int ContainerIndex, int QuadDrawNum)
1673{
1674 RenderQuadContainer(ContainerIndex, QuadOffset: 0, QuadDrawNum);
1675}
1676
1677void CGraphics_Threaded::RenderQuadContainer(int ContainerIndex, int QuadOffset, int QuadDrawNum, bool ChangeWrapMode)
1678{
1679 SQuadContainer &Container = m_vQuadContainers[ContainerIndex];
1680
1681 if(QuadDrawNum == -1)
1682 QuadDrawNum = (int)Container.m_vQuads.size() - QuadOffset;
1683
1684 if((int)Container.m_vQuads.size() < QuadOffset + QuadDrawNum || QuadDrawNum == 0)
1685 return;
1686
1687 if(IsQuadContainerBufferingEnabled())
1688 {
1689 if(Container.m_QuadBufferContainerIndex == -1)
1690 return;
1691
1692 if(ChangeWrapMode)
1693 WrapClamp();
1694
1695 CCommandBuffer::SCommand_RenderQuadContainer Cmd;
1696 Cmd.m_State = m_State;
1697 Cmd.m_DrawNum = (unsigned int)QuadDrawNum * 6;
1698 Cmd.m_pOffset = (void *)(QuadOffset * 6 * sizeof(unsigned int));
1699 Cmd.m_BufferContainerIndex = Container.m_QuadBufferContainerIndex;
1700
1701 AddCmd(Cmd);
1702 m_pCommandBuffer->AddRenderCalls(RenderCallCountToAdd: 1);
1703 }
1704 else
1705 {
1706 if(g_Config.m_GfxQuadAsTriangle)
1707 {
1708 for(int i = 0; i < QuadDrawNum; ++i)
1709 {
1710 SQuadContainer::SQuad &Quad = Container.m_vQuads[QuadOffset + i];
1711 m_aVertices[i * 6] = Quad.m_aVertices[0];
1712 m_aVertices[i * 6 + 1] = Quad.m_aVertices[1];
1713 m_aVertices[i * 6 + 2] = Quad.m_aVertices[2];
1714 m_aVertices[i * 6 + 3] = Quad.m_aVertices[0];
1715 m_aVertices[i * 6 + 4] = Quad.m_aVertices[2];
1716 m_aVertices[i * 6 + 5] = Quad.m_aVertices[3];
1717 m_NumVertices += 6;
1718 }
1719 }
1720 else
1721 {
1722 mem_copy(dest: m_aVertices, source: &Container.m_vQuads[QuadOffset], size: sizeof(CCommandBuffer::SVertex) * 4 * QuadDrawNum);
1723 m_NumVertices += 4 * QuadDrawNum;
1724 }
1725 m_Drawing = EDrawing::QUADS;
1726 if(ChangeWrapMode)
1727 WrapClamp();
1728 FlushVertices(KeepVertices: false);
1729 m_Drawing = EDrawing::NONE;
1730 }
1731 WrapNormal();
1732}
1733
1734void CGraphics_Threaded::RenderQuadContainerEx(int ContainerIndex, int QuadOffset, int QuadDrawNum, float X, float Y, float ScaleX, float ScaleY)
1735{
1736 SQuadContainer &Container = m_vQuadContainers[ContainerIndex];
1737
1738 if((int)Container.m_vQuads.size() < QuadOffset + 1)
1739 return;
1740
1741 if(QuadDrawNum == -1)
1742 QuadDrawNum = (int)Container.m_vQuads.size() - QuadOffset;
1743
1744 if(IsQuadContainerBufferingEnabled())
1745 {
1746 if(Container.m_QuadBufferContainerIndex == -1)
1747 return;
1748
1749 SQuadContainer::SQuad &Quad = Container.m_vQuads[QuadOffset];
1750 CCommandBuffer::SCommand_RenderQuadContainerEx Cmd;
1751
1752 WrapClamp();
1753
1754 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
1755 GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
1756 MapScreen(TopLeftX: (ScreenX0 - X) / ScaleX, TopLeftY: (ScreenY0 - Y) / ScaleY, BottomRightX: (ScreenX1 - X) / ScaleX, BottomRightY: (ScreenY1 - Y) / ScaleY);
1757 Cmd.m_State = m_State;
1758 MapScreen(TopLeftX: ScreenX0, TopLeftY: ScreenY0, BottomRightX: ScreenX1, BottomRightY: ScreenY1);
1759
1760 Cmd.m_DrawNum = QuadDrawNum * 6;
1761 Cmd.m_pOffset = (void *)(QuadOffset * 6 * sizeof(unsigned int));
1762 Cmd.m_BufferContainerIndex = Container.m_QuadBufferContainerIndex;
1763
1764 Cmd.m_VertexColor.r = (float)m_aColor[0].r / 255.f;
1765 Cmd.m_VertexColor.g = (float)m_aColor[0].g / 255.f;
1766 Cmd.m_VertexColor.b = (float)m_aColor[0].b / 255.f;
1767 Cmd.m_VertexColor.a = (float)m_aColor[0].a / 255.f;
1768
1769 Cmd.m_Rotation = m_Rotation;
1770
1771 // rotate before positioning
1772 Cmd.m_Center.x = Quad.m_aVertices[0].m_Pos.x + (Quad.m_aVertices[1].m_Pos.x - Quad.m_aVertices[0].m_Pos.x) / 2.f;
1773 Cmd.m_Center.y = Quad.m_aVertices[0].m_Pos.y + (Quad.m_aVertices[2].m_Pos.y - Quad.m_aVertices[0].m_Pos.y) / 2.f;
1774
1775 AddCmd(Cmd);
1776 m_pCommandBuffer->AddRenderCalls(RenderCallCountToAdd: 1);
1777 }
1778 else
1779 {
1780 if(g_Config.m_GfxQuadAsTriangle)
1781 {
1782 for(int i = 0; i < QuadDrawNum; ++i)
1783 {
1784 SQuadContainer::SQuad &Quad = Container.m_vQuads[QuadOffset + i];
1785 m_aVertices[i * 6 + 0] = Quad.m_aVertices[0];
1786 m_aVertices[i * 6 + 1] = Quad.m_aVertices[1];
1787 m_aVertices[i * 6 + 2] = Quad.m_aVertices[2];
1788 m_aVertices[i * 6 + 3] = Quad.m_aVertices[0];
1789 m_aVertices[i * 6 + 4] = Quad.m_aVertices[2];
1790 m_aVertices[i * 6 + 5] = Quad.m_aVertices[3];
1791
1792 for(int n = 0; n < 6; ++n)
1793 {
1794 m_aVertices[i * 6 + n].m_Pos.x *= ScaleX;
1795 m_aVertices[i * 6 + n].m_Pos.y *= ScaleY;
1796
1797 SetColor(pVertex: &m_aVertices[i * 6 + n], ColorIndex: 0);
1798 }
1799
1800 if(m_Rotation != 0)
1801 {
1802 CCommandBuffer::SPoint Center;
1803 Center.x = m_aVertices[i * 6 + 0].m_Pos.x + (m_aVertices[i * 6 + 1].m_Pos.x - m_aVertices[i * 6 + 0].m_Pos.x) / 2.f;
1804 Center.y = m_aVertices[i * 6 + 0].m_Pos.y + (m_aVertices[i * 6 + 2].m_Pos.y - m_aVertices[i * 6 + 0].m_Pos.y) / 2.f;
1805
1806 Rotate(Center, pPoints: &m_aVertices[i * 6 + 0], NumPoints: 6);
1807 }
1808
1809 for(int n = 0; n < 6; ++n)
1810 {
1811 m_aVertices[i * 6 + n].m_Pos.x += X;
1812 m_aVertices[i * 6 + n].m_Pos.y += Y;
1813 }
1814 m_NumVertices += 6;
1815 }
1816 }
1817 else
1818 {
1819 mem_copy(dest: m_aVertices, source: &Container.m_vQuads[QuadOffset], size: sizeof(CCommandBuffer::SVertex) * 4 * QuadDrawNum);
1820 for(int i = 0; i < QuadDrawNum; ++i)
1821 {
1822 for(int n = 0; n < 4; ++n)
1823 {
1824 m_aVertices[i * 4 + n].m_Pos.x *= ScaleX;
1825 m_aVertices[i * 4 + n].m_Pos.y *= ScaleY;
1826 SetColor(pVertex: &m_aVertices[i * 4 + n], ColorIndex: 0);
1827 }
1828
1829 if(m_Rotation != 0)
1830 {
1831 CCommandBuffer::SPoint Center;
1832 Center.x = m_aVertices[i * 4 + 0].m_Pos.x + (m_aVertices[i * 4 + 1].m_Pos.x - m_aVertices[i * 4 + 0].m_Pos.x) / 2.f;
1833 Center.y = m_aVertices[i * 4 + 0].m_Pos.y + (m_aVertices[i * 4 + 2].m_Pos.y - m_aVertices[i * 4 + 0].m_Pos.y) / 2.f;
1834
1835 Rotate(Center, pPoints: &m_aVertices[i * 4 + 0], NumPoints: 4);
1836 }
1837
1838 for(int n = 0; n < 4; ++n)
1839 {
1840 m_aVertices[i * 4 + n].m_Pos.x += X;
1841 m_aVertices[i * 4 + n].m_Pos.y += Y;
1842 }
1843 m_NumVertices += 4;
1844 }
1845 }
1846 m_Drawing = EDrawing::QUADS;
1847 WrapClamp();
1848 FlushVertices(KeepVertices: false);
1849 m_Drawing = EDrawing::NONE;
1850 }
1851 WrapNormal();
1852}
1853
1854void CGraphics_Threaded::RenderQuadContainerAsSprite(int ContainerIndex, int QuadOffset, float X, float Y, float ScaleX, float ScaleY)
1855{
1856 RenderQuadContainerEx(ContainerIndex, QuadOffset, QuadDrawNum: 1, X, Y, ScaleX, ScaleY);
1857}
1858
1859void CGraphics_Threaded::RenderQuadContainerAsSpriteMultiple(int ContainerIndex, int QuadOffset, int DrawCount, SRenderSpriteInfo *pRenderInfo)
1860{
1861 SQuadContainer &Container = m_vQuadContainers[ContainerIndex];
1862
1863 if(DrawCount == 0)
1864 return;
1865
1866 if(IsQuadContainerBufferingEnabled())
1867 {
1868 if(Container.m_QuadBufferContainerIndex == -1)
1869 return;
1870
1871 WrapClamp();
1872 SQuadContainer::SQuad &Quad = Container.m_vQuads[0];
1873 CCommandBuffer::SCommand_RenderQuadContainerAsSpriteMultiple Cmd;
1874
1875 Cmd.m_State = m_State;
1876
1877 Cmd.m_DrawNum = 1 * 6;
1878 Cmd.m_DrawCount = DrawCount;
1879 Cmd.m_pOffset = (void *)(QuadOffset * 6 * sizeof(unsigned int));
1880 Cmd.m_BufferContainerIndex = Container.m_QuadBufferContainerIndex;
1881
1882 Cmd.m_VertexColor.r = (float)m_aColor[0].r / 255.f;
1883 Cmd.m_VertexColor.g = (float)m_aColor[0].g / 255.f;
1884 Cmd.m_VertexColor.b = (float)m_aColor[0].b / 255.f;
1885 Cmd.m_VertexColor.a = (float)m_aColor[0].a / 255.f;
1886
1887 // rotate before positioning
1888 Cmd.m_Center.x = Quad.m_aVertices[0].m_Pos.x + (Quad.m_aVertices[1].m_Pos.x - Quad.m_aVertices[0].m_Pos.x) / 2.f;
1889 Cmd.m_Center.y = Quad.m_aVertices[0].m_Pos.y + (Quad.m_aVertices[2].m_Pos.y - Quad.m_aVertices[0].m_Pos.y) / 2.f;
1890
1891 Cmd.m_pRenderInfo = (IGraphics::SRenderSpriteInfo *)m_pCommandBuffer->AllocData(WantedSize: sizeof(IGraphics::SRenderSpriteInfo) * DrawCount);
1892 if(Cmd.m_pRenderInfo == nullptr)
1893 {
1894 // kick command buffer and try again
1895 KickCommandBuffer();
1896
1897 Cmd.m_pRenderInfo = (IGraphics::SRenderSpriteInfo *)m_pCommandBuffer->AllocData(WantedSize: sizeof(IGraphics::SRenderSpriteInfo) * DrawCount);
1898 if(Cmd.m_pRenderInfo == nullptr)
1899 {
1900 dbg_msg(sys: "graphics", fmt: "failed to allocate data for render info");
1901 return;
1902 }
1903 }
1904
1905 AddCmd(Cmd, FailFunc: [&] {
1906 Cmd.m_pRenderInfo = (IGraphics::SRenderSpriteInfo *)m_pCommandBuffer->AllocData(WantedSize: sizeof(IGraphics::SRenderSpriteInfo) * DrawCount);
1907 return Cmd.m_pRenderInfo != nullptr;
1908 });
1909
1910 mem_copy(dest: Cmd.m_pRenderInfo, source: pRenderInfo, size: sizeof(IGraphics::SRenderSpriteInfo) * DrawCount);
1911
1912 m_pCommandBuffer->AddRenderCalls(RenderCallCountToAdd: ((DrawCount - 1) / gs_GraphicsMaxParticlesRenderCount) + 1);
1913
1914 WrapNormal();
1915 }
1916 else
1917 {
1918 for(int i = 0; i < DrawCount; ++i)
1919 {
1920 QuadsSetRotation(Angle: pRenderInfo[i].m_Rotation);
1921 RenderQuadContainerAsSprite(ContainerIndex, QuadOffset, X: pRenderInfo[i].m_Pos.x, Y: pRenderInfo[i].m_Pos.y, ScaleX: pRenderInfo[i].m_Scale, ScaleY: pRenderInfo[i].m_Scale);
1922 }
1923 }
1924}
1925
1926void *CGraphics_Threaded::AllocCommandBufferData(size_t AllocSize)
1927{
1928 void *pData = m_pCommandBuffer->AllocData(WantedSize: AllocSize);
1929 if(pData == nullptr)
1930 {
1931 // kick command buffer and try again
1932 KickCommandBuffer();
1933
1934 pData = m_pCommandBuffer->AllocData(WantedSize: AllocSize);
1935 dbg_assert(pData, "graphics: failed to allocate data (size %" PRIzu ") for command buffer", AllocSize);
1936 }
1937 return pData;
1938}
1939
1940int CGraphics_Threaded::CreateBufferObject(size_t UploadDataSize, void *pUploadData, int CreateFlags, bool IsMovedPointer)
1941{
1942 int Index = -1;
1943 if(m_FirstFreeBufferObjectIndex == -1)
1944 {
1945 Index = m_vBufferObjectIndices.size();
1946 m_vBufferObjectIndices.push_back(x: Index);
1947 }
1948 else
1949 {
1950 Index = m_FirstFreeBufferObjectIndex;
1951 m_FirstFreeBufferObjectIndex = m_vBufferObjectIndices[Index];
1952 m_vBufferObjectIndices[Index] = Index;
1953 }
1954
1955 dbg_assert((CreateFlags & EBufferObjectCreateFlags::BUFFER_OBJECT_CREATE_FLAGS_ONE_TIME_USE_BIT) == 0 || (UploadDataSize <= CCommandBuffer::MAX_VERTICES * maximum(sizeof(CCommandBuffer::SVertexTex3DStream), sizeof(CCommandBuffer::SVertexTex3D))),
1956 "If BUFFER_OBJECT_CREATE_FLAGS_ONE_TIME_USE_BIT is used, then the buffer size must not exceed max(sizeof(CCommandBuffer::SVertexTex3DStream), sizeof(CCommandBuffer::SVertexTex3D)) * CCommandBuffer::MAX_VERTICES");
1957
1958 CCommandBuffer::SCommand_CreateBufferObject Cmd;
1959 Cmd.m_BufferIndex = Index;
1960 Cmd.m_DataSize = UploadDataSize;
1961 Cmd.m_DeletePointer = IsMovedPointer;
1962 Cmd.m_Flags = CreateFlags;
1963
1964 if(IsMovedPointer)
1965 {
1966 Cmd.m_pUploadData = pUploadData;
1967 AddCmd(Cmd);
1968 }
1969 else
1970 {
1971 if(UploadDataSize <= CMD_BUFFER_DATA_BUFFER_SIZE)
1972 {
1973 Cmd.m_pUploadData = AllocCommandBufferData(AllocSize: UploadDataSize);
1974
1975 AddCmd(Cmd, FailFunc: [&] {
1976 Cmd.m_pUploadData = m_pCommandBuffer->AllocData(WantedSize: UploadDataSize);
1977 return Cmd.m_pUploadData != nullptr;
1978 });
1979 mem_copy(dest: Cmd.m_pUploadData, source: pUploadData, size: UploadDataSize);
1980 }
1981 else
1982 {
1983 Cmd.m_pUploadData = nullptr;
1984 AddCmd(Cmd);
1985
1986 // update the buffer instead
1987 size_t UploadDataOffset = 0;
1988 while(UploadDataSize > 0)
1989 {
1990 size_t UpdateSize = (UploadDataSize > CMD_BUFFER_DATA_BUFFER_SIZE ? CMD_BUFFER_DATA_BUFFER_SIZE : UploadDataSize);
1991
1992 UpdateBufferObjectInternal(BufferIndex: Index, UploadDataSize: UpdateSize, pUploadData: (((char *)pUploadData) + UploadDataOffset), pOffset: (void *)UploadDataOffset);
1993
1994 UploadDataOffset += UpdateSize;
1995 UploadDataSize -= UpdateSize;
1996 }
1997 }
1998 }
1999
2000 return Index;
2001}
2002
2003void CGraphics_Threaded::RecreateBufferObject(int BufferIndex, size_t UploadDataSize, void *pUploadData, int CreateFlags, bool IsMovedPointer)
2004{
2005 CCommandBuffer::SCommand_RecreateBufferObject Cmd;
2006 Cmd.m_BufferIndex = BufferIndex;
2007 Cmd.m_DataSize = UploadDataSize;
2008 Cmd.m_DeletePointer = IsMovedPointer;
2009 Cmd.m_Flags = CreateFlags;
2010
2011 dbg_assert((CreateFlags & EBufferObjectCreateFlags::BUFFER_OBJECT_CREATE_FLAGS_ONE_TIME_USE_BIT) == 0 || (UploadDataSize <= CCommandBuffer::MAX_VERTICES * maximum(sizeof(CCommandBuffer::SVertexTex3DStream), sizeof(CCommandBuffer::SVertexTex3D))),
2012 "If BUFFER_OBJECT_CREATE_FLAGS_ONE_TIME_USE_BIT is used, then the buffer size must not exceed max(sizeof(CCommandBuffer::SVertexTex3DStream), sizeof(CCommandBuffer::SVertexTex3D)) * CCommandBuffer::MAX_VERTICES");
2013
2014 if(IsMovedPointer)
2015 {
2016 Cmd.m_pUploadData = pUploadData;
2017 AddCmd(Cmd);
2018 }
2019 else
2020 {
2021 if(UploadDataSize <= CMD_BUFFER_DATA_BUFFER_SIZE)
2022 {
2023 Cmd.m_pUploadData = AllocCommandBufferData(AllocSize: UploadDataSize);
2024
2025 AddCmd(Cmd, FailFunc: [&] {
2026 Cmd.m_pUploadData = m_pCommandBuffer->AllocData(WantedSize: UploadDataSize);
2027 return Cmd.m_pUploadData != nullptr;
2028 });
2029
2030 mem_copy(dest: Cmd.m_pUploadData, source: pUploadData, size: UploadDataSize);
2031 }
2032 else
2033 {
2034 Cmd.m_pUploadData = nullptr;
2035 AddCmd(Cmd);
2036
2037 // update the buffer instead
2038 size_t UploadDataOffset = 0;
2039 while(UploadDataSize > 0)
2040 {
2041 size_t UpdateSize = (UploadDataSize > CMD_BUFFER_DATA_BUFFER_SIZE ? CMD_BUFFER_DATA_BUFFER_SIZE : UploadDataSize);
2042
2043 UpdateBufferObjectInternal(BufferIndex, UploadDataSize: UpdateSize, pUploadData: (((char *)pUploadData) + UploadDataOffset), pOffset: (void *)UploadDataOffset);
2044
2045 UploadDataOffset += UpdateSize;
2046 UploadDataSize -= UpdateSize;
2047 }
2048 }
2049 }
2050}
2051
2052void CGraphics_Threaded::UpdateBufferObjectInternal(int BufferIndex, size_t UploadDataSize, void *pUploadData, void *pOffset, bool IsMovedPointer)
2053{
2054 CCommandBuffer::SCommand_UpdateBufferObject Cmd;
2055 Cmd.m_BufferIndex = BufferIndex;
2056 Cmd.m_DataSize = UploadDataSize;
2057 Cmd.m_pOffset = pOffset;
2058 Cmd.m_DeletePointer = IsMovedPointer;
2059
2060 if(IsMovedPointer)
2061 {
2062 Cmd.m_pUploadData = pUploadData;
2063 AddCmd(Cmd);
2064 }
2065 else
2066 {
2067 Cmd.m_pUploadData = AllocCommandBufferData(AllocSize: UploadDataSize);
2068
2069 AddCmd(Cmd, FailFunc: [&] {
2070 Cmd.m_pUploadData = m_pCommandBuffer->AllocData(WantedSize: UploadDataSize);
2071 return Cmd.m_pUploadData != nullptr;
2072 });
2073
2074 mem_copy(dest: Cmd.m_pUploadData, source: pUploadData, size: UploadDataSize);
2075 }
2076}
2077
2078void CGraphics_Threaded::CopyBufferObjectInternal(int WriteBufferIndex, int ReadBufferIndex, size_t WriteOffset, size_t ReadOffset, size_t CopyDataSize)
2079{
2080 CCommandBuffer::SCommand_CopyBufferObject Cmd;
2081 Cmd.m_WriteBufferIndex = WriteBufferIndex;
2082 Cmd.m_ReadBufferIndex = ReadBufferIndex;
2083 Cmd.m_WriteOffset = WriteOffset;
2084 Cmd.m_ReadOffset = ReadOffset;
2085 Cmd.m_CopySize = CopyDataSize;
2086 AddCmd(Cmd);
2087}
2088
2089void CGraphics_Threaded::DeleteBufferObject(int BufferIndex)
2090{
2091 if(BufferIndex == -1)
2092 return;
2093
2094 CCommandBuffer::SCommand_DeleteBufferObject Cmd;
2095 Cmd.m_BufferIndex = BufferIndex;
2096 AddCmd(Cmd);
2097
2098 // also clear the buffer object index
2099 m_vBufferObjectIndices[BufferIndex] = m_FirstFreeBufferObjectIndex;
2100 m_FirstFreeBufferObjectIndex = BufferIndex;
2101}
2102
2103int CGraphics_Threaded::CreateBufferContainer(SBufferContainerInfo *pContainerInfo)
2104{
2105 int Index = -1;
2106 if(m_FirstFreeVertexArrayInfo == -1)
2107 {
2108 Index = m_vVertexArrayInfo.size();
2109 m_vVertexArrayInfo.emplace_back();
2110 }
2111 else
2112 {
2113 Index = m_FirstFreeVertexArrayInfo;
2114 m_FirstFreeVertexArrayInfo = m_vVertexArrayInfo[Index].m_FreeIndex;
2115 m_vVertexArrayInfo[Index].m_FreeIndex = Index;
2116 }
2117
2118 CCommandBuffer::SCommand_CreateBufferContainer Cmd;
2119 Cmd.m_BufferContainerIndex = Index;
2120 Cmd.m_AttrCount = pContainerInfo->m_vAttributes.size();
2121 Cmd.m_Stride = pContainerInfo->m_Stride;
2122 Cmd.m_VertBufferBindingIndex = pContainerInfo->m_VertBufferBindingIndex;
2123 Cmd.m_pAttributes = (SBufferContainerInfo::SAttribute *)AllocCommandBufferData(AllocSize: Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute));
2124
2125 AddCmd(Cmd, FailFunc: [&] {
2126 Cmd.m_pAttributes = (SBufferContainerInfo::SAttribute *)m_pCommandBuffer->AllocData(WantedSize: Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute));
2127 return Cmd.m_pAttributes != nullptr;
2128 });
2129
2130 mem_copy(dest: Cmd.m_pAttributes, source: pContainerInfo->m_vAttributes.data(), size: Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute));
2131
2132 m_vVertexArrayInfo[Index].m_AssociatedBufferObjectIndex = pContainerInfo->m_VertBufferBindingIndex;
2133
2134 return Index;
2135}
2136
2137void CGraphics_Threaded::DeleteBufferContainer(int &ContainerIndex, bool DestroyAllBO)
2138{
2139 if(ContainerIndex == -1)
2140 return;
2141
2142 CCommandBuffer::SCommand_DeleteBufferContainer Cmd;
2143 Cmd.m_BufferContainerIndex = ContainerIndex;
2144 Cmd.m_DestroyAllBO = DestroyAllBO;
2145 AddCmd(Cmd);
2146
2147 if(DestroyAllBO)
2148 {
2149 // delete all associated references
2150 int BufferObjectIndex = m_vVertexArrayInfo[ContainerIndex].m_AssociatedBufferObjectIndex;
2151 if(BufferObjectIndex != -1)
2152 {
2153 // clear the buffer object index
2154 m_vBufferObjectIndices[BufferObjectIndex] = m_FirstFreeBufferObjectIndex;
2155 m_FirstFreeBufferObjectIndex = BufferObjectIndex;
2156 }
2157 }
2158 m_vVertexArrayInfo[ContainerIndex].m_AssociatedBufferObjectIndex = -1;
2159
2160 // also clear the buffer object index
2161 m_vVertexArrayInfo[ContainerIndex].m_FreeIndex = m_FirstFreeVertexArrayInfo;
2162 m_FirstFreeVertexArrayInfo = ContainerIndex;
2163 ContainerIndex = -1;
2164}
2165
2166void CGraphics_Threaded::UpdateBufferContainerInternal(int ContainerIndex, SBufferContainerInfo *pContainerInfo)
2167{
2168 CCommandBuffer::SCommand_UpdateBufferContainer Cmd;
2169 Cmd.m_BufferContainerIndex = ContainerIndex;
2170 Cmd.m_AttrCount = pContainerInfo->m_vAttributes.size();
2171 Cmd.m_Stride = pContainerInfo->m_Stride;
2172 Cmd.m_VertBufferBindingIndex = pContainerInfo->m_VertBufferBindingIndex;
2173 Cmd.m_pAttributes = (SBufferContainerInfo::SAttribute *)AllocCommandBufferData(AllocSize: Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute));
2174
2175 AddCmd(Cmd, FailFunc: [&] {
2176 Cmd.m_pAttributes = (SBufferContainerInfo::SAttribute *)m_pCommandBuffer->AllocData(WantedSize: Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute));
2177 return Cmd.m_pAttributes != nullptr;
2178 });
2179
2180 mem_copy(dest: Cmd.m_pAttributes, source: pContainerInfo->m_vAttributes.data(), size: Cmd.m_AttrCount * sizeof(SBufferContainerInfo::SAttribute));
2181
2182 m_vVertexArrayInfo[ContainerIndex].m_AssociatedBufferObjectIndex = pContainerInfo->m_VertBufferBindingIndex;
2183}
2184
2185void CGraphics_Threaded::IndicesNumRequiredNotify(unsigned int RequiredIndicesCount)
2186{
2187 CCommandBuffer::SCommand_IndicesRequiredNumNotify Cmd;
2188 Cmd.m_RequiredIndicesNum = RequiredIndicesCount;
2189 AddCmd(Cmd);
2190}
2191
2192int CGraphics_Threaded::IssueInit()
2193{
2194 // The flags have to be kept consistent with flags set in the CGraphicsBackend_SDL_GL::SetWindowParams function!
2195
2196 bool IsPurelyWindowed = g_Config.m_GfxFullscreen == 0;
2197 bool IsExclusiveFullscreen = g_Config.m_GfxFullscreen == 1;
2198 bool IsDesktopFullscreen = g_Config.m_GfxFullscreen == 2;
2199#ifndef CONF_FAMILY_WINDOWS
2200 // Windowed fullscreen is only available on Windows, use desktop fullscreen on other platforms
2201 IsDesktopFullscreen |= g_Config.m_GfxFullscreen == 3;
2202#endif
2203
2204 int Flags = 0;
2205 if(IsExclusiveFullscreen)
2206 {
2207 Flags |= IGraphicsBackend::INITFLAG_FULLSCREEN;
2208 }
2209 else if(IsDesktopFullscreen)
2210 {
2211 Flags |= IGraphicsBackend::INITFLAG_DESKTOP_FULLSCREEN;
2212 }
2213 else if(IsPurelyWindowed)
2214 {
2215 Flags |= IGraphicsBackend::INITFLAG_RESIZABLE;
2216 if(g_Config.m_GfxBorderless)
2217 {
2218 Flags |= IGraphicsBackend::INITFLAG_BORDERLESS;
2219 }
2220 }
2221 if(g_Config.m_GfxVsync)
2222 {
2223 Flags |= IGraphicsBackend::INITFLAG_VSYNC;
2224 }
2225
2226 const int Result = m_pBackend->Init(pName: "DDNet Client", pScreen: &g_Config.m_GfxScreen, pWidth: &g_Config.m_GfxScreenWidth, pHeight: &g_Config.m_GfxScreenHeight, pRefreshRate: &g_Config.m_GfxScreenRefreshRate, pFsaaSamples: &g_Config.m_GfxFsaaSamples, Flags, pDesktopWidth: &g_Config.m_GfxDesktopWidth, pDesktopHeight: &g_Config.m_GfxDesktopHeight, pCurrentWidth: &m_ScreenWidth, pCurrentHeight: &m_ScreenHeight, pStorage: m_pStorage);
2227 AddBackEndWarningIfExists();
2228 if(Result == 0)
2229 {
2230 m_GLUseTrianglesAsQuad = m_pBackend->UseTrianglesAsQuad();
2231 m_GLTileBufferingEnabled = m_pBackend->HasTileBuffering();
2232 m_GLQuadBufferingEnabled = m_pBackend->HasQuadBuffering();
2233 m_GLQuadContainerBufferingEnabled = m_pBackend->HasQuadContainerBuffering();
2234 m_GLTextBufferingEnabled = (m_GLQuadContainerBufferingEnabled && m_pBackend->HasTextBuffering());
2235 m_GLUses2DTextureArrays = m_pBackend->Uses2DTextureArrays();
2236 m_GLHasTextureArraysSupport = m_pBackend->HasTextureArraysSupport();
2237 m_ScreenHiDPIScale = m_ScreenWidth / (float)g_Config.m_GfxScreenWidth;
2238 m_ScreenRefreshRate = g_Config.m_GfxScreenRefreshRate;
2239 }
2240 return Result;
2241}
2242
2243void CGraphics_Threaded::AdjustViewport(bool SendViewportChangeToBackend)
2244{
2245 // adjust the viewport to only allow certain aspect ratios
2246 // keep this in sync with backend_vulkan GetSwapImageSize's check
2247 if(m_ScreenHeight > 4 * m_ScreenWidth / 5)
2248 {
2249 m_IsForcedViewport = true;
2250 m_ScreenHeight = 4 * m_ScreenWidth / 5;
2251
2252 if(SendViewportChangeToBackend)
2253 {
2254 UpdateViewport(X: 0, Y: 0, W: m_ScreenWidth, H: m_ScreenHeight, ByResize: true);
2255 }
2256 }
2257 else
2258 {
2259 m_IsForcedViewport = false;
2260 }
2261}
2262
2263void CGraphics_Threaded::UpdateViewport(int X, int Y, int W, int H, bool ByResize)
2264{
2265 CCommandBuffer::SCommand_Update_Viewport Cmd;
2266 Cmd.m_X = X;
2267 Cmd.m_Y = Y;
2268 Cmd.m_Width = W;
2269 Cmd.m_Height = H;
2270 Cmd.m_ByResize = ByResize;
2271 AddCmd(Cmd);
2272}
2273
2274void CGraphics_Threaded::AddBackEndWarningIfExists()
2275{
2276 const char *pErrStr = m_pBackend->GetErrorString();
2277 if(pErrStr != nullptr)
2278 {
2279 SWarning NewWarning;
2280 str_copy(dst&: NewWarning.m_aWarningMsg, src: Localize(pStr: pErrStr));
2281 AddWarning(Warning: NewWarning);
2282 }
2283}
2284
2285int CGraphics_Threaded::InitWindow()
2286{
2287 int ErrorCode = IssueInit();
2288 if(ErrorCode == 0)
2289 return 0;
2290
2291 // try disabling fsaa
2292 while(g_Config.m_GfxFsaaSamples)
2293 {
2294 // 4 is the minimum required by OpenGL ES spec (GL_MAX_SAMPLES - https://www.khronos.org/registry/OpenGL-Refpages/es3.0/html/glGet.xhtml), so can probably also be assumed for OpenGL
2295 if(g_Config.m_GfxFsaaSamples > 4)
2296 g_Config.m_GfxFsaaSamples = 4;
2297 else
2298 g_Config.m_GfxFsaaSamples = 0;
2299
2300 if(g_Config.m_GfxFsaaSamples)
2301 dbg_msg(sys: "gfx", fmt: "lowering FSAA to %d and trying again", g_Config.m_GfxFsaaSamples);
2302 else
2303 dbg_msg(sys: "gfx", fmt: "disabling FSAA and trying again");
2304
2305 ErrorCode = IssueInit();
2306 if(ErrorCode == 0)
2307 return 0;
2308 }
2309
2310 size_t GLInitTryCount = 0;
2311 while(ErrorCode == EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_GL_CONTEXT_FAILED || ErrorCode == EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_GL_VERSION_FAILED)
2312 {
2313 if(ErrorCode == EGraphicsBackendErrorCodes::GRAPHICS_BACKEND_ERROR_CODE_GL_CONTEXT_FAILED)
2314 {
2315 // try next smaller major/minor or patch version
2316 if(g_Config.m_GfxGLMajor >= 4)
2317 {
2318 g_Config.m_GfxGLMajor = 3;
2319 g_Config.m_GfxGLMinor = 3;
2320 g_Config.m_GfxGLPatch = 0;
2321 }
2322 else if(g_Config.m_GfxGLMajor == 3 && g_Config.m_GfxGLMinor >= 1)
2323 {
2324 g_Config.m_GfxGLMajor = 3;
2325 g_Config.m_GfxGLMinor = 0;
2326 g_Config.m_GfxGLPatch = 0;
2327 }
2328 else if(g_Config.m_GfxGLMajor == 3 && g_Config.m_GfxGLMinor == 0)
2329 {
2330 g_Config.m_GfxGLMajor = 2;
2331 g_Config.m_GfxGLMinor = 1;
2332 g_Config.m_GfxGLPatch = 0;
2333 }
2334 else if(g_Config.m_GfxGLMajor == 2 && g_Config.m_GfxGLMinor >= 1)
2335 {
2336 g_Config.m_GfxGLMajor = 2;
2337 g_Config.m_GfxGLMinor = 0;
2338 g_Config.m_GfxGLPatch = 0;
2339 }
2340 else if(g_Config.m_GfxGLMajor == 2 && g_Config.m_GfxGLMinor == 0)
2341 {
2342 g_Config.m_GfxGLMajor = 1;
2343 g_Config.m_GfxGLMinor = 5;
2344 g_Config.m_GfxGLPatch = 0;
2345 }
2346 else if(g_Config.m_GfxGLMajor == 1 && g_Config.m_GfxGLMinor == 5)
2347 {
2348 g_Config.m_GfxGLMajor = 1;
2349 g_Config.m_GfxGLMinor = 4;
2350 g_Config.m_GfxGLPatch = 0;
2351 }
2352 else if(g_Config.m_GfxGLMajor == 1 && g_Config.m_GfxGLMinor == 4)
2353 {
2354 g_Config.m_GfxGLMajor = 1;
2355 g_Config.m_GfxGLMinor = 3;
2356 g_Config.m_GfxGLPatch = 0;
2357 }
2358 else if(g_Config.m_GfxGLMajor == 1 && g_Config.m_GfxGLMinor == 3)
2359 {
2360 g_Config.m_GfxGLMajor = 1;
2361 g_Config.m_GfxGLMinor = 2;
2362 g_Config.m_GfxGLPatch = 1;
2363 }
2364 else if(g_Config.m_GfxGLMajor == 1 && g_Config.m_GfxGLMinor == 2)
2365 {
2366 g_Config.m_GfxGLMajor = 1;
2367 g_Config.m_GfxGLMinor = 1;
2368 g_Config.m_GfxGLPatch = 0;
2369 }
2370 }
2371
2372 // new gl version was set by backend, try again
2373 ErrorCode = IssueInit();
2374 if(ErrorCode == 0)
2375 {
2376 return 0;
2377 }
2378
2379 if(++GLInitTryCount >= 9)
2380 {
2381 // try something else
2382 break;
2383 }
2384 }
2385
2386 // try lowering the resolution
2387 if(g_Config.m_GfxScreenWidth != 640 || g_Config.m_GfxScreenHeight != 480)
2388 {
2389 dbg_msg(sys: "gfx", fmt: "setting resolution to 640x480 and trying again");
2390 g_Config.m_GfxScreenWidth = 640;
2391 g_Config.m_GfxScreenHeight = 480;
2392
2393 if(IssueInit() == 0)
2394 return 0;
2395 }
2396
2397 // at the very end, just try to set to gl 1.4
2398 {
2399 g_Config.m_GfxGLMajor = 1;
2400 g_Config.m_GfxGLMinor = 4;
2401 g_Config.m_GfxGLPatch = 0;
2402
2403 if(IssueInit() == 0)
2404 return 0;
2405 }
2406
2407 dbg_msg(sys: "gfx", fmt: "out of ideas. failed to init graphics");
2408
2409 return -1;
2410}
2411
2412int CGraphics_Threaded::Init()
2413{
2414 // fetch pointers
2415 m_pStorage = Kernel()->RequestInterface<IStorage>();
2416 m_pConsole = Kernel()->RequestInterface<IConsole>();
2417 m_pEngine = Kernel()->RequestInterface<IEngine>();
2418
2419 // init textures
2420 m_FirstFreeTexture = 0;
2421 m_vTextureIndices.resize(new_size: CCommandBuffer::MAX_TEXTURES);
2422 for(size_t i = 0; i < m_vTextureIndices.size(); ++i)
2423 m_vTextureIndices[i] = i + 1;
2424
2425 m_FirstFreeVertexArrayInfo = -1;
2426 m_FirstFreeBufferObjectIndex = -1;
2427 m_FirstFreeQuadContainer = -1;
2428
2429 m_pBackend = CreateGraphicsBackend(TranslateFunc: Localize);
2430 if(InitWindow() != 0)
2431 return -1;
2432
2433 for(auto &FakeMode : g_aFakeModes)
2434 {
2435 FakeMode.m_WindowWidth = FakeMode.m_CanvasWidth / m_ScreenHiDPIScale;
2436 FakeMode.m_WindowHeight = FakeMode.m_CanvasHeight / m_ScreenHiDPIScale;
2437 FakeMode.m_RefreshRate = g_Config.m_GfxScreenRefreshRate;
2438 }
2439
2440 // create command buffers
2441 for(auto &pCommandBuffer : m_apCommandBuffers)
2442 pCommandBuffer = new CCommandBuffer(CMD_BUFFER_CMD_BUFFER_SIZE, CMD_BUFFER_DATA_BUFFER_SIZE);
2443 m_pCommandBuffer = m_apCommandBuffers[0];
2444
2445 // create null texture, will get id=0
2446 {
2447 const size_t PixelSize = 4;
2448 const unsigned char aRed[] = {0xff, 0x00, 0x00, 0xff};
2449 const unsigned char aGreen[] = {0x00, 0xff, 0x00, 0xff};
2450 const unsigned char aBlue[] = {0x00, 0x00, 0xff, 0xff};
2451 const unsigned char aYellow[] = {0xff, 0xff, 0x00, 0xff};
2452 constexpr size_t NullTextureDimension = 16;
2453 unsigned char aNullTextureData[NullTextureDimension * NullTextureDimension * PixelSize];
2454 for(size_t y = 0; y < NullTextureDimension; ++y)
2455 {
2456 for(size_t x = 0; x < NullTextureDimension; ++x)
2457 {
2458 const unsigned char *pColor;
2459 if(x < NullTextureDimension / 2 && y < NullTextureDimension / 2)
2460 pColor = aRed;
2461 else if(x >= NullTextureDimension / 2 && y < NullTextureDimension / 2)
2462 pColor = aGreen;
2463 else if(x < NullTextureDimension / 2 && y >= NullTextureDimension / 2)
2464 pColor = aBlue;
2465 else
2466 pColor = aYellow;
2467 mem_copy(dest: &aNullTextureData[(y * NullTextureDimension + x) * PixelSize], source: pColor, size: PixelSize);
2468 }
2469 }
2470 CImageInfo NullTextureInfo;
2471 NullTextureInfo.m_Width = NullTextureDimension;
2472 NullTextureInfo.m_Height = NullTextureDimension;
2473 NullTextureInfo.m_Format = CImageInfo::FORMAT_RGBA;
2474 NullTextureInfo.m_pData = aNullTextureData;
2475 const int TextureLoadFlags = Uses2DTextureArrays() ? IGraphics::TEXLOAD_TO_2D_ARRAY_TEXTURE : IGraphics::TEXLOAD_TO_3D_TEXTURE;
2476 m_NullTexture.Invalidate();
2477 m_NullTexture = LoadTextureRaw(Image: NullTextureInfo, Flags: TextureLoadFlags, pTexName: "null-texture");
2478 dbg_assert(m_NullTexture.IsNullTexture(), "Null texture invalid");
2479 }
2480
2481 ColorRGBA GPUInfoPrintColor{0.6f, 0.5f, 1.0f, 1.0f};
2482
2483 char aBuf[256];
2484 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "GPU vendor: %s", GetVendorString());
2485 m_pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "gfx", pStr: aBuf, PrintColor: GPUInfoPrintColor);
2486
2487 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "GPU renderer: %s", GetRendererString());
2488 m_pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "gfx", pStr: aBuf, PrintColor: GPUInfoPrintColor);
2489
2490 str_format(buffer: aBuf, buffer_size: sizeof(aBuf), format: "GPU version: %s", GetVersionString());
2491 m_pConsole->Print(Level: IConsole::OUTPUT_LEVEL_STANDARD, pFrom: "gfx", pStr: aBuf, PrintColor: GPUInfoPrintColor);
2492
2493 AdjustViewport(SendViewportChangeToBackend: true);
2494
2495 return 0;
2496}
2497
2498void CGraphics_Threaded::Shutdown()
2499{
2500 // shutdown the backend
2501 m_pBackend->Shutdown();
2502 delete m_pBackend;
2503 m_pBackend = nullptr;
2504
2505 // delete the command buffers
2506 for(auto &pCommandBuffer : m_apCommandBuffers)
2507 delete pCommandBuffer;
2508}
2509
2510int CGraphics_Threaded::GetNumScreens() const
2511{
2512 return m_pBackend->GetNumScreens();
2513}
2514
2515const char *CGraphics_Threaded::GetScreenName(int Screen) const
2516{
2517 return m_pBackend->GetScreenName(Screen);
2518}
2519
2520void CGraphics_Threaded::Minimize()
2521{
2522 m_pBackend->Minimize();
2523
2524 for(auto &PropChangedListener : m_vPropChangeListeners)
2525 PropChangedListener();
2526}
2527
2528void CGraphics_Threaded::WarnPngliteIncompatibleImages(bool Warn)
2529{
2530 m_WarnPngliteIncompatibleImages = Warn;
2531}
2532
2533void CGraphics_Threaded::SetWindowParams(int FullscreenMode, bool IsBorderless)
2534{
2535 g_Config.m_GfxFullscreen = std::clamp(val: FullscreenMode, lo: 0, hi: 3);
2536 g_Config.m_GfxBorderless = (int)IsBorderless;
2537
2538 m_pBackend->SetWindowParams(FullscreenMode: g_Config.m_GfxFullscreen, IsBorderless: g_Config.m_GfxBorderless);
2539 CVideoMode CurMode;
2540 m_pBackend->GetCurrentVideoMode(CurMode, HiDPIScale: m_ScreenHiDPIScale, MaxWindowWidth: g_Config.m_GfxDesktopWidth, MaxWindowHeight: g_Config.m_GfxDesktopHeight, Screen: g_Config.m_GfxScreen);
2541 GotResized(w: CurMode.m_WindowWidth, h: CurMode.m_WindowHeight, RefreshRate: CurMode.m_RefreshRate);
2542
2543 for(auto &PropChangedListener : m_vPropChangeListeners)
2544 PropChangedListener();
2545}
2546
2547bool CGraphics_Threaded::SetWindowScreen(int Index, bool MoveToCenter)
2548{
2549 if(!m_pBackend->SetWindowScreen(Index, MoveToCenter))
2550 {
2551 return false;
2552 }
2553
2554 // send a got resized event so that the current canvas size is requested
2555 GotResized(w: g_Config.m_GfxScreenWidth, h: g_Config.m_GfxScreenHeight, RefreshRate: g_Config.m_GfxScreenRefreshRate);
2556
2557 for(auto &PropChangedListener : m_vPropChangeListeners)
2558 PropChangedListener();
2559
2560 return true;
2561}
2562
2563bool CGraphics_Threaded::SwitchWindowScreen(int Index, bool MoveToCenter)
2564{
2565 const int IsFullscreen = g_Config.m_GfxFullscreen;
2566 const int IsBorderless = g_Config.m_GfxBorderless;
2567 const bool IsPurelyWindowed = IsFullscreen == 0 && !IsBorderless;
2568
2569 if(!SetWindowScreen(Index, MoveToCenter: !IsPurelyWindowed || MoveToCenter))
2570 {
2571 return false;
2572 }
2573
2574 if(IsFullscreen != 3 && !IsPurelyWindowed)
2575 {
2576 // Prevent window from being stretched over multiple monitors by temporarily switching to
2577 // windowed fullscreen mode on Windows, which is desktop fullscreen mode on other systems.
2578 SetWindowParams(FullscreenMode: 3, IsBorderless: false);
2579 }
2580
2581 // In purely windowed mode we preserve the window's size instead of resizing to the screen.
2582 if(!IsPurelyWindowed)
2583 {
2584 CVideoMode CurMode;
2585 GetCurrentVideoMode(CurMode, Screen: Index);
2586
2587 g_Config.m_GfxColorDepth = CurMode.m_Red + CurMode.m_Green + CurMode.m_Blue > 16 ? 24 : 16;
2588 g_Config.m_GfxScreenWidth = CurMode.m_WindowWidth;
2589 g_Config.m_GfxScreenHeight = CurMode.m_WindowHeight;
2590 g_Config.m_GfxScreenRefreshRate = CurMode.m_RefreshRate;
2591
2592 ResizeToScreen();
2593 }
2594
2595 SetWindowParams(FullscreenMode: IsFullscreen, IsBorderless);
2596 return true;
2597}
2598
2599void CGraphics_Threaded::Move(int x, int y)
2600{
2601#if defined(CONF_VIDEORECORDER)
2602 if(IVideo::Current() && IVideo::Current()->IsRecording())
2603 return;
2604#endif
2605
2606 // Only handling CurScreen != m_GfxScreen doesn't work reliably
2607 const int CurScreen = m_pBackend->GetWindowScreen();
2608 m_pBackend->UpdateDisplayMode(Index: CurScreen);
2609
2610 // send a got resized event so that the current canvas size is requested
2611 GotResized(w: g_Config.m_GfxScreenWidth, h: g_Config.m_GfxScreenHeight, RefreshRate: g_Config.m_GfxScreenRefreshRate);
2612
2613 for(auto &PropChangedListener : m_vPropChangeListeners)
2614 PropChangedListener();
2615}
2616
2617bool CGraphics_Threaded::Resize(int w, int h, int RefreshRate)
2618{
2619#if defined(CONF_VIDEORECORDER)
2620 if(IVideo::Current() && IVideo::Current()->IsRecording())
2621 return false;
2622#endif
2623
2624 if(WindowWidth() == w && WindowHeight() == h && RefreshRate == m_ScreenRefreshRate)
2625 return false;
2626
2627 // if the size is changed manually, only set the window resize, a window size changed event is triggered anyway
2628 if(m_pBackend->ResizeWindow(w, h, RefreshRate))
2629 {
2630 CVideoMode CurMode;
2631 m_pBackend->GetCurrentVideoMode(CurMode, HiDPIScale: m_ScreenHiDPIScale, MaxWindowWidth: g_Config.m_GfxDesktopWidth, MaxWindowHeight: g_Config.m_GfxDesktopHeight, Screen: g_Config.m_GfxScreen);
2632 GotResized(w, h, RefreshRate);
2633 return true;
2634 }
2635 return false;
2636}
2637
2638void CGraphics_Threaded::ResizeToScreen()
2639{
2640 if(Resize(w: g_Config.m_GfxScreenWidth, h: g_Config.m_GfxScreenHeight, RefreshRate: g_Config.m_GfxScreenRefreshRate))
2641 return;
2642
2643 // Revert config variables if the change was not accepted
2644 g_Config.m_GfxScreenWidth = ScreenWidth();
2645 g_Config.m_GfxScreenHeight = ScreenHeight();
2646 g_Config.m_GfxScreenRefreshRate = m_ScreenRefreshRate;
2647}
2648
2649void CGraphics_Threaded::GotResized(int w, int h, int RefreshRate)
2650{
2651#if defined(CONF_VIDEORECORDER)
2652 if(IVideo::Current() && IVideo::Current()->IsRecording())
2653 return;
2654#endif
2655
2656 // if RefreshRate is -1 use the current config refresh rate
2657 if(RefreshRate == -1)
2658 RefreshRate = g_Config.m_GfxScreenRefreshRate;
2659
2660 // if the size change event is triggered, set all parameters and change the viewport
2661 auto PrevCanvasWidth = m_ScreenWidth;
2662 auto PrevCanvasHeight = m_ScreenHeight;
2663 m_pBackend->GetViewportSize(w&: m_ScreenWidth, h&: m_ScreenHeight);
2664
2665 AdjustViewport(SendViewportChangeToBackend: false);
2666
2667 m_ScreenRefreshRate = RefreshRate;
2668
2669 g_Config.m_GfxScreenWidth = w;
2670 g_Config.m_GfxScreenHeight = h;
2671 g_Config.m_GfxScreenRefreshRate = m_ScreenRefreshRate;
2672
2673 auto OldDpi = m_ScreenHiDPIScale;
2674 m_ScreenHiDPIScale = m_ScreenWidth / (float)g_Config.m_GfxScreenWidth;
2675
2676 // A DPI change must notify the listeners, since e.g. video modes
2677 // currently depend on it.
2678 if(OldDpi != m_ScreenHiDPIScale)
2679 {
2680 for(auto &PropChangedListener : m_vPropChangeListeners)
2681 PropChangedListener();
2682 }
2683
2684 UpdateViewport(X: 0, Y: 0, W: m_ScreenWidth, H: m_ScreenHeight, ByResize: true);
2685
2686 // kick the command buffer and wait
2687 KickCommandBuffer();
2688 WaitForIdle();
2689
2690 if(PrevCanvasWidth != m_ScreenWidth || PrevCanvasHeight != m_ScreenHeight)
2691 {
2692 for(auto &ResizeListener : m_vResizeListeners)
2693 ResizeListener();
2694 }
2695}
2696
2697bool CGraphics_Threaded::IsScreenKeyboardShown()
2698{
2699 return m_pBackend->IsScreenKeyboardShown();
2700}
2701
2702void CGraphics_Threaded::AddWindowResizeListener(WINDOW_RESIZE_FUNC pFunc)
2703{
2704 m_vResizeListeners.emplace_back(args&: pFunc);
2705}
2706
2707void CGraphics_Threaded::AddWindowPropChangeListener(WINDOW_PROPS_CHANGED_FUNC pFunc)
2708{
2709 m_vPropChangeListeners.emplace_back(args&: pFunc);
2710}
2711
2712int CGraphics_Threaded::GetWindowScreen()
2713{
2714 return m_pBackend->GetWindowScreen();
2715}
2716
2717void CGraphics_Threaded::WindowDestroyNtf(uint32_t WindowId)
2718{
2719 m_pBackend->WindowDestroyNtf(WindowId);
2720
2721 CCommandBuffer::SCommand_WindowDestroyNtf Cmd;
2722 Cmd.m_WindowId = WindowId;
2723 AddCmd(Cmd);
2724
2725 // wait
2726 KickCommandBuffer();
2727 WaitForIdle();
2728}
2729
2730void CGraphics_Threaded::WindowCreateNtf(uint32_t WindowId)
2731{
2732 m_pBackend->WindowCreateNtf(WindowId);
2733
2734 CCommandBuffer::SCommand_WindowCreateNtf Cmd;
2735 Cmd.m_WindowId = WindowId;
2736 AddCmd(Cmd);
2737
2738 // wait
2739 KickCommandBuffer();
2740 WaitForIdle();
2741}
2742
2743int CGraphics_Threaded::WindowActive()
2744{
2745 return m_pBackend->WindowActive();
2746}
2747
2748int CGraphics_Threaded::WindowOpen()
2749{
2750 return m_pBackend->WindowOpen();
2751}
2752
2753void CGraphics_Threaded::SetWindowGrab(bool Grab)
2754{
2755 m_pBackend->SetWindowGrab(Grab);
2756}
2757
2758void CGraphics_Threaded::NotifyWindow()
2759{
2760 m_pBackend->NotifyWindow();
2761}
2762
2763void CGraphics_Threaded::ReadPixel(ivec2 Position, ColorRGBA *pColor)
2764{
2765 dbg_assert(Position.x >= 0 && Position.x < ScreenWidth(), "ReadPixel position x out of range");
2766 dbg_assert(Position.y >= 0 && Position.y < ScreenHeight(), "ReadPixel position y out of range");
2767
2768 m_ReadPixelPosition = Position;
2769 m_pReadPixelColor = pColor;
2770}
2771
2772void CGraphics_Threaded::ReadPixelDirect(bool *pSwapped)
2773{
2774 if(m_pReadPixelColor == nullptr)
2775 return;
2776
2777 CCommandBuffer::SCommand_TrySwapAndReadPixel Cmd;
2778 Cmd.m_Position = m_ReadPixelPosition;
2779 Cmd.m_pColor = m_pReadPixelColor;
2780 Cmd.m_pSwapped = pSwapped;
2781 AddCmd(Cmd);
2782
2783 KickCommandBuffer();
2784 WaitForIdle();
2785
2786 m_pReadPixelColor = nullptr;
2787}
2788
2789void CGraphics_Threaded::TakeScreenshot(const char *pFilename)
2790{
2791 // TODO: screenshot support
2792 char aDate[20];
2793 str_timestamp(buffer: aDate, buffer_size: sizeof(aDate));
2794 str_format(buffer: m_aScreenshotName, buffer_size: sizeof(m_aScreenshotName), format: "screenshots/%s_%s.png", pFilename ? pFilename : "screenshot", aDate);
2795 m_DoScreenshot = true;
2796}
2797
2798void CGraphics_Threaded::TakeCustomScreenshot(const char *pFilename)
2799{
2800 str_copy(dst&: m_aScreenshotName, src: pFilename);
2801 m_DoScreenshot = true;
2802}
2803
2804void CGraphics_Threaded::Swap()
2805{
2806 bool Swapped = false;
2807 ScreenshotDirect(pSwapped: &Swapped);
2808 ReadPixelDirect(pSwapped: &Swapped);
2809
2810 if(!Swapped)
2811 {
2812 CCommandBuffer::SCommand_Swap Cmd;
2813 AddCmd(Cmd);
2814 }
2815
2816 KickCommandBuffer();
2817 // TODO: Remove when https://github.com/libsdl-org/SDL/issues/5203 is fixed
2818#ifdef CONF_PLATFORM_MACOS
2819 if(str_find(GetVersionString(), "Metal"))
2820 WaitForIdle();
2821#endif
2822}
2823
2824bool CGraphics_Threaded::SetVSync(bool State)
2825{
2826 if(!m_pCommandBuffer)
2827 return true;
2828
2829 const bool OldState = State;
2830
2831 // add vsync command
2832 bool RetOk = false;
2833 CCommandBuffer::SCommand_VSync Cmd;
2834 Cmd.m_VSync = State ? 1 : 0;
2835 Cmd.m_pRetOk = &RetOk;
2836 AddCmd(Cmd);
2837
2838 // kick the command buffer
2839 KickCommandBuffer();
2840 WaitForIdle();
2841
2842 g_Config.m_GfxVsync = RetOk ? State : OldState;
2843 return RetOk;
2844}
2845
2846bool CGraphics_Threaded::SetMultiSampling(uint32_t ReqMultiSamplingCount, uint32_t &MultiSamplingCountBackend)
2847{
2848 if(!m_pCommandBuffer)
2849 return true;
2850
2851 // add multisampling command
2852 bool RetOk = false;
2853 CCommandBuffer::SCommand_MultiSampling Cmd;
2854 Cmd.m_RequestedMultiSamplingCount = ReqMultiSamplingCount;
2855 Cmd.m_pRetMultiSamplingCount = &MultiSamplingCountBackend;
2856 Cmd.m_pRetOk = &RetOk;
2857 AddCmd(Cmd);
2858
2859 // kick the command buffer
2860 KickCommandBuffer();
2861 WaitForIdle();
2862 return RetOk;
2863}
2864
2865// synchronization
2866void CGraphics_Threaded::InsertSignal(CSemaphore *pSemaphore)
2867{
2868 CCommandBuffer::SCommand_Signal Cmd;
2869 Cmd.m_pSemaphore = pSemaphore;
2870 AddCmd(Cmd);
2871}
2872
2873bool CGraphics_Threaded::IsIdle() const
2874{
2875 return m_pBackend->IsIdle();
2876}
2877
2878void CGraphics_Threaded::WaitForIdle()
2879{
2880 m_pBackend->WaitForIdle();
2881}
2882
2883void CGraphics_Threaded::AddWarning(const SWarning &Warning)
2884{
2885 const std::unique_lock<std::mutex> Lock(m_WarningsMutex);
2886 m_vWarnings.emplace_back(args: Warning);
2887}
2888
2889std::optional<SWarning> CGraphics_Threaded::CurrentWarning()
2890{
2891 const std::unique_lock<std::mutex> Lock(m_WarningsMutex);
2892 if(m_vWarnings.empty())
2893 {
2894 return std::nullopt;
2895 }
2896 else
2897 {
2898 std::optional<SWarning> Result = std::make_optional(t&: m_vWarnings[0]);
2899 m_vWarnings.erase(position: m_vWarnings.begin());
2900 return Result;
2901 }
2902}
2903
2904std::optional<int> CGraphics_Threaded::ShowMessageBox(const CMessageBox &MessageBox)
2905{
2906 if(m_pBackend == nullptr)
2907 {
2908 return std::nullopt;
2909 }
2910 m_pBackend->WaitForIdle();
2911 return m_pBackend->ShowMessageBox(MessageBox);
2912}
2913
2914bool CGraphics_Threaded::IsBackendInitialized()
2915{
2916 return m_pBackend != nullptr;
2917}
2918
2919const char *CGraphics_Threaded::GetVendorString()
2920{
2921 return m_pBackend->GetVendorString();
2922}
2923
2924const char *CGraphics_Threaded::GetVersionString()
2925{
2926 return m_pBackend->GetVersionString();
2927}
2928
2929const char *CGraphics_Threaded::GetRendererString()
2930{
2931 return m_pBackend->GetRendererString();
2932}
2933
2934TGLBackendReadPresentedImageData &CGraphics_Threaded::GetReadPresentedImageDataFuncUnsafe()
2935{
2936 return m_pBackend->GetReadPresentedImageDataFuncUnsafe();
2937}
2938
2939int CGraphics_Threaded::GetVideoModes(CVideoMode *pModes, int MaxModes, int Screen)
2940{
2941 if(g_Config.m_GfxDisplayAllVideoModes)
2942 {
2943 const int Count = minimum<size_t>(a: std::size(g_aFakeModes), b: MaxModes);
2944 mem_copy(dest: pModes, source: g_aFakeModes, size: Count * sizeof(CVideoMode));
2945 return Count;
2946 }
2947
2948 int NumModes = 0;
2949 m_pBackend->GetVideoModes(pModes, MaxModes, pNumModes: &NumModes, HiDPIScale: m_ScreenHiDPIScale, MaxWindowWidth: g_Config.m_GfxDesktopWidth, MaxWindowHeight: g_Config.m_GfxDesktopHeight, Screen);
2950 return NumModes;
2951}
2952
2953void CGraphics_Threaded::GetCurrentVideoMode(CVideoMode &CurMode, int Screen)
2954{
2955 m_pBackend->GetCurrentVideoMode(CurMode, HiDPIScale: m_ScreenHiDPIScale, MaxWindowWidth: g_Config.m_GfxDesktopWidth, MaxWindowHeight: g_Config.m_GfxDesktopHeight, Screen);
2956}
2957
2958extern IEngineGraphics *CreateEngineGraphicsThreaded()
2959{
2960 return new CGraphics_Threaded();
2961}
2962