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