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