1#include "render_layer.h"
2
3#include <base/log.h>
4
5#include <engine/graphics.h>
6#include <engine/map.h>
7#include <engine/shared/config.h>
8#include <engine/storage.h>
9
10#include <game/localization.h>
11#include <game/mapitems.h>
12
13#include <array>
14#include <chrono>
15
16/************************
17 * Render Buffer Helper *
18 ************************/
19class CTexCoords
20{
21public:
22 std::array<uint8_t, 4> m_aTexX;
23 std::array<uint8_t, 4> m_aTexY;
24};
25
26constexpr static CTexCoords CalculateTexCoords(unsigned int Flags)
27{
28 CTexCoords TexCoord;
29 TexCoord.m_aTexX = {0, 1, 1, 0};
30 TexCoord.m_aTexY = {0, 0, 1, 1};
31
32 if(Flags & TILEFLAG_XFLIP)
33 std::rotate(first: std::begin(cont&: TexCoord.m_aTexX), middle: std::begin(cont&: TexCoord.m_aTexX) + 2, last: std::end(cont&: TexCoord.m_aTexX));
34
35 if(Flags & TILEFLAG_YFLIP)
36 std::rotate(first: std::begin(cont&: TexCoord.m_aTexY), middle: std::begin(cont&: TexCoord.m_aTexY) + 2, last: std::end(cont&: TexCoord.m_aTexY));
37
38 if(Flags & (TILEFLAG_ROTATE >> 1))
39 {
40 std::rotate(first: std::begin(cont&: TexCoord.m_aTexX), middle: std::begin(cont&: TexCoord.m_aTexX) + 3, last: std::end(cont&: TexCoord.m_aTexX));
41 std::rotate(first: std::begin(cont&: TexCoord.m_aTexY), middle: std::begin(cont&: TexCoord.m_aTexY) + 3, last: std::end(cont&: TexCoord.m_aTexY));
42 }
43 return TexCoord;
44}
45
46template<std::size_t N>
47constexpr static std::array<CTexCoords, N> MakeTexCoordsTable()
48{
49 std::array<CTexCoords, N> aTexCoords = {};
50 for(std::size_t i = 0; i < N; ++i)
51 aTexCoords[i] = CalculateTexCoords(Flags: i);
52 return aTexCoords;
53}
54
55constexpr std::array<CTexCoords, 8> TEX_COORDS_TABLE = MakeTexCoordsTable<8>();
56
57static void FillTmpTile(CGraphicTile *pTmpTile, CGraphicTileTextureCoords *pTmpTex, unsigned char Flags, unsigned char Index, int x, int y, const ivec2 &Offset, int Scale)
58{
59 if(pTmpTex)
60 {
61 uint8_t TableFlag = (Flags & (TILEFLAG_XFLIP | TILEFLAG_YFLIP)) + ((Flags & TILEFLAG_ROTATE) >> 1);
62 const auto &aTexX = TEX_COORDS_TABLE[TableFlag].m_aTexX;
63 const auto &aTexY = TEX_COORDS_TABLE[TableFlag].m_aTexY;
64
65 pTmpTex->m_TexCoordTopLeft.x = aTexX[0];
66 pTmpTex->m_TexCoordTopLeft.y = aTexY[0];
67 pTmpTex->m_TexCoordBottomLeft.x = aTexX[3];
68 pTmpTex->m_TexCoordBottomLeft.y = aTexY[3];
69 pTmpTex->m_TexCoordTopRight.x = aTexX[1];
70 pTmpTex->m_TexCoordTopRight.y = aTexY[1];
71 pTmpTex->m_TexCoordBottomRight.x = aTexX[2];
72 pTmpTex->m_TexCoordBottomRight.y = aTexY[2];
73
74 pTmpTex->m_TexCoordTopLeft.z = Index;
75 pTmpTex->m_TexCoordBottomLeft.z = Index;
76 pTmpTex->m_TexCoordTopRight.z = Index;
77 pTmpTex->m_TexCoordBottomRight.z = Index;
78
79 bool HasRotation = (Flags & TILEFLAG_ROTATE) != 0;
80 pTmpTex->m_TexCoordTopLeft.w = HasRotation;
81 pTmpTex->m_TexCoordBottomLeft.w = HasRotation;
82 pTmpTex->m_TexCoordTopRight.w = HasRotation;
83 pTmpTex->m_TexCoordBottomRight.w = HasRotation;
84 }
85
86 vec2 TopLeft(x * Scale + Offset.x, y * Scale + Offset.y);
87 vec2 BottomRight(x * Scale + Scale + Offset.x, y * Scale + Scale + Offset.y);
88 pTmpTile->m_TopLeft = TopLeft;
89 pTmpTile->m_BottomLeft.x = TopLeft.x;
90 pTmpTile->m_BottomLeft.y = BottomRight.y;
91 pTmpTile->m_TopRight.x = BottomRight.x;
92 pTmpTile->m_TopRight.y = TopLeft.y;
93 pTmpTile->m_BottomRight = BottomRight;
94}
95
96static void FillTmpTileSpeedup(CGraphicTile *pTmpTile, CGraphicTileTextureCoords *pTmpTex, unsigned char Flags, int x, int y, const ivec2 &Offset, int Scale, short AngleRotate)
97{
98 int Angle = AngleRotate % 360;
99 FillTmpTile(pTmpTile, pTmpTex, Flags: Angle >= 270 ? ROTATION_270 : (Angle >= 180 ? ROTATION_180 : (Angle >= 90 ? ROTATION_90 : 0)), Index: AngleRotate % 90, x, y, Offset, Scale);
100}
101
102static bool AddTile(std::vector<CGraphicTile> &vTmpTiles, std::vector<CGraphicTileTextureCoords> &vTmpTileTexCoords, unsigned char Index, unsigned char Flags, int x, int y, bool DoTextureCoords, bool FillSpeedup = false, int AngleRotate = -1, const ivec2 &Offset = ivec2{0, 0}, int Scale = 32)
103{
104 if(Index <= 0)
105 return false;
106
107 vTmpTiles.emplace_back();
108 CGraphicTile &Tile = vTmpTiles.back();
109 CGraphicTileTextureCoords *pTileTex = nullptr;
110 if(DoTextureCoords)
111 {
112 vTmpTileTexCoords.emplace_back();
113 CGraphicTileTextureCoords &TileTex = vTmpTileTexCoords.back();
114 pTileTex = &TileTex;
115 }
116 if(FillSpeedup)
117 FillTmpTileSpeedup(pTmpTile: &Tile, pTmpTex: pTileTex, Flags, x, y, Offset, Scale, AngleRotate);
118 else
119 FillTmpTile(pTmpTile: &Tile, pTmpTex: pTileTex, Flags, Index, x, y, Offset, Scale);
120
121 return true;
122}
123
124class CTmpQuadVertexTextured
125{
126public:
127 float m_X, m_Y, m_CenterX, m_CenterY;
128 unsigned char m_R, m_G, m_B, m_A;
129 float m_U, m_V;
130};
131
132class CTmpQuadVertex
133{
134public:
135 float m_X, m_Y, m_CenterX, m_CenterY;
136 unsigned char m_R, m_G, m_B, m_A;
137};
138
139class CTmpQuad
140{
141public:
142 CTmpQuadVertex m_aVertices[4];
143};
144
145class CTmpQuadTextured
146{
147public:
148 CTmpQuadVertexTextured m_aVertices[4];
149};
150
151static void mem_copy_special(void *pDest, void *pSource, size_t Size, size_t Count, size_t Steps)
152{
153 size_t CurStep = 0;
154 for(size_t i = 0; i < Count; ++i)
155 {
156 mem_copy(dest: ((char *)pDest) + CurStep + i * Size, source: ((char *)pSource) + i * Size, size: Size);
157 CurStep += Steps;
158 }
159}
160
161bool CRenderLayerTile::CTileLayerVisuals::Init(unsigned int Width, unsigned int Height)
162{
163 m_Width = Width;
164 m_Height = Height;
165 if(Width == 0 || Height == 0)
166 return false;
167 if constexpr(sizeof(unsigned int) >= sizeof(ptrdiff_t))
168 if(Width >= std::numeric_limits<std::ptrdiff_t>::max() || Height >= std::numeric_limits<std::ptrdiff_t>::max())
169 return false;
170
171 m_vTilesOfLayer.resize(new_size: (size_t)Height * (size_t)Width);
172
173 m_vBorderTop.resize(new_size: Width);
174 m_vBorderBottom.resize(new_size: Width);
175
176 m_vBorderLeft.resize(new_size: Height);
177 m_vBorderRight.resize(new_size: Height);
178 return true;
179}
180
181/**************
182 * Base Layer *
183 **************/
184
185CRenderLayer::CRenderLayer(int GroupId, int LayerId, int Flags) :
186 m_GroupId(GroupId), m_LayerId(LayerId), m_Flags(Flags) {}
187
188void CRenderLayer::OnInit(IGraphics *pGraphics, ITextRender *pTextRender, CRenderMap *pRenderMap, std::shared_ptr<CEnvelopeManager> &pEnvelopeManager, IMap *pMap, IMapImages *pMapImages, std::optional<FRenderUploadCallback> &FRenderUploadCallbackOptional)
189{
190 CRenderComponent::OnInit(pGraphics, pTextRender, pRenderMap);
191 m_pMap = pMap;
192 m_pMapImages = pMapImages;
193 m_RenderUploadCallback = FRenderUploadCallbackOptional;
194 m_pEnvelopeManager = pEnvelopeManager;
195}
196
197void CRenderLayer::UseTexture(IGraphics::CTextureHandle TextureHandle)
198{
199 if(TextureHandle.IsValid())
200 Graphics()->TextureSet(Texture: TextureHandle);
201 else
202 Graphics()->TextureClear();
203}
204
205void CRenderLayer::RenderLoading() const
206{
207 const char *pLoadingTitle = Localize(pStr: "Loading map");
208 const char *pLoadingMessage = Localize(pStr: "Uploading map data to GPU");
209 if(m_RenderUploadCallback.has_value())
210 (*m_RenderUploadCallback)(pLoadingTitle, pLoadingMessage, 0);
211}
212
213bool CRenderLayer::IsVisibleInClipRegion(const std::optional<CClipRegion> &ClipRegion) const
214{
215 // always show unclipped regions
216 if(!ClipRegion.has_value())
217 return true;
218
219 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
220 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
221 float Left = ClipRegion->m_X;
222 float Top = ClipRegion->m_Y;
223 float Right = ClipRegion->m_X + ClipRegion->m_Width;
224 float Bottom = ClipRegion->m_Y + ClipRegion->m_Height;
225
226 return Right >= ScreenX0 && Left <= ScreenX1 && Bottom >= ScreenY0 && Top <= ScreenY1;
227}
228
229/**************
230 * Group *
231 **************/
232
233CRenderLayerGroup::CRenderLayerGroup(int GroupId, CMapItemGroup *pGroup) :
234 CRenderLayer(GroupId, 0, 0), m_pGroup(pGroup) {}
235
236bool CRenderLayerGroup::DoRender(const CRenderLayerParams &Params)
237{
238 if(!g_Config.m_GfxNoclip || Params.m_RenderType == ERenderType::RENDERTYPE_FULL_DESIGN)
239 {
240 Graphics()->ClipDisable();
241 if(m_pGroup->m_Version >= 2 && m_pGroup->m_UseClipping)
242 {
243 // set clipping
244 Graphics()->MapScreenToInterface(CenterX: Params.m_Center.x, CenterY: Params.m_Center.y, Zoom: Params.m_Zoom);
245
246 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
247 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
248 float ScreenWidth = ScreenX1 - ScreenX0;
249 float ScreenHeight = ScreenY1 - ScreenY0;
250 float Left = m_pGroup->m_ClipX - ScreenX0;
251 float Top = m_pGroup->m_ClipY - ScreenY0;
252 float Right = m_pGroup->m_ClipX + m_pGroup->m_ClipW - ScreenX0;
253 float Bottom = m_pGroup->m_ClipY + m_pGroup->m_ClipH - ScreenY0;
254
255 if(Right < 0.0f || Left > ScreenWidth || Bottom < 0.0f || Top > ScreenHeight)
256 return false;
257
258 // Render debug before enabling the clip
259 if(Params.m_DebugRenderGroupClips)
260 {
261 char aDebugText[32];
262 str_format(buffer: aDebugText, buffer_size: sizeof(aDebugText), format: "Group %d", m_GroupId);
263 RenderMap()->RenderDebugClip(ClipX: m_pGroup->m_ClipX, ClipY: m_pGroup->m_ClipY, ClipW: m_pGroup->m_ClipW, ClipH: m_pGroup->m_ClipH, Color: ColorRGBA(1.0f, 0.0f, 0.0f, 1.0f), Zoom: Params.m_Zoom, pLabel: aDebugText);
264 }
265
266 int ClipX = (int)std::round(x: Left * Graphics()->ScreenWidth() / ScreenWidth);
267 int ClipY = (int)std::round(x: Top * Graphics()->ScreenHeight() / ScreenHeight);
268
269 Graphics()->ClipEnable(
270 x: ClipX,
271 y: ClipY,
272 w: (int)std::round(x: Right * Graphics()->ScreenWidth() / ScreenWidth) - ClipX,
273 h: (int)std::round(x: Bottom * Graphics()->ScreenHeight() / ScreenHeight) - ClipY);
274 }
275 }
276 return true;
277}
278
279void CRenderLayerGroup::Render(const CRenderLayerParams &Params)
280{
281 int ParallaxZoom = std::clamp(val: (maximum(a: m_pGroup->m_ParallaxX, b: m_pGroup->m_ParallaxY)), lo: 0, hi: 100);
282 float aPoints[4];
283 Graphics()->MapScreenToWorld(CenterX: Params.m_Center.x, CenterY: Params.m_Center.y, ParallaxX: m_pGroup->m_ParallaxX, ParallaxY: m_pGroup->m_ParallaxY, ParallaxZoom: (float)ParallaxZoom,
284 OffsetX: m_pGroup->m_OffsetX, OffsetY: m_pGroup->m_OffsetY, Aspect: Graphics()->ScreenAspect(), Zoom: Params.m_Zoom, pPoints: aPoints);
285 Graphics()->MapScreen(TopLeftX: aPoints[0], TopLeftY: aPoints[1], BottomRightX: aPoints[2], BottomRightY: aPoints[3]);
286}
287
288/**************
289 * Tile Layer *
290 **************/
291
292CRenderLayerTile::CRenderLayerTile(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
293 CRenderLayer(GroupId, LayerId, Flags)
294{
295 m_pLayerTilemap = pLayerTilemap;
296 m_Color = ColorRGBA(m_pLayerTilemap->m_Color.r / 255.0f, m_pLayerTilemap->m_Color.g / 255.0f, m_pLayerTilemap->m_Color.b / 255.0f, pLayerTilemap->m_Color.a / 255.0f);
297 m_pTiles = nullptr;
298}
299
300void CRenderLayerTile::RenderTileLayer(const ColorRGBA &Color, const CRenderLayerParams &Params, CTileLayerVisuals *pTileLayerVisuals)
301{
302 CTileLayerVisuals &Visuals = pTileLayerVisuals ? *pTileLayerVisuals : m_VisualTiles.value();
303 if(Visuals.m_BufferContainerIndex == -1)
304 return; // no visuals were created
305
306 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
307 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
308
309 int ScreenRectY0 = std::floor(x: ScreenY0 / 32);
310 int ScreenRectX0 = std::floor(x: ScreenX0 / 32);
311 int ScreenRectY1 = std::ceil(x: ScreenY1 / 32);
312 int ScreenRectX1 = std::ceil(x: ScreenX1 / 32);
313
314 if(IsVisibleInClipRegion(ClipRegion: m_LayerClip))
315 {
316 // create the indice buffers we want to draw -- reuse them
317 std::vector<char *> vpIndexOffsets;
318 std::vector<unsigned int> vDrawCounts;
319
320 int X0 = std::max(a: ScreenRectX0, b: 0);
321 int X1 = std::min(a: ScreenRectX1, b: (int)Visuals.m_Width);
322 int XR = X1 - 1;
323 if(X0 <= XR)
324 {
325 int Y0 = std::max(a: ScreenRectY0, b: 0);
326 int Y1 = std::min(a: ScreenRectY1, b: (int)Visuals.m_Height);
327
328 unsigned long long Reserve = absolute(a: Y1 - Y0) + 1;
329 vpIndexOffsets.reserve(n: Reserve);
330 vDrawCounts.reserve(n: Reserve);
331
332 for(int y = Y0; y < Y1; ++y)
333 {
334 dbg_assert(Visuals.m_vTilesOfLayer[y * Visuals.m_Width + XR].IndexBufferByteOffset() >= Visuals.m_vTilesOfLayer[y * Visuals.m_Width + X0].IndexBufferByteOffset(), "Tile offsets are not monotone.");
335 unsigned int NumVertices = ((Visuals.m_vTilesOfLayer[y * Visuals.m_Width + XR].IndexBufferByteOffset() - Visuals.m_vTilesOfLayer[y * Visuals.m_Width + X0].IndexBufferByteOffset()) / sizeof(unsigned int)) + (Visuals.m_vTilesOfLayer[y * Visuals.m_Width + XR].DoDraw() ? 6lu : 0lu);
336
337 if(NumVertices)
338 {
339 vpIndexOffsets.push_back(x: (offset_ptr_size)Visuals.m_vTilesOfLayer[y * Visuals.m_Width + X0].IndexBufferByteOffset());
340 vDrawCounts.push_back(x: NumVertices);
341 }
342 }
343
344 int DrawCount = vpIndexOffsets.size();
345 if(DrawCount != 0)
346 {
347 Graphics()->RenderTileLayer(BufferContainerIndex: Visuals.m_BufferContainerIndex, Color, pOffsets: vpIndexOffsets.data(), pIndicedVertexDrawNum: vDrawCounts.data(), NumIndicesOffset: DrawCount);
348 }
349 }
350 }
351
352 if(Params.m_RenderTileBorder && (ScreenRectX1 > (int)Visuals.m_Width || ScreenRectY1 > (int)Visuals.m_Height || ScreenRectX0 < 0 || ScreenRectY0 < 0))
353 {
354 RenderTileBorder(Color, BorderX0: ScreenRectX0, BorderY0: ScreenRectY0, BorderX1: ScreenRectX1, BorderY1: ScreenRectY1, pTileLayerVisuals: &Visuals);
355 }
356
357 if(Params.m_DebugRenderTileClips && m_LayerClip.has_value())
358 {
359 const CClipRegion &Clip = m_LayerClip.value();
360 char aDebugText[32];
361 str_format(buffer: aDebugText, buffer_size: sizeof(aDebugText), format: "Group %d LayerId %d", m_GroupId, m_LayerId);
362 RenderMap()->RenderDebugClip(ClipX: Clip.m_X, ClipY: Clip.m_Y, ClipW: Clip.m_Width, ClipH: Clip.m_Height, Color: ColorRGBA(1.0f, 0.5f, 0.0f, 1.0f), Zoom: Params.m_Zoom, pLabel: aDebugText);
363 }
364}
365
366void CRenderLayerTile::RenderTileBorder(const ColorRGBA &Color, int BorderX0, int BorderY0, int BorderX1, int BorderY1, CTileLayerVisuals *pTileLayerVisuals)
367{
368 CTileLayerVisuals &Visuals = *pTileLayerVisuals;
369
370 int Y0 = std::max(a: 0, b: BorderY0);
371 int X0 = std::max(a: 0, b: BorderX0);
372 int Y1 = std::min(a: (int)Visuals.m_Height, b: BorderY1);
373 int X1 = std::min(a: (int)Visuals.m_Width, b: BorderX1);
374
375 // corners
376 auto DrawCorner = [&](vec2 Offset, vec2 Scale, CTileLayerVisuals::CTileVisual &Visual) {
377 Offset *= 32.0f;
378 Graphics()->RenderBorderTiles(BufferContainerIndex: Visuals.m_BufferContainerIndex, Color, pIndexBufferOffset: (offset_ptr_size)Visual.IndexBufferByteOffset(), Offset, Scale, DrawNum: 1);
379 };
380
381 if(BorderX0 < 0)
382 {
383 // Draw corners on left side
384 if(BorderY0 < 0 && Visuals.m_BorderTopLeft.DoDraw())
385 {
386 DrawCorner(
387 vec2(0, 0),
388 vec2(std::abs(x: BorderX0), std::abs(x: BorderY0)),
389 Visuals.m_BorderTopLeft);
390 }
391 if(BorderY1 > (int)Visuals.m_Height && Visuals.m_BorderBottomLeft.DoDraw())
392 {
393 DrawCorner(
394 vec2(0, Visuals.m_Height),
395 vec2(std::abs(x: BorderX0), BorderY1 - Visuals.m_Height),
396 Visuals.m_BorderBottomLeft);
397 }
398 }
399 if(BorderX1 > (int)Visuals.m_Width)
400 {
401 // Draw corners on right side
402 if(BorderY0 < 0 && Visuals.m_BorderTopRight.DoDraw())
403 {
404 DrawCorner(
405 vec2(Visuals.m_Width, 0),
406 vec2(BorderX1 - Visuals.m_Width, std::abs(x: BorderY0)),
407 Visuals.m_BorderTopRight);
408 }
409 if(BorderY1 > (int)Visuals.m_Height && Visuals.m_BorderBottomRight.DoDraw())
410 {
411 DrawCorner(
412 vec2(Visuals.m_Width, Visuals.m_Height),
413 vec2(BorderX1 - Visuals.m_Width, BorderY1 - Visuals.m_Height),
414 Visuals.m_BorderBottomRight);
415 }
416 }
417
418 // borders
419 auto DrawBorder = [&](vec2 Offset, vec2 Scale, CTileLayerVisuals::CTileVisual &StartVisual, CTileLayerVisuals::CTileVisual &EndVisual) {
420 unsigned int DrawNum = ((EndVisual.IndexBufferByteOffset() - StartVisual.IndexBufferByteOffset()) / (sizeof(unsigned int) * 6)) + (EndVisual.DoDraw() ? 1lu : 0lu);
421 offset_ptr_size pOffset = (offset_ptr_size)StartVisual.IndexBufferByteOffset();
422 Offset *= 32.0f;
423 Graphics()->RenderBorderTiles(BufferContainerIndex: Visuals.m_BufferContainerIndex, Color, pIndexBufferOffset: pOffset, Offset, Scale, DrawNum);
424 };
425
426 if(Y0 < (int)Visuals.m_Height && Y1 > 0)
427 {
428 if(BorderX1 > (int)Visuals.m_Width)
429 {
430 // Draw right border
431 DrawBorder(
432 vec2(Visuals.m_Width, 0),
433 vec2(BorderX1 - Visuals.m_Width, 1.f),
434 Visuals.m_vBorderRight[Y0], Visuals.m_vBorderRight[Y1 - 1]);
435 }
436 if(BorderX0 < 0)
437 {
438 // Draw left border
439 DrawBorder(
440 vec2(0, 0),
441 vec2(std::abs(x: BorderX0), 1),
442 Visuals.m_vBorderLeft[Y0], Visuals.m_vBorderLeft[Y1 - 1]);
443 }
444 }
445
446 if(X0 < (int)Visuals.m_Width && X1 > 0)
447 {
448 if(BorderY0 < 0)
449 {
450 // Draw top border
451 DrawBorder(
452 vec2(0, 0),
453 vec2(1, std::abs(x: BorderY0)),
454 Visuals.m_vBorderTop[X0], Visuals.m_vBorderTop[X1 - 1]);
455 }
456 if(BorderY1 > (int)Visuals.m_Height)
457 {
458 // Draw bottom border
459 DrawBorder(
460 vec2(0, Visuals.m_Height),
461 vec2(1, BorderY1 - Visuals.m_Height),
462 Visuals.m_vBorderBottom[X0], Visuals.m_vBorderBottom[X1 - 1]);
463 }
464 }
465}
466
467void CRenderLayerTile::RenderKillTileBorder(const ColorRGBA &Color)
468{
469 CTileLayerVisuals &Visuals = m_VisualTiles.value();
470 if(Visuals.m_BufferContainerIndex == -1)
471 return; // no visuals were created
472
473 float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
474 Graphics()->GetScreen(pTopLeftX: &ScreenX0, pTopLeftY: &ScreenY0, pBottomRightX: &ScreenX1, pBottomRightY: &ScreenY1);
475
476 int BorderY0 = std::floor(x: ScreenY0 / 32);
477 int BorderX0 = std::floor(x: ScreenX0 / 32);
478 int BorderY1 = std::ceil(x: ScreenY1 / 32);
479 int BorderX1 = std::ceil(x: ScreenX1 / 32);
480
481 if(BorderX0 >= -BorderRenderDistance && BorderY0 >= -BorderRenderDistance && BorderX1 <= (int)Visuals.m_Width + BorderRenderDistance && BorderY1 <= (int)Visuals.m_Height + BorderRenderDistance)
482 return;
483 if(!Visuals.m_BorderKillTile.DoDraw())
484 return;
485
486 BorderX0 = std::clamp(val: BorderX0, lo: -300, hi: (int)Visuals.m_Width + 299);
487 BorderY0 = std::clamp(val: BorderY0, lo: -300, hi: (int)Visuals.m_Height + 299);
488 BorderX1 = std::clamp(val: BorderX1, lo: -300, hi: (int)Visuals.m_Width + 299);
489 BorderY1 = std::clamp(val: BorderY1, lo: -300, hi: (int)Visuals.m_Height + 299);
490
491 auto DrawKillBorder = [&](vec2 Offset, vec2 Scale) {
492 offset_ptr_size pOffset = (offset_ptr_size)Visuals.m_BorderKillTile.IndexBufferByteOffset();
493 Offset *= 32.0f;
494 Graphics()->RenderBorderTiles(BufferContainerIndex: Visuals.m_BufferContainerIndex, Color, pIndexBufferOffset: pOffset, Offset, Scale, DrawNum: 1);
495 };
496
497 // Draw left kill tile border
498 if(BorderX0 < -BorderRenderDistance)
499 {
500 DrawKillBorder(
501 vec2(BorderX0, BorderY0),
502 vec2(-BorderRenderDistance - BorderX0, BorderY1 - BorderY0));
503 }
504 // Draw top kill tile border
505 if(BorderY0 < -BorderRenderDistance)
506 {
507 DrawKillBorder(
508 vec2(std::max(a: BorderX0, b: -BorderRenderDistance), BorderY0),
509 vec2(std::min(a: BorderX1, b: (int)Visuals.m_Width + BorderRenderDistance) - std::max(a: BorderX0, b: -BorderRenderDistance), -BorderRenderDistance - BorderY0));
510 }
511 // Draw right kill tile border
512 if(BorderX1 > (int)Visuals.m_Width + BorderRenderDistance)
513 {
514 DrawKillBorder(
515 vec2(Visuals.m_Width + BorderRenderDistance, BorderY0),
516 vec2(BorderX1 - (Visuals.m_Width + BorderRenderDistance), BorderY1 - BorderY0));
517 }
518 // Draw bottom kill tile border
519 if(BorderY1 > (int)Visuals.m_Height + BorderRenderDistance)
520 {
521 DrawKillBorder(
522 vec2(std::max(a: BorderX0, b: -BorderRenderDistance), Visuals.m_Height + BorderRenderDistance),
523 vec2(std::min(a: BorderX1, b: (int)Visuals.m_Width + BorderRenderDistance) - std::max(a: BorderX0, b: -BorderRenderDistance), BorderY1 - (Visuals.m_Height + BorderRenderDistance)));
524 }
525}
526
527ColorRGBA CRenderLayerTile::GetRenderColor(const CRenderLayerParams &Params) const
528{
529 ColorRGBA Color = m_Color;
530 if(Params.m_EntityOverlayVal && Params.m_RenderType != ERenderType::RENDERTYPE_BACKGROUND_FORCE)
531 Color.a *= (100 - Params.m_EntityOverlayVal) / 100.0f;
532
533 ColorRGBA ColorEnv = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
534 m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(TimeOffsetMillis: m_pLayerTilemap->m_ColorEnvOffset, Env: m_pLayerTilemap->m_ColorEnv, Result&: ColorEnv, Channels: 4);
535 Color = Color.Multiply(Other: ColorEnv);
536 return Color;
537}
538
539void CRenderLayerTile::Render(const CRenderLayerParams &Params)
540{
541 UseTexture(TextureHandle: GetTexture());
542 ColorRGBA Color = GetRenderColor(Params);
543 if(Graphics()->IsTileBufferingEnabled() && Params.m_TileAndQuadBuffering)
544 {
545 RenderTileLayerWithTileBuffer(Color, Params);
546 }
547 else
548 {
549 RenderTileLayerNoTileBuffer(Color, Params);
550 }
551}
552
553bool CRenderLayerTile::DoRender(const CRenderLayerParams &Params)
554{
555 // skip rendering if we render background force, but deactivated tile layer and want to render a tilelayer
556 if(!g_Config.m_ClBackgroundShowTilesLayers && Params.m_RenderType == ERenderType::RENDERTYPE_BACKGROUND_FORCE)
557 return false;
558
559 // skip rendering anything but entities if we only want to render entities
560 if(Params.m_EntityOverlayVal == 100 && Params.m_RenderType != ERenderType::RENDERTYPE_BACKGROUND_FORCE)
561 return false;
562
563 // skip rendering if detail layers if not wanted
564 if(m_Flags & LAYERFLAG_DETAIL && !g_Config.m_GfxHighDetail && Params.m_RenderType != ERenderType::RENDERTYPE_FULL_DESIGN) // detail but no details
565 return false;
566 return true;
567}
568
569void CRenderLayerTile::RenderTileLayerWithTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
570{
571 Graphics()->BlendNormal();
572 RenderTileLayer(Color, Params);
573}
574
575void CRenderLayerTile::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
576{
577 Graphics()->BlendNone();
578 RenderMap()->RenderTilemap(pTiles: m_pTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_OPAQUE);
579 Graphics()->BlendNormal();
580 RenderMap()->RenderTilemap(pTiles: m_pTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_TRANSPARENT);
581}
582
583void CRenderLayerTile::Init()
584{
585 if(m_pLayerTilemap->m_Image >= 0 && m_pLayerTilemap->m_Image < m_pMapImages->Num())
586 m_TextureHandle = m_pMapImages->Get(Index: m_pLayerTilemap->m_Image);
587 else
588 m_TextureHandle.Invalidate();
589 UploadTileData(VisualsOptional&: m_VisualTiles, CurOverlay: 0, AddAsSpeedup: false);
590}
591
592void CRenderLayerTile::UploadTileData(std::optional<CTileLayerVisuals> &VisualsOptional, int CurOverlay, bool AddAsSpeedup, bool IsGameLayer)
593{
594 if(!Graphics()->IsTileBufferingEnabled())
595 return;
596
597 // prepare all visuals for all tile layers
598 std::vector<CGraphicTile> vTmpTiles;
599 std::vector<CGraphicTileTextureCoords> vTmpTileTexCoords;
600 std::vector<CGraphicTile> vTmpBorderTopTiles;
601 std::vector<CGraphicTileTextureCoords> vTmpBorderTopTilesTexCoords;
602 std::vector<CGraphicTile> vTmpBorderLeftTiles;
603 std::vector<CGraphicTileTextureCoords> vTmpBorderLeftTilesTexCoords;
604 std::vector<CGraphicTile> vTmpBorderRightTiles;
605 std::vector<CGraphicTileTextureCoords> vTmpBorderRightTilesTexCoords;
606 std::vector<CGraphicTile> vTmpBorderBottomTiles;
607 std::vector<CGraphicTileTextureCoords> vTmpBorderBottomTilesTexCoords;
608 std::vector<CGraphicTile> vTmpBorderCorners;
609 std::vector<CGraphicTileTextureCoords> vTmpBorderCornersTexCoords;
610
611 const bool DoTextureCoords = GetTexture().IsValid();
612
613 // create the visual and set it in the optional, afterwards get it
614 CTileLayerVisuals v;
615 v.OnInit(pRenderComponent: this);
616 VisualsOptional = v;
617 CTileLayerVisuals &Visuals = VisualsOptional.value();
618
619 if(!Visuals.Init(Width: m_pLayerTilemap->m_Width, Height: m_pLayerTilemap->m_Height))
620 return;
621
622 Visuals.m_IsTextured = DoTextureCoords;
623
624 if(!DoTextureCoords)
625 {
626 vTmpTiles.reserve(n: (size_t)m_pLayerTilemap->m_Width * m_pLayerTilemap->m_Height);
627 vTmpBorderTopTiles.reserve(n: (size_t)m_pLayerTilemap->m_Width);
628 vTmpBorderBottomTiles.reserve(n: (size_t)m_pLayerTilemap->m_Width);
629 vTmpBorderLeftTiles.reserve(n: (size_t)m_pLayerTilemap->m_Height);
630 vTmpBorderRightTiles.reserve(n: (size_t)m_pLayerTilemap->m_Height);
631 vTmpBorderCorners.reserve(n: (size_t)4);
632 }
633 else
634 {
635 vTmpTileTexCoords.reserve(n: (size_t)m_pLayerTilemap->m_Width * m_pLayerTilemap->m_Height);
636 vTmpBorderTopTilesTexCoords.reserve(n: (size_t)m_pLayerTilemap->m_Width);
637 vTmpBorderBottomTilesTexCoords.reserve(n: (size_t)m_pLayerTilemap->m_Width);
638 vTmpBorderLeftTilesTexCoords.reserve(n: (size_t)m_pLayerTilemap->m_Height);
639 vTmpBorderRightTilesTexCoords.reserve(n: (size_t)m_pLayerTilemap->m_Height);
640 vTmpBorderCornersTexCoords.reserve(n: (size_t)4);
641 }
642
643 int DrawLeft = m_pLayerTilemap->m_Width;
644 int DrawRight = 0;
645 int DrawTop = m_pLayerTilemap->m_Height;
646 int DrawBottom = 0;
647
648 int x = 0;
649 int y = 0;
650 for(y = 0; y < m_pLayerTilemap->m_Height; ++y)
651 {
652 for(x = 0; x < m_pLayerTilemap->m_Width; ++x)
653 {
654 unsigned char Index = 0;
655 unsigned char Flags = 0;
656 int AngleRotate = -1;
657 GetTileData(pIndex: &Index, pFlags: &Flags, pAngleRotate: &AngleRotate, x, y, CurOverlay);
658
659 // the amount of tiles handled before this tile
660 int TilesHandledCount = vTmpTiles.size();
661 Visuals.m_vTilesOfLayer[y * m_pLayerTilemap->m_Width + x].SetIndexBufferByteOffset((offset_ptr32)(TilesHandledCount));
662
663 if(AddTile(vTmpTiles, vTmpTileTexCoords, Index, Flags, x, y, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate))
664 {
665 Visuals.m_vTilesOfLayer[y * m_pLayerTilemap->m_Width + x].Draw(SetDraw: true);
666
667 // calculate clip region boundaries based on draws
668 DrawLeft = std::min(a: DrawLeft, b: x);
669 DrawRight = std::max(a: DrawRight, b: x);
670 DrawTop = std::min(a: DrawTop, b: y);
671 DrawBottom = std::max(a: DrawBottom, b: y);
672 }
673
674 // do the border tiles
675 if(x == 0)
676 {
677 if(y == 0)
678 {
679 Visuals.m_BorderTopLeft.SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderCorners.size()));
680 if(AddTile(vTmpTiles&: vTmpBorderCorners, vTmpTileTexCoords&: vTmpBorderCornersTexCoords, Index, Flags, x: 0, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{-32, -32}))
681 Visuals.m_BorderTopLeft.Draw(SetDraw: true);
682 }
683 else if(y == m_pLayerTilemap->m_Height - 1)
684 {
685 Visuals.m_BorderBottomLeft.SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderCorners.size()));
686 if(AddTile(vTmpTiles&: vTmpBorderCorners, vTmpTileTexCoords&: vTmpBorderCornersTexCoords, Index, Flags, x: 0, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{-32, 0}))
687 Visuals.m_BorderBottomLeft.Draw(SetDraw: true);
688 }
689 Visuals.m_vBorderLeft[y].SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderLeftTiles.size()));
690 if(AddTile(vTmpTiles&: vTmpBorderLeftTiles, vTmpTileTexCoords&: vTmpBorderLeftTilesTexCoords, Index, Flags, x: 0, y, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{-32, 0}))
691 Visuals.m_vBorderLeft[y].Draw(SetDraw: true);
692 }
693 else if(x == m_pLayerTilemap->m_Width - 1)
694 {
695 if(y == 0)
696 {
697 Visuals.m_BorderTopRight.SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderCorners.size()));
698 if(AddTile(vTmpTiles&: vTmpBorderCorners, vTmpTileTexCoords&: vTmpBorderCornersTexCoords, Index, Flags, x: 0, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{0, -32}))
699 Visuals.m_BorderTopRight.Draw(SetDraw: true);
700 }
701 else if(y == m_pLayerTilemap->m_Height - 1)
702 {
703 Visuals.m_BorderBottomRight.SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderCorners.size()));
704 if(AddTile(vTmpTiles&: vTmpBorderCorners, vTmpTileTexCoords&: vTmpBorderCornersTexCoords, Index, Flags, x: 0, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{0, 0}))
705 Visuals.m_BorderBottomRight.Draw(SetDraw: true);
706 }
707 Visuals.m_vBorderRight[y].SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderRightTiles.size()));
708 if(AddTile(vTmpTiles&: vTmpBorderRightTiles, vTmpTileTexCoords&: vTmpBorderRightTilesTexCoords, Index, Flags, x: 0, y, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{0, 0}))
709 Visuals.m_vBorderRight[y].Draw(SetDraw: true);
710 }
711 if(y == 0)
712 {
713 Visuals.m_vBorderTop[x].SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderTopTiles.size()));
714 if(AddTile(vTmpTiles&: vTmpBorderTopTiles, vTmpTileTexCoords&: vTmpBorderTopTilesTexCoords, Index, Flags, x, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{0, -32}))
715 Visuals.m_vBorderTop[x].Draw(SetDraw: true);
716 }
717 else if(y == m_pLayerTilemap->m_Height - 1)
718 {
719 Visuals.m_vBorderBottom[x].SetIndexBufferByteOffset((offset_ptr32)(vTmpBorderBottomTiles.size()));
720 if(AddTile(vTmpTiles&: vTmpBorderBottomTiles, vTmpTileTexCoords&: vTmpBorderBottomTilesTexCoords, Index, Flags, x, y: 0, DoTextureCoords, FillSpeedup: AddAsSpeedup, AngleRotate, Offset: ivec2{0, 0}))
721 Visuals.m_vBorderBottom[x].Draw(SetDraw: true);
722 }
723 }
724 }
725
726 // shrink clip region
727 // we only apply the clip once for the first overlay type (tile visuals). Physic layers can have multiple layers for text, e.g. speedup force
728 // the first overlay is always the largest and you will never find an overlay, where the text is written over AIR
729 if(CurOverlay == 0)
730 {
731 if(DrawLeft > DrawRight || DrawTop > DrawBottom)
732 {
733 // we are drawing nothing, layer is empty
734 m_LayerClip->m_Height = 0.0f;
735 m_LayerClip->m_Width = 0.0f;
736 }
737 else
738 {
739 m_LayerClip->m_X = DrawLeft * 32.0f;
740 m_LayerClip->m_Y = DrawTop * 32.0f;
741 m_LayerClip->m_Width = (DrawRight - DrawLeft + 1) * 32.0f;
742 m_LayerClip->m_Height = (DrawBottom - DrawTop + 1) * 32.0f;
743 }
744 }
745
746 // append one kill tile to the gamelayer
747 if(IsGameLayer)
748 {
749 Visuals.m_BorderKillTile.SetIndexBufferByteOffset((offset_ptr32)(vTmpTiles.size()));
750 if(AddTile(vTmpTiles, vTmpTileTexCoords, Index: TILE_DEATH, Flags: 0, x: 0, y: 0, DoTextureCoords))
751 Visuals.m_BorderKillTile.Draw(SetDraw: true);
752 }
753
754 // inserts and clears tiles and tile texture coords
755 auto InsertTiles = [&](std::vector<CGraphicTile> &vTiles, std::vector<CGraphicTileTextureCoords> &vTexCoords) {
756 vTmpTiles.insert(position: vTmpTiles.end(), first: vTiles.begin(), last: vTiles.end());
757 vTmpTileTexCoords.insert(position: vTmpTileTexCoords.end(), first: vTexCoords.begin(), last: vTexCoords.end());
758 vTiles.clear();
759 vTexCoords.clear();
760 };
761
762 // add the border corners, then the borders and fix their byte offsets
763 int TilesHandledCount = vTmpTiles.size();
764 Visuals.m_BorderTopLeft.AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCount);
765 Visuals.m_BorderTopRight.AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCount);
766 Visuals.m_BorderBottomLeft.AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCount);
767 Visuals.m_BorderBottomRight.AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCount);
768
769 // add the Corners to the tiles
770 InsertTiles(vTmpBorderCorners, vTmpBorderCornersTexCoords);
771
772 // now the borders
773 int TilesHandledCountTop = vTmpTiles.size();
774 int TilesHandledCountBottom = TilesHandledCountTop + vTmpBorderTopTiles.size();
775 int TilesHandledCountLeft = TilesHandledCountBottom + vTmpBorderBottomTiles.size();
776 int TilesHandledCountRight = TilesHandledCountLeft + vTmpBorderLeftTiles.size();
777
778 if(m_pLayerTilemap->m_Width > 0 && m_pLayerTilemap->m_Height > 0)
779 {
780 for(int i = 0; i < std::max(a: m_pLayerTilemap->m_Width, b: m_pLayerTilemap->m_Height); ++i)
781 {
782 if(i < m_pLayerTilemap->m_Width)
783 {
784 Visuals.m_vBorderTop[i].AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCountTop);
785 Visuals.m_vBorderBottom[i].AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCountBottom);
786 }
787 if(i < m_pLayerTilemap->m_Height)
788 {
789 Visuals.m_vBorderLeft[i].AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCountLeft);
790 Visuals.m_vBorderRight[i].AddIndexBufferByteOffset(IndexBufferByteOff: TilesHandledCountRight);
791 }
792 }
793 }
794
795 InsertTiles(vTmpBorderTopTiles, vTmpBorderTopTilesTexCoords);
796 InsertTiles(vTmpBorderBottomTiles, vTmpBorderBottomTilesTexCoords);
797 InsertTiles(vTmpBorderLeftTiles, vTmpBorderLeftTilesTexCoords);
798 InsertTiles(vTmpBorderRightTiles, vTmpBorderRightTilesTexCoords);
799
800 // setup params
801 float *pTmpTiles = vTmpTiles.empty() ? nullptr : (float *)vTmpTiles.data();
802 unsigned char *pTmpTileTexCoords = vTmpTileTexCoords.empty() ? nullptr : (unsigned char *)vTmpTileTexCoords.data();
803
804 Visuals.m_BufferContainerIndex = -1;
805 size_t UploadDataSize = vTmpTileTexCoords.size() * sizeof(CGraphicTileTextureCoords) + vTmpTiles.size() * sizeof(CGraphicTile);
806 if(UploadDataSize > 0)
807 {
808 char *pUploadData = (char *)malloc(size: sizeof(char) * UploadDataSize);
809
810 mem_copy_special(pDest: pUploadData, pSource: pTmpTiles, Size: sizeof(vec2), Count: vTmpTiles.size() * 4, Steps: (DoTextureCoords ? sizeof(ubvec4) : 0));
811 if(DoTextureCoords)
812 {
813 mem_copy_special(pDest: pUploadData + sizeof(vec2), pSource: pTmpTileTexCoords, Size: sizeof(ubvec4), Count: vTmpTiles.size() * 4, Steps: sizeof(vec2));
814 }
815
816 // first create the buffer object
817 int BufferObjectIndex = Graphics()->CreateBufferObject(UploadDataSize, pUploadData, CreateFlags: 0, IsMovedPointer: true);
818
819 // then create the buffer container
820 SBufferContainerInfo ContainerInfo;
821 ContainerInfo.m_Stride = (DoTextureCoords ? (sizeof(float) * 2 + sizeof(ubvec4)) : 0);
822 ContainerInfo.m_VertBufferBindingIndex = BufferObjectIndex;
823 ContainerInfo.m_vAttributes.emplace_back();
824 SBufferContainerInfo::SAttribute *pAttr = &ContainerInfo.m_vAttributes.back();
825 pAttr->m_DataTypeCount = 2;
826 pAttr->m_Type = GRAPHICS_TYPE_FLOAT;
827 pAttr->m_Normalized = false;
828 pAttr->m_pOffset = nullptr;
829 pAttr->m_FuncType = 0;
830 if(DoTextureCoords)
831 {
832 ContainerInfo.m_vAttributes.emplace_back();
833 pAttr = &ContainerInfo.m_vAttributes.back();
834 pAttr->m_DataTypeCount = 4;
835 pAttr->m_Type = GRAPHICS_TYPE_UNSIGNED_BYTE;
836 pAttr->m_Normalized = false;
837 pAttr->m_pOffset = (void *)(sizeof(vec2));
838 pAttr->m_FuncType = 1;
839 }
840
841 Visuals.m_BufferContainerIndex = Graphics()->CreateBufferContainer(pContainerInfo: &ContainerInfo);
842 // and finally inform the backend how many indices are required
843 Graphics()->IndicesNumRequiredNotify(RequiredIndicesCount: vTmpTiles.size() * 6);
844 }
845 RenderLoading();
846}
847
848void CRenderLayerTile::Unload()
849{
850 if(m_VisualTiles.has_value())
851 {
852 m_VisualTiles->Unload();
853 m_VisualTiles = std::nullopt;
854 }
855}
856
857void CRenderLayerTile::CTileLayerVisuals::Unload()
858{
859 Graphics()->DeleteBufferContainer(ContainerIndex&: m_BufferContainerIndex);
860}
861
862int CRenderLayerTile::GetDataIndex(unsigned int &TileSize) const
863{
864 TileSize = sizeof(CTile);
865 return m_pLayerTilemap->m_Data;
866}
867
868void *CRenderLayerTile::GetRawData() const
869{
870 unsigned int TileSize;
871 unsigned int DataIndex = GetDataIndex(TileSize);
872 void *pTiles = m_pMap->GetData(Index: DataIndex);
873 int Size = m_pMap->GetDataSize(Index: DataIndex);
874
875 if(!pTiles || Size < m_pLayerTilemap->m_Width * m_pLayerTilemap->m_Height * (int)TileSize)
876 return nullptr;
877
878 return pTiles;
879}
880
881void CRenderLayerTile::OnInit(IGraphics *pGraphics, ITextRender *pTextRender, CRenderMap *pRenderMap, std::shared_ptr<CEnvelopeManager> &pEnvelopeManager, IMap *pMap, IMapImages *pMapImages, std::optional<FRenderUploadCallback> &FRenderUploadCallbackOptional)
882{
883 CRenderLayer::OnInit(pGraphics, pTextRender, pRenderMap, pEnvelopeManager, pMap, pMapImages, FRenderUploadCallbackOptional);
884 InitTileData();
885 m_LayerClip = CClipRegion(0.0f, 0.0f, m_pLayerTilemap->m_Width * 32.0f, m_pLayerTilemap->m_Height * 32.0f);
886}
887
888void CRenderLayerTile::InitTileData()
889{
890 m_pTiles = GetData<CTile>();
891}
892
893template<class T>
894T *CRenderLayerTile::GetData() const
895{
896 return (T *)GetRawData();
897}
898
899void CRenderLayerTile::GetTileData(unsigned char *pIndex, unsigned char *pFlags, int *pAngleRotate, unsigned int x, unsigned int y, int CurOverlay) const
900{
901 *pIndex = m_pTiles[y * m_pLayerTilemap->m_Width + x].m_Index;
902 *pFlags = m_pTiles[y * m_pLayerTilemap->m_Width + x].m_Flags;
903}
904
905/**************
906 * Quad Layer *
907 **************/
908
909CRenderLayerQuads::CRenderLayerQuads(int GroupId, int LayerId, int Flags, CMapItemLayerQuads *pLayerQuads) :
910 CRenderLayer(GroupId, LayerId, Flags)
911{
912 m_pLayerQuads = pLayerQuads;
913 m_pQuads = nullptr;
914}
915
916void CRenderLayerQuads::RenderQuadLayer(float Alpha, const CRenderLayerParams &Params)
917{
918 CQuadLayerVisuals &Visuals = m_VisualQuad.value();
919 if(Visuals.m_BufferContainerIndex == -1)
920 return; // no visuals were created
921
922 for(auto &QuadCluster : m_vQuadClusters)
923 {
924 if(!IsVisibleInClipRegion(ClipRegion: QuadCluster.m_ClipRegion))
925 continue;
926
927 if(!QuadCluster.m_Grouped)
928 {
929 bool AnyVisible = false;
930 for(int QuadClusterId = 0; QuadClusterId < QuadCluster.m_NumQuads; ++QuadClusterId)
931 {
932 CQuad *pQuad = &m_pQuads[QuadCluster.m_StartIndex + QuadClusterId];
933
934 ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
935 if(pQuad->m_ColorEnv >= 0)
936 {
937 m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(TimeOffsetMillis: pQuad->m_ColorEnvOffset, Env: pQuad->m_ColorEnv, Result&: Color, Channels: 4);
938 }
939 Color.a *= Alpha;
940
941 SQuadRenderInfo &QInfo = QuadCluster.m_vQuadRenderInfo[QuadClusterId];
942 if(Color.a < 0.0f)
943 Color.a = 0.0f;
944 QInfo.m_Color = Color;
945 const bool IsVisible = Color.a >= 0.0f;
946 AnyVisible |= IsVisible;
947
948 if(IsVisible)
949 {
950 ColorRGBA Position = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
951 m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(TimeOffsetMillis: pQuad->m_PosEnvOffset, Env: pQuad->m_PosEnv, Result&: Position, Channels: 3);
952 QInfo.m_Offsets.x = Position.r;
953 QInfo.m_Offsets.y = Position.g;
954 QInfo.m_Rotation = Position.b / 180.0f * pi;
955 }
956 }
957 if(AnyVisible)
958 Graphics()->RenderQuadLayer(BufferContainerIndex: Visuals.m_BufferContainerIndex, pQuadInfo: QuadCluster.m_vQuadRenderInfo.data(), QuadNum: QuadCluster.m_NumQuads, QuadOffset: QuadCluster.m_StartIndex);
959 }
960 else
961 {
962 SQuadRenderInfo &QInfo = QuadCluster.m_vQuadRenderInfo[0];
963
964 ColorRGBA Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
965 if(QuadCluster.m_ColorEnv >= 0)
966 {
967 m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(TimeOffsetMillis: QuadCluster.m_ColorEnvOffset, Env: QuadCluster.m_ColorEnv, Result&: Color, Channels: 4);
968 }
969
970 Color.a *= Alpha;
971 if(Color.a <= 0.0f)
972 continue;
973 QInfo.m_Color = Color;
974
975 if(QuadCluster.m_PosEnv >= 0)
976 {
977 ColorRGBA Position = ColorRGBA(0.0f, 0.0f, 0.0f, 0.0f);
978 m_pEnvelopeManager->EnvelopeEval()->EnvelopeEval(TimeOffsetMillis: QuadCluster.m_PosEnvOffset, Env: QuadCluster.m_PosEnv, Result&: Position, Channels: 3);
979
980 QInfo.m_Offsets.x = Position.r;
981 QInfo.m_Offsets.y = Position.g;
982 QInfo.m_Rotation = Position.b / 180.0f * pi;
983 }
984 Graphics()->RenderQuadLayer(BufferContainerIndex: Visuals.m_BufferContainerIndex, pQuadInfo: &QInfo, QuadNum: (size_t)QuadCluster.m_NumQuads, QuadOffset: QuadCluster.m_StartIndex, Grouped: true);
985 }
986 }
987
988 if(Params.m_DebugRenderClusterClips)
989 {
990 for(auto &QuadCluster : m_vQuadClusters)
991 {
992 if(!IsVisibleInClipRegion(ClipRegion: QuadCluster.m_ClipRegion) || !QuadCluster.m_ClipRegion.has_value())
993 continue;
994
995 char aDebugText[64];
996 str_format(buffer: aDebugText, buffer_size: sizeof(aDebugText), format: "Group %d, quad layer %d, quad start %d, grouped %d", m_GroupId, m_LayerId, QuadCluster.m_StartIndex, QuadCluster.m_Grouped);
997 RenderMap()->RenderDebugClip(ClipX: QuadCluster.m_ClipRegion->m_X, ClipY: QuadCluster.m_ClipRegion->m_Y, ClipW: QuadCluster.m_ClipRegion->m_Width, ClipH: QuadCluster.m_ClipRegion->m_Height, Color: ColorRGBA(1.0f, 0.0f, 1.0f, 1.0f), Zoom: Params.m_Zoom, pLabel: aDebugText);
998 }
999 }
1000}
1001
1002void CRenderLayerQuads::OnInit(IGraphics *pGraphics, ITextRender *pTextRender, CRenderMap *pRenderMap, std::shared_ptr<CEnvelopeManager> &pEnvelopeManager, IMap *pMap, IMapImages *pMapImages, std::optional<FRenderUploadCallback> &FRenderUploadCallbackOptional)
1003{
1004 CRenderLayer::OnInit(pGraphics, pTextRender, pRenderMap, pEnvelopeManager, pMap, pMapImages, FRenderUploadCallbackOptional);
1005 int DataSize = m_pMap->GetDataSize(Index: m_pLayerQuads->m_Data);
1006 if(m_pLayerQuads->m_NumQuads > 0 && DataSize / (int)sizeof(CQuad) >= m_pLayerQuads->m_NumQuads)
1007 m_pQuads = (CQuad *)m_pMap->GetDataSwapped(Index: m_pLayerQuads->m_Data);
1008}
1009
1010void CRenderLayerQuads::Init()
1011{
1012 if(m_pLayerQuads->m_Image >= 0 && m_pLayerQuads->m_Image < m_pMapImages->Num())
1013 m_TextureHandle = m_pMapImages->Get(Index: m_pLayerQuads->m_Image);
1014 else
1015 m_TextureHandle.Invalidate();
1016
1017 if(!Graphics()->IsQuadBufferingEnabled())
1018 return;
1019
1020 std::vector<CTmpQuad> vTmpQuads;
1021 std::vector<CTmpQuadTextured> vTmpQuadsTextured;
1022 CQuadLayerVisuals v;
1023 v.OnInit(pRenderComponent: this);
1024 m_VisualQuad = v;
1025 CQuadLayerVisuals *pQLayerVisuals = &(m_VisualQuad.value());
1026
1027 const bool Textured = m_pLayerQuads->m_Image >= 0 && m_pLayerQuads->m_Image < m_pMapImages->Num();
1028
1029 if(Textured)
1030 vTmpQuadsTextured.resize(new_size: m_pLayerQuads->m_NumQuads);
1031 else
1032 vTmpQuads.resize(new_size: m_pLayerQuads->m_NumQuads);
1033
1034 auto SetQuadRenderInfo = [&](SQuadRenderInfo &QInfo, int QuadId, bool InitInfo) {
1035 CQuad *pQuad = &m_pQuads[QuadId];
1036
1037 // init for envelopeless quad layers
1038 if(InitInfo)
1039 {
1040 QInfo.m_Color = ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f);
1041 QInfo.m_Offsets.x = 0;
1042 QInfo.m_Offsets.y = 0;
1043 QInfo.m_Rotation = 0;
1044 }
1045
1046 for(int j = 0; j < 4; ++j)
1047 {
1048 int QuadIdX = j;
1049 if(j == 2)
1050 QuadIdX = 3;
1051 else if(j == 3)
1052 QuadIdX = 2;
1053 if(!Textured)
1054 {
1055 // ignore the conversion for the position coordinates
1056 vTmpQuads[QuadId].m_aVertices[j].m_X = fx2f(v: pQuad->m_aPoints[QuadIdX].x);
1057 vTmpQuads[QuadId].m_aVertices[j].m_Y = fx2f(v: pQuad->m_aPoints[QuadIdX].y);
1058 vTmpQuads[QuadId].m_aVertices[j].m_CenterX = fx2f(v: pQuad->m_aPoints[4].x);
1059 vTmpQuads[QuadId].m_aVertices[j].m_CenterY = fx2f(v: pQuad->m_aPoints[4].y);
1060 vTmpQuads[QuadId].m_aVertices[j].m_R = (unsigned char)pQuad->m_aColors[QuadIdX].r;
1061 vTmpQuads[QuadId].m_aVertices[j].m_G = (unsigned char)pQuad->m_aColors[QuadIdX].g;
1062 vTmpQuads[QuadId].m_aVertices[j].m_B = (unsigned char)pQuad->m_aColors[QuadIdX].b;
1063 vTmpQuads[QuadId].m_aVertices[j].m_A = (unsigned char)pQuad->m_aColors[QuadIdX].a;
1064 }
1065 else
1066 {
1067 // ignore the conversion for the position coordinates
1068 vTmpQuadsTextured[QuadId].m_aVertices[j].m_X = fx2f(v: pQuad->m_aPoints[QuadIdX].x);
1069 vTmpQuadsTextured[QuadId].m_aVertices[j].m_Y = fx2f(v: pQuad->m_aPoints[QuadIdX].y);
1070 vTmpQuadsTextured[QuadId].m_aVertices[j].m_CenterX = fx2f(v: pQuad->m_aPoints[4].x);
1071 vTmpQuadsTextured[QuadId].m_aVertices[j].m_CenterY = fx2f(v: pQuad->m_aPoints[4].y);
1072 vTmpQuadsTextured[QuadId].m_aVertices[j].m_U = fx2f(v: pQuad->m_aTexcoords[QuadIdX].x);
1073 vTmpQuadsTextured[QuadId].m_aVertices[j].m_V = fx2f(v: pQuad->m_aTexcoords[QuadIdX].y);
1074 vTmpQuadsTextured[QuadId].m_aVertices[j].m_R = (unsigned char)pQuad->m_aColors[QuadIdX].r;
1075 vTmpQuadsTextured[QuadId].m_aVertices[j].m_G = (unsigned char)pQuad->m_aColors[QuadIdX].g;
1076 vTmpQuadsTextured[QuadId].m_aVertices[j].m_B = (unsigned char)pQuad->m_aColors[QuadIdX].b;
1077 vTmpQuadsTextured[QuadId].m_aVertices[j].m_A = (unsigned char)pQuad->m_aColors[QuadIdX].a;
1078 }
1079 }
1080 };
1081
1082 m_vQuadClusters.clear();
1083 CQuadCluster QuadCluster;
1084
1085 // create quad clusters
1086 int QuadStart = 0;
1087 while(QuadStart < m_pLayerQuads->m_NumQuads)
1088 {
1089 QuadCluster.m_StartIndex = QuadStart;
1090 QuadCluster.m_Grouped = true;
1091 QuadCluster.m_ColorEnv = m_pQuads[QuadStart].m_ColorEnv;
1092 QuadCluster.m_ColorEnvOffset = m_pQuads[QuadStart].m_ColorEnvOffset;
1093 QuadCluster.m_PosEnv = m_pQuads[QuadStart].m_PosEnv;
1094 QuadCluster.m_PosEnvOffset = m_pQuads[QuadStart].m_PosEnvOffset;
1095
1096 int QuadOffset = 0;
1097 for(int QuadClusterId = 0; QuadClusterId < m_pLayerQuads->m_NumQuads - QuadStart; ++QuadClusterId)
1098 {
1099 const CQuad *pQuad = &m_pQuads[QuadStart + QuadClusterId];
1100 bool IsGrouped = QuadCluster.m_Grouped && pQuad->m_ColorEnv == QuadCluster.m_ColorEnv && pQuad->m_ColorEnvOffset == QuadCluster.m_ColorEnvOffset && pQuad->m_PosEnv == QuadCluster.m_PosEnv && pQuad->m_PosEnvOffset == QuadCluster.m_PosEnvOffset;
1101
1102 // we are reaching gpu batch limit, here we break and close the QuadCluster if it's ungrouped
1103 if(QuadClusterId >= (int)gs_GraphicsMaxQuadsRenderCount)
1104 {
1105 // expand a cluster, if it's grouped
1106 if(!IsGrouped)
1107 break;
1108 }
1109 QuadOffset++;
1110 QuadCluster.m_Grouped = IsGrouped;
1111 }
1112 QuadCluster.m_NumQuads = QuadOffset;
1113
1114 // fill cluster info
1115 if(QuadCluster.m_Grouped)
1116 {
1117 // grouped quads only need one render info, because all their envs and env offsets are equal
1118 QuadCluster.m_vQuadRenderInfo.resize(new_size: 1);
1119 for(int QuadClusterId = 0; QuadClusterId < QuadCluster.m_NumQuads; ++QuadClusterId)
1120 SetQuadRenderInfo(QuadCluster.m_vQuadRenderInfo[0], QuadCluster.m_StartIndex + QuadClusterId, QuadClusterId == 0);
1121 }
1122 else
1123 {
1124 QuadCluster.m_vQuadRenderInfo.resize(new_size: QuadCluster.m_NumQuads);
1125 for(int QuadClusterId = 0; QuadClusterId < QuadCluster.m_NumQuads; ++QuadClusterId)
1126 SetQuadRenderInfo(QuadCluster.m_vQuadRenderInfo[QuadClusterId], QuadCluster.m_StartIndex + QuadClusterId, true);
1127 }
1128
1129 CalculateClipping(QuadCluster);
1130
1131 m_vQuadClusters.push_back(x: QuadCluster);
1132 QuadStart += QuadOffset;
1133 }
1134
1135 // gpu upload
1136 size_t UploadDataSize = 0;
1137 if(Textured)
1138 UploadDataSize = vTmpQuadsTextured.size() * sizeof(CTmpQuadTextured);
1139 else
1140 UploadDataSize = vTmpQuads.size() * sizeof(CTmpQuad);
1141
1142 if(UploadDataSize > 0)
1143 {
1144 void *pUploadData = nullptr;
1145 if(Textured)
1146 pUploadData = vTmpQuadsTextured.data();
1147 else
1148 pUploadData = vTmpQuads.data();
1149 // create the buffer object
1150 int BufferObjectIndex = Graphics()->CreateBufferObject(UploadDataSize, pUploadData, CreateFlags: 0);
1151 // then create the buffer container
1152 SBufferContainerInfo ContainerInfo;
1153 ContainerInfo.m_Stride = (Textured ? (sizeof(CTmpQuadTextured) / 4) : (sizeof(CTmpQuad) / 4));
1154 ContainerInfo.m_VertBufferBindingIndex = BufferObjectIndex;
1155 ContainerInfo.m_vAttributes.emplace_back();
1156 SBufferContainerInfo::SAttribute *pAttr = &ContainerInfo.m_vAttributes.back();
1157 pAttr->m_DataTypeCount = 4;
1158 pAttr->m_Type = GRAPHICS_TYPE_FLOAT;
1159 pAttr->m_Normalized = false;
1160 pAttr->m_pOffset = nullptr;
1161 pAttr->m_FuncType = 0;
1162 ContainerInfo.m_vAttributes.emplace_back();
1163 pAttr = &ContainerInfo.m_vAttributes.back();
1164 pAttr->m_DataTypeCount = 4;
1165 pAttr->m_Type = GRAPHICS_TYPE_UNSIGNED_BYTE;
1166 pAttr->m_Normalized = true;
1167 pAttr->m_pOffset = (void *)(sizeof(float) * 4);
1168 pAttr->m_FuncType = 0;
1169 if(Textured)
1170 {
1171 ContainerInfo.m_vAttributes.emplace_back();
1172 pAttr = &ContainerInfo.m_vAttributes.back();
1173 pAttr->m_DataTypeCount = 2;
1174 pAttr->m_Type = GRAPHICS_TYPE_FLOAT;
1175 pAttr->m_Normalized = false;
1176 pAttr->m_pOffset = (void *)(sizeof(float) * 4 + sizeof(unsigned char) * 4);
1177 pAttr->m_FuncType = 0;
1178 }
1179
1180 pQLayerVisuals->m_BufferContainerIndex = Graphics()->CreateBufferContainer(pContainerInfo: &ContainerInfo);
1181 // and finally inform the backend how many indices are required
1182 Graphics()->IndicesNumRequiredNotify(RequiredIndicesCount: m_pLayerQuads->m_NumQuads * 6);
1183 }
1184 RenderLoading();
1185}
1186
1187void CRenderLayerQuads::Unload()
1188{
1189 if(m_VisualQuad.has_value())
1190 {
1191 m_VisualQuad->Unload();
1192 m_VisualQuad = std::nullopt;
1193 }
1194}
1195
1196void CRenderLayerQuads::CQuadLayerVisuals::Unload()
1197{
1198 Graphics()->DeleteBufferContainer(ContainerIndex&: m_BufferContainerIndex);
1199}
1200
1201bool CRenderLayerQuads::CalculateQuadClipping(const CQuadCluster &QuadCluster, float aQuadOffsetMin[2], float aQuadOffsetMax[2]) const
1202{
1203 // check if the grouped clipping is available for early exit
1204 if(QuadCluster.m_Grouped)
1205 {
1206 const CEnvelopeExtrema::CEnvelopeExtremaItem &Extrema = m_pEnvelopeManager->EnvelopeExtrema()->GetExtrema(Env: QuadCluster.m_PosEnv);
1207 if(!Extrema.m_Available)
1208 return false;
1209 }
1210
1211 // calculate quad position offsets
1212 for(int Channel = 0; Channel < 2; ++Channel)
1213 {
1214 aQuadOffsetMin[Channel] = std::numeric_limits<float>::max(); // minimum of channel
1215 aQuadOffsetMax[Channel] = std::numeric_limits<float>::min(); // maximum of channel
1216 }
1217
1218 for(int QuadId = QuadCluster.m_StartIndex; QuadId < QuadCluster.m_StartIndex + QuadCluster.m_NumQuads; ++QuadId)
1219 {
1220 const CQuad *pQuad = &m_pQuads[QuadId];
1221
1222 const CEnvelopeExtrema::CEnvelopeExtremaItem &Extrema = m_pEnvelopeManager->EnvelopeExtrema()->GetExtrema(Env: pQuad->m_PosEnv);
1223 if(!Extrema.m_Available)
1224 return false;
1225
1226 // calculate clip region
1227 if(!Extrema.m_Rotating)
1228 {
1229 for(int QuadIdPoint = 0; QuadIdPoint < 4; ++QuadIdPoint)
1230 {
1231 for(int Channel = 0; Channel < 2; ++Channel)
1232 {
1233 float OffsetMinimum = fx2f(v: pQuad->m_aPoints[QuadIdPoint][Channel]);
1234 float OffsetMaximum = fx2f(v: pQuad->m_aPoints[QuadIdPoint][Channel]);
1235
1236 // calculate env offsets for every ungrouped quad
1237 if(!QuadCluster.m_Grouped && pQuad->m_PosEnv >= 0)
1238 {
1239 OffsetMinimum += fx2f(v: Extrema.m_Minima[Channel]);
1240 OffsetMaximum += fx2f(v: Extrema.m_Maxima[Channel]);
1241 }
1242 aQuadOffsetMin[Channel] = std::min(a: aQuadOffsetMin[Channel], b: OffsetMinimum);
1243 aQuadOffsetMax[Channel] = std::max(a: aQuadOffsetMax[Channel], b: OffsetMaximum);
1244 }
1245 }
1246 }
1247 else
1248 {
1249 const CPoint &CenterFX = pQuad->m_aPoints[4];
1250 vec2 Center(fx2f(v: CenterFX.x), fx2f(v: CenterFX.y));
1251 float MaxDistance = 0;
1252 for(int QuadIdPoint = 0; QuadIdPoint < 4; ++QuadIdPoint)
1253 {
1254 const CPoint &QuadPointFX = pQuad->m_aPoints[QuadIdPoint];
1255 vec2 QuadPoint(fx2f(v: QuadPointFX.x), fx2f(v: QuadPointFX.y));
1256 float Distance = length(a: Center - QuadPoint);
1257 MaxDistance = std::max(a: Distance, b: MaxDistance);
1258 }
1259
1260 for(int Channel = 0; Channel < 2; ++Channel)
1261 {
1262 float OffsetMinimum = Center[Channel] - MaxDistance;
1263 float OffsetMaximum = Center[Channel] + MaxDistance;
1264 if(!QuadCluster.m_Grouped && pQuad->m_PosEnv >= 0)
1265 {
1266 OffsetMinimum += fx2f(v: Extrema.m_Minima[Channel]);
1267 OffsetMaximum += fx2f(v: Extrema.m_Maxima[Channel]);
1268 }
1269 aQuadOffsetMin[Channel] = std::min(a: aQuadOffsetMin[Channel], b: OffsetMinimum);
1270 aQuadOffsetMax[Channel] = std::max(a: aQuadOffsetMax[Channel], b: OffsetMaximum);
1271 }
1272 }
1273 }
1274
1275 // add env offsets for the quad group
1276 if(QuadCluster.m_Grouped && QuadCluster.m_PosEnv >= 0)
1277 {
1278 const CEnvelopeExtrema::CEnvelopeExtremaItem &Extrema = m_pEnvelopeManager->EnvelopeExtrema()->GetExtrema(Env: QuadCluster.m_PosEnv);
1279
1280 for(int Channel = 0; Channel < 2; ++Channel)
1281 {
1282 aQuadOffsetMin[Channel] += fx2f(v: Extrema.m_Minima[Channel]);
1283 aQuadOffsetMax[Channel] += fx2f(v: Extrema.m_Maxima[Channel]);
1284 }
1285 }
1286 return true;
1287}
1288
1289void CRenderLayerQuads::CalculateClipping(CQuadCluster &QuadCluster)
1290{
1291 float aQuadOffsetMin[2];
1292 float aQuadOffsetMax[2];
1293
1294 bool CreateClip = CalculateQuadClipping(QuadCluster, aQuadOffsetMin, aQuadOffsetMax);
1295
1296 if(!CreateClip)
1297 return;
1298
1299 QuadCluster.m_ClipRegion = std::make_optional<CClipRegion>();
1300 std::optional<CClipRegion> &ClipRegion = QuadCluster.m_ClipRegion;
1301
1302 // X channel
1303 ClipRegion->m_X = aQuadOffsetMin[0];
1304 ClipRegion->m_Width = aQuadOffsetMax[0] - aQuadOffsetMin[0];
1305
1306 // Y channel
1307 ClipRegion->m_Y = aQuadOffsetMin[1];
1308 ClipRegion->m_Height = aQuadOffsetMax[1] - aQuadOffsetMin[1];
1309
1310 // update layer clip
1311 if(!m_LayerClip.has_value())
1312 {
1313 m_LayerClip = ClipRegion;
1314 }
1315 else
1316 {
1317 float ClipRight = std::max(a: ClipRegion->m_X + ClipRegion->m_Width, b: m_LayerClip->m_X + m_LayerClip->m_Width);
1318 float ClipBottom = std::max(a: ClipRegion->m_Y + ClipRegion->m_Height, b: m_LayerClip->m_Y + m_LayerClip->m_Height);
1319 m_LayerClip->m_X = std::min(a: ClipRegion->m_X, b: m_LayerClip->m_X);
1320 m_LayerClip->m_Y = std::min(a: ClipRegion->m_Y, b: m_LayerClip->m_Y);
1321 m_LayerClip->m_Width = ClipRight - m_LayerClip->m_X;
1322 m_LayerClip->m_Height = ClipBottom - m_LayerClip->m_Y;
1323 }
1324}
1325
1326void CRenderLayerQuads::Render(const CRenderLayerParams &Params)
1327{
1328 UseTexture(TextureHandle: GetTexture());
1329
1330 bool Force = Params.m_RenderType == ERenderType::RENDERTYPE_BACKGROUND_FORCE || Params.m_RenderType == ERenderType::RENDERTYPE_FULL_DESIGN;
1331 float Alpha = Force ? 1.f : (100 - Params.m_EntityOverlayVal) / 100.0f;
1332 if(!Graphics()->IsQuadBufferingEnabled() || !Params.m_TileAndQuadBuffering)
1333 {
1334 Graphics()->BlendNormal();
1335 RenderMap()->ForceRenderQuads(pQuads: m_pQuads, NumQuads: m_pLayerQuads->m_NumQuads, Flags: LAYERRENDERFLAG_TRANSPARENT, pEnvEval: m_pEnvelopeManager->EnvelopeEval(), Alpha);
1336 }
1337 else
1338 {
1339 RenderQuadLayer(Alpha, Params);
1340 }
1341
1342 if(Params.m_DebugRenderQuadClips && m_LayerClip.has_value())
1343 {
1344 char aDebugText[64];
1345 str_format(buffer: aDebugText, buffer_size: sizeof(aDebugText), format: "Group %d, quad layer %d", m_GroupId, m_LayerId);
1346 RenderMap()->RenderDebugClip(ClipX: m_LayerClip->m_X, ClipY: m_LayerClip->m_Y, ClipW: m_LayerClip->m_Width, ClipH: m_LayerClip->m_Height, Color: ColorRGBA(1.0f, 0.0f, 0.5f, 1.0f), Zoom: Params.m_Zoom, pLabel: aDebugText);
1347 }
1348}
1349
1350bool CRenderLayerQuads::DoRender(const CRenderLayerParams &Params)
1351{
1352 // skip rendering anything but entities if we only want to render entities
1353 if(Params.m_EntityOverlayVal == 100 && Params.m_RenderType != ERenderType::RENDERTYPE_BACKGROUND_FORCE)
1354 return false;
1355
1356 // skip rendering if detail layers if not wanted
1357 if(m_Flags & LAYERFLAG_DETAIL && !g_Config.m_GfxHighDetail && Params.m_RenderType != ERenderType::RENDERTYPE_FULL_DESIGN) // detail but no details
1358 return false;
1359
1360 // this option only deactivates quads in the background
1361 if(Params.m_RenderType == ERenderType::RENDERTYPE_BACKGROUND || Params.m_RenderType == ERenderType::RENDERTYPE_BACKGROUND_FORCE)
1362 {
1363 if(!g_Config.m_ClShowQuads)
1364 return false;
1365 }
1366
1367 return IsVisibleInClipRegion(ClipRegion: m_LayerClip);
1368}
1369
1370/****************
1371 * Entity Layer *
1372 ****************/
1373// BASE
1374CRenderLayerEntityBase::CRenderLayerEntityBase(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1375 CRenderLayerTile(GroupId, LayerId, Flags, pLayerTilemap) {}
1376
1377bool CRenderLayerEntityBase::DoRender(const CRenderLayerParams &Params)
1378{
1379 // skip rendering if we render background force or full design
1380 if(Params.m_RenderType == ERenderType::RENDERTYPE_BACKGROUND_FORCE || Params.m_RenderType == ERenderType::RENDERTYPE_FULL_DESIGN)
1381 return false;
1382
1383 // skip rendering of entities if don't want them
1384 if(!Params.m_EntityOverlayVal)
1385 return false;
1386
1387 return true;
1388}
1389
1390IGraphics::CTextureHandle CRenderLayerEntityBase::GetTexture() const
1391{
1392 return m_pMapImages->GetEntities(EntityLayerType: MAP_IMAGE_ENTITY_LAYER_TYPE_ALL_EXCEPT_SWITCH);
1393}
1394
1395// GAME
1396CRenderLayerEntityGame::CRenderLayerEntityGame(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1397 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1398
1399void CRenderLayerEntityGame::Init()
1400{
1401 UploadTileData(VisualsOptional&: m_VisualTiles, CurOverlay: 0, AddAsSpeedup: false, IsGameLayer: true);
1402}
1403
1404void CRenderLayerEntityGame::RenderTileLayerWithTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1405{
1406 Graphics()->BlendNormal();
1407 if(Params.m_RenderTileBorder)
1408 RenderKillTileBorder(Color: Color.Multiply(Other: GetDeathBorderColor()));
1409 RenderTileLayer(Color, Params);
1410}
1411
1412void CRenderLayerEntityGame::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1413{
1414 Graphics()->BlendNone();
1415 RenderMap()->RenderTilemap(pTiles: m_pTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_OPAQUE);
1416 Graphics()->BlendNormal();
1417
1418 if(Params.m_RenderTileBorder)
1419 {
1420 RenderMap()->RenderTileRectangle(RectX: -BorderRenderDistance, RectY: -BorderRenderDistance, RectW: m_pLayerTilemap->m_Width + 2 * BorderRenderDistance, RectH: m_pLayerTilemap->m_Height + 2 * BorderRenderDistance,
1421 IndexIn: TILE_AIR, IndexOut: TILE_DEATH, // display air inside, death outside
1422 Scale: 32.0f, Color: Color.Multiply(Other: GetDeathBorderColor()), RenderFlags: TILERENDERFLAG_EXTEND | LAYERRENDERFLAG_TRANSPARENT);
1423 }
1424
1425 RenderMap()->RenderTilemap(pTiles: m_pTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_TRANSPARENT);
1426}
1427
1428ColorRGBA CRenderLayerEntityGame::GetDeathBorderColor() const
1429{
1430 // draw kill tiles outside the entity clipping rectangle
1431 // slow blinking to hint that it's not a part of the map
1432 float Seconds = time_get() / (float)time_freq();
1433 float Alpha = 0.3f + 0.35f * (1.f + std::sin(x: 2.f * pi * Seconds / 3.f));
1434 return ColorRGBA(1.f, 1.f, 1.f, Alpha);
1435}
1436
1437// FRONT
1438CRenderLayerEntityFront::CRenderLayerEntityFront(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1439 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1440
1441int CRenderLayerEntityFront::GetDataIndex(unsigned int &TileSize) const
1442{
1443 TileSize = sizeof(CTile);
1444 return m_pLayerTilemap->m_Front;
1445}
1446
1447// TELE
1448CRenderLayerEntityTele::CRenderLayerEntityTele(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1449 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1450
1451int CRenderLayerEntityTele::GetDataIndex(unsigned int &TileSize) const
1452{
1453 TileSize = sizeof(CTeleTile);
1454 return m_pLayerTilemap->m_Tele;
1455}
1456
1457void CRenderLayerEntityTele::Init()
1458{
1459 UploadTileData(VisualsOptional&: m_VisualTiles, CurOverlay: 0, AddAsSpeedup: false);
1460 UploadTileData(VisualsOptional&: m_VisualTeleNumbers, CurOverlay: 1, AddAsSpeedup: false);
1461}
1462
1463void CRenderLayerEntityTele::InitTileData()
1464{
1465 m_pTeleTiles = GetData<CTeleTile>();
1466}
1467
1468void CRenderLayerEntityTele::Unload()
1469{
1470 CRenderLayerTile::Unload();
1471 if(m_VisualTeleNumbers.has_value())
1472 {
1473 m_VisualTeleNumbers->Unload();
1474 m_VisualTeleNumbers = std::nullopt;
1475 }
1476}
1477
1478void CRenderLayerEntityTele::RenderTileLayerWithTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1479{
1480 Graphics()->BlendNormal();
1481 RenderTileLayer(Color, Params);
1482 if(Params.m_RenderText)
1483 {
1484 Graphics()->TextureSet(Texture: m_pMapImages->GetOverlayCenter());
1485 RenderTileLayer(Color, Params, pTileLayerVisuals: &m_VisualTeleNumbers.value());
1486 }
1487}
1488
1489void CRenderLayerEntityTele::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1490{
1491 Graphics()->BlendNone();
1492 RenderMap()->RenderTelemap(pTele: m_pTeleTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_OPAQUE);
1493 Graphics()->BlendNormal();
1494 RenderMap()->RenderTelemap(pTele: m_pTeleTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_TRANSPARENT);
1495 int OverlayRenderFlags = (Params.m_RenderText ? OVERLAYRENDERFLAG_TEXT : 0) | (Params.m_RenderInvalidTiles ? OVERLAYRENDERFLAG_EDITOR : 0);
1496 RenderMap()->RenderTeleOverlay(pTele: m_pTeleTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, OverlayRenderFlags, Alpha: Color.a);
1497}
1498
1499void CRenderLayerEntityTele::GetTileData(unsigned char *pIndex, unsigned char *pFlags, int *pAngleRotate, unsigned int x, unsigned int y, int CurOverlay) const
1500{
1501 *pIndex = m_pTeleTiles[y * m_pLayerTilemap->m_Width + x].m_Type;
1502 *pFlags = 0;
1503 if(CurOverlay == 1)
1504 {
1505 if(IsTeleTileNumberUsedAny(Index: *pIndex))
1506 *pIndex = m_pTeleTiles[y * m_pLayerTilemap->m_Width + x].m_Number;
1507 else
1508 *pIndex = 0;
1509 }
1510}
1511
1512// SPEEDUP
1513CRenderLayerEntitySpeedup::CRenderLayerEntitySpeedup(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1514 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1515
1516IGraphics::CTextureHandle CRenderLayerEntitySpeedup::GetTexture() const
1517{
1518 return m_pMapImages->GetSpeedupArrow();
1519}
1520
1521int CRenderLayerEntitySpeedup::GetDataIndex(unsigned int &TileSize) const
1522{
1523 TileSize = sizeof(CSpeedupTile);
1524 return m_pLayerTilemap->m_Speedup;
1525}
1526
1527void CRenderLayerEntitySpeedup::Init()
1528{
1529 UploadTileData(VisualsOptional&: m_VisualTiles, CurOverlay: 0, AddAsSpeedup: true);
1530 UploadTileData(VisualsOptional&: m_VisualForce, CurOverlay: 1, AddAsSpeedup: false);
1531 UploadTileData(VisualsOptional&: m_VisualMaxSpeed, CurOverlay: 2, AddAsSpeedup: false);
1532}
1533
1534void CRenderLayerEntitySpeedup::InitTileData()
1535{
1536 m_pSpeedupTiles = GetData<CSpeedupTile>();
1537}
1538
1539void CRenderLayerEntitySpeedup::Unload()
1540{
1541 CRenderLayerTile::Unload();
1542 if(m_VisualForce.has_value())
1543 {
1544 m_VisualForce->Unload();
1545 m_VisualForce = std::nullopt;
1546 }
1547 if(m_VisualMaxSpeed.has_value())
1548 {
1549 m_VisualMaxSpeed->Unload();
1550 m_VisualMaxSpeed = std::nullopt;
1551 }
1552}
1553
1554void CRenderLayerEntitySpeedup::GetTileData(unsigned char *pIndex, unsigned char *pFlags, int *pAngleRotate, unsigned int x, unsigned int y, int CurOverlay) const
1555{
1556 *pIndex = m_pSpeedupTiles[y * m_pLayerTilemap->m_Width + x].m_Type;
1557 unsigned char Force = m_pSpeedupTiles[y * m_pLayerTilemap->m_Width + x].m_Force;
1558 unsigned char MaxSpeed = m_pSpeedupTiles[y * m_pLayerTilemap->m_Width + x].m_MaxSpeed;
1559 *pFlags = 0;
1560 *pAngleRotate = m_pSpeedupTiles[y * m_pLayerTilemap->m_Width + x].m_Angle;
1561 if((Force == 0 && *pIndex == TILE_SPEED_BOOST_OLD) || (Force == 0 && MaxSpeed == 0 && *pIndex == TILE_SPEED_BOOST) || !IsValidSpeedupTile(Index: *pIndex))
1562 *pIndex = 0;
1563 else if(CurOverlay == 1)
1564 *pIndex = Force;
1565 else if(CurOverlay == 2)
1566 *pIndex = MaxSpeed;
1567}
1568
1569void CRenderLayerEntitySpeedup::RenderTileLayerWithTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1570{
1571 // draw arrow -- clamp to the edge of the arrow image
1572 Graphics()->WrapClamp();
1573 UseTexture(TextureHandle: GetTexture());
1574 RenderTileLayer(Color, Params);
1575 Graphics()->WrapNormal();
1576
1577 if(Params.m_RenderText)
1578 {
1579 Graphics()->TextureSet(Texture: m_pMapImages->GetOverlayBottom());
1580 RenderTileLayer(Color, Params, pTileLayerVisuals: &m_VisualForce.value());
1581 Graphics()->TextureSet(Texture: m_pMapImages->GetOverlayTop());
1582 RenderTileLayer(Color, Params, pTileLayerVisuals: &m_VisualMaxSpeed.value());
1583 }
1584}
1585
1586void CRenderLayerEntitySpeedup::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1587{
1588 int OverlayRenderFlags = (Params.m_RenderText ? OVERLAYRENDERFLAG_TEXT : 0) | (Params.m_RenderInvalidTiles ? OVERLAYRENDERFLAG_EDITOR : 0);
1589 RenderMap()->RenderSpeedupOverlay(pSpeedup: m_pSpeedupTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, OverlayRenderFlags, Alpha: Color.a);
1590}
1591
1592// SWITCH
1593CRenderLayerEntitySwitch::CRenderLayerEntitySwitch(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1594 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1595
1596IGraphics::CTextureHandle CRenderLayerEntitySwitch::GetTexture() const
1597{
1598 return m_pMapImages->GetEntities(EntityLayerType: MAP_IMAGE_ENTITY_LAYER_TYPE_SWITCH);
1599}
1600
1601int CRenderLayerEntitySwitch::GetDataIndex(unsigned int &TileSize) const
1602{
1603 TileSize = sizeof(CSwitchTile);
1604 return m_pLayerTilemap->m_Switch;
1605}
1606
1607void CRenderLayerEntitySwitch::Init()
1608{
1609 UploadTileData(VisualsOptional&: m_VisualTiles, CurOverlay: 0, AddAsSpeedup: false);
1610 UploadTileData(VisualsOptional&: m_VisualSwitchNumberTop, CurOverlay: 1, AddAsSpeedup: false);
1611 UploadTileData(VisualsOptional&: m_VisualSwitchNumberBottom, CurOverlay: 2, AddAsSpeedup: false);
1612}
1613
1614void CRenderLayerEntitySwitch::InitTileData()
1615{
1616 m_pSwitchTiles = GetData<CSwitchTile>();
1617}
1618
1619void CRenderLayerEntitySwitch::Unload()
1620{
1621 CRenderLayerTile::Unload();
1622 if(m_VisualSwitchNumberTop.has_value())
1623 {
1624 m_VisualSwitchNumberTop->Unload();
1625 m_VisualSwitchNumberTop = std::nullopt;
1626 }
1627 if(m_VisualSwitchNumberBottom.has_value())
1628 {
1629 m_VisualSwitchNumberBottom->Unload();
1630 m_VisualSwitchNumberBottom = std::nullopt;
1631 }
1632}
1633
1634void CRenderLayerEntitySwitch::GetTileData(unsigned char *pIndex, unsigned char *pFlags, int *pAngleRotate, unsigned int x, unsigned int y, int CurOverlay) const
1635{
1636 *pFlags = 0;
1637 *pIndex = m_pSwitchTiles[y * m_pLayerTilemap->m_Width + x].m_Type;
1638 if(CurOverlay == 0)
1639 {
1640 *pFlags = m_pSwitchTiles[y * m_pLayerTilemap->m_Width + x].m_Flags;
1641 if(*pIndex == TILE_SWITCHTIMEDOPEN)
1642 *pIndex = 8;
1643 }
1644 else if(CurOverlay == 1)
1645 *pIndex = m_pSwitchTiles[y * m_pLayerTilemap->m_Width + x].m_Number;
1646 else if(CurOverlay == 2)
1647 *pIndex = m_pSwitchTiles[y * m_pLayerTilemap->m_Width + x].m_Delay;
1648}
1649
1650void CRenderLayerEntitySwitch::RenderTileLayerWithTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1651{
1652 Graphics()->BlendNormal();
1653 RenderTileLayer(Color, Params);
1654 if(Params.m_RenderText)
1655 {
1656 Graphics()->TextureSet(Texture: m_pMapImages->GetOverlayTop());
1657 RenderTileLayer(Color, Params, pTileLayerVisuals: &m_VisualSwitchNumberTop.value());
1658 Graphics()->TextureSet(Texture: m_pMapImages->GetOverlayBottom());
1659 RenderTileLayer(Color, Params, pTileLayerVisuals: &m_VisualSwitchNumberBottom.value());
1660 }
1661}
1662
1663void CRenderLayerEntitySwitch::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1664{
1665 Graphics()->BlendNone();
1666 RenderMap()->RenderSwitchmap(pSwitch: m_pSwitchTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_OPAQUE);
1667 Graphics()->BlendNormal();
1668 RenderMap()->RenderSwitchmap(pSwitch: m_pSwitchTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_TRANSPARENT);
1669 int OverlayRenderFlags = (Params.m_RenderText ? OVERLAYRENDERFLAG_TEXT : 0) | (Params.m_RenderInvalidTiles ? OVERLAYRENDERFLAG_EDITOR : 0);
1670 RenderMap()->RenderSwitchOverlay(pSwitch: m_pSwitchTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, OverlayRenderFlags, Alpha: Color.a);
1671}
1672
1673// TUNE
1674CRenderLayerEntityTune::CRenderLayerEntityTune(int GroupId, int LayerId, int Flags, CMapItemLayerTilemap *pLayerTilemap) :
1675 CRenderLayerEntityBase(GroupId, LayerId, Flags, pLayerTilemap) {}
1676
1677void CRenderLayerEntityTune::GetTileData(unsigned char *pIndex, unsigned char *pFlags, int *pAngleRotate, unsigned int x, unsigned int y, int CurOverlay) const
1678{
1679 *pIndex = m_pTuneTiles[y * m_pLayerTilemap->m_Width + x].m_Type;
1680 *pFlags = 0;
1681}
1682
1683int CRenderLayerEntityTune::GetDataIndex(unsigned int &TileSize) const
1684{
1685 TileSize = sizeof(CTuneTile);
1686 return m_pLayerTilemap->m_Tune;
1687}
1688
1689void CRenderLayerEntityTune::InitTileData()
1690{
1691 m_pTuneTiles = GetData<CTuneTile>();
1692}
1693
1694void CRenderLayerEntityTune::RenderTileLayerNoTileBuffer(const ColorRGBA &Color, const CRenderLayerParams &Params)
1695{
1696 Graphics()->BlendNone();
1697 RenderMap()->RenderTunemap(pTune: m_pTuneTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_OPAQUE);
1698 Graphics()->BlendNormal();
1699 RenderMap()->RenderTunemap(pTune: m_pTuneTiles, w: m_pLayerTilemap->m_Width, h: m_pLayerTilemap->m_Height, Scale: 32.0f, Color, RenderFlags: (Params.m_RenderTileBorder ? TILERENDERFLAG_EXTEND : 0) | LAYERRENDERFLAG_TRANSPARENT);
1700}
1701