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