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