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