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