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