1 /*
2  Copyright (c) 2013 yvt
3 
4  This file is part of OpenSpades.
5 
6  OpenSpades is free software: you can redistribute it and/or modify
7  it under the terms of the GNU General Public License as published by
8  the Free Software Foundation, either version 3 of the License, or
9  (at your option) any later version.
10 
11  OpenSpades is distributed in the hope that it will be useful,
12  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  GNU General Public License for more details.
15 
16  You should have received a copy of the GNU General Public License
17  along with OpenSpades.  If not, see <http://www.gnu.org/licenses/>.
18 
19  */
20 
21 #include <algorithm>
22 #include <array>
23 #include <cfenv>
24 #include <cstdlib>
25 
26 #include "SWFlatMapRenderer.h"
27 #include "SWImage.h"
28 #include "SWImageRenderer.h"
29 #include "SWMapRenderer.h"
30 #include "SWModel.h"
31 #include "SWModelRenderer.h"
32 #include "SWPort.h"
33 #include "SWRenderer.h"
34 #include <Client/GameMap.h>
35 #include <Core/Bitmap.h>
36 #include <Core/Settings.h>
37 
38 #include "SWUtils.h"
39 
40 DEFINE_SPADES_SETTING(r_swStatistics, "0");
41 DEFINE_SPADES_SETTING(r_swNumThreads, "4");
42 
43 namespace spades {
44 	namespace draw {
SWRenderer(SWPort * port,SWFeatureLevel level)45 		SWRenderer::SWRenderer(SWPort *port, SWFeatureLevel level)
46 		    : featureLevel(level),
47 		      port(port),
48 		      map(nullptr),
49 		      fb(nullptr),
50 		      inited(false),
51 		      sceneUsedInThisFrame(false),
52 		      fogDistance(128.f),
53 		      fogColor(MakeVector3(0.f, 0.f, 0.f)),
54 		      drawColorAlphaPremultiplied(MakeVector4(1, 1, 1, 1)),
55 		      legacyColorPremultiply(false),
56 		      lastTime(0),
57 		      duringSceneRendering(false) {
58 
59 			SPADES_MARK_FUNCTION();
60 
61 			if (port == nullptr) {
62 				SPRaise("Port is null.");
63 			}
64 
65 			SPLog("---- SWRenderer early initialization started ---");
66 
67 #ifdef FE_DFL_DISABLE_SSE_DENORMS_ENV
68 			SPLog("initializing FPU");
69 			fesetenv(FE_DFL_DISABLE_SSE_DENORMS_ENV);
70 #endif
71 
72 			SPLog("creating image manager");
73 			imageManager = std::make_shared<SWImageManager>();
74 
75 			SPLog("creating image renderer");
76 			imageRenderer = std::make_shared<SWImageRenderer>(featureLevel);
77 			imageRenderer->ResetPixelStatistics();
78 			renderStopwatch.Reset();
79 
80 			SPLog("setting framebuffer.");
81 			SetFramebuffer(port->GetFramebuffer());
82 
83 			// alloc depth buffer
84 			SPLog("initializing depth buffer.");
85 			depthBuffer.resize(fb->GetWidth() * fb->GetHeight());
86 			imageRenderer->SetDepthBuffer(depthBuffer.data());
87 
88 			SPLog("---- SWRenderer early initialization done ---");
89 		}
90 
~SWRenderer()91 		SWRenderer::~SWRenderer() {
92 			SPADES_MARK_FUNCTION();
93 
94 			Shutdown();
95 		}
96 
SetFramebuffer(spades::Bitmap * bmp)97 		void SWRenderer::SetFramebuffer(spades::Bitmap *bmp) {
98 			if (bmp == nullptr) {
99 				SPRaise("Framebuffer is null.");
100 			}
101 			if (fb) {
102 				SPAssert(bmp->GetWidth() == fb->GetWidth());
103 				SPAssert(bmp->GetHeight() == fb->GetHeight());
104 			}
105 			fb = bmp;
106 			imageRenderer->SetFramebuffer(bmp);
107 			if ((bmp->GetWidth() & 7) || (bmp->GetHeight() & 7)) {
108 				SPRaise("Framebuffer size is not multiple of 8.");
109 			}
110 		}
111 
Init()112 		void SWRenderer::Init() {
113 			SPADES_MARK_FUNCTION();
114 
115 			SPLog("---- SWRenderer late initialization started ---");
116 			modelManager = std::make_shared<SWModelManager>();
117 
118 			SPLog("creating model renderer");
119 			modelRenderer = std::make_shared<SWModelRenderer>(this, featureLevel);
120 			renderStopwatch.Reset();
121 
122 			SPLog("---- SWRenderer late initialization done ---");
123 
124 			inited = true;
125 		}
126 
Shutdown()127 		void SWRenderer::Shutdown() {
128 			SPADES_MARK_FUNCTION();
129 
130 			SetGameMap(nullptr);
131 
132 			imageRenderer.reset();
133 			flatMapRenderer.reset();
134 
135 			imageManager.reset();
136 			modelManager.reset();
137 			map = nullptr;
138 			port = nullptr;
139 
140 			inited = false;
141 		}
142 
RegisterImage(const char * filename)143 		client::IImage *SWRenderer::RegisterImage(const char *filename) {
144 			SPADES_MARK_FUNCTION();
145 			EnsureValid();
146 			return imageManager->RegisterImage(filename);
147 		}
148 
RegisterModel(const char * filename)149 		client::IModel *SWRenderer::RegisterModel(const char *filename) {
150 			SPADES_MARK_FUNCTION();
151 			EnsureInitialized();
152 			return modelManager->RegisterModel(filename);
153 		}
154 
CreateImage(spades::Bitmap * bmp)155 		client::IImage *SWRenderer::CreateImage(spades::Bitmap *bmp) {
156 			SPADES_MARK_FUNCTION();
157 			EnsureValid();
158 			return imageManager->CreateImage(bmp);
159 		}
160 
CreateModel(spades::VoxelModel * model)161 		client::IModel *SWRenderer::CreateModel(spades::VoxelModel *model) {
162 			SPADES_MARK_FUNCTION();
163 			EnsureInitialized();
164 			return modelManager->CreateModel(model);
165 		}
166 
SetGameMap(client::GameMap * map)167 		void SWRenderer::SetGameMap(client::GameMap *map) {
168 			SPADES_MARK_FUNCTION();
169 			if (map)
170 				EnsureInitialized();
171 			if (map == this->map)
172 				return;
173 
174 			flatMapRenderer.reset();
175 			mapRenderer.reset();
176 
177 			if (this->map)
178 				this->map->RemoveListener(this);
179 			this->map = map;
180 			if (this->map) {
181 				this->map->AddListener(this);
182 				flatMapRenderer = std::make_shared<SWFlatMapRenderer>(this, map);
183 				mapRenderer = std::make_shared<SWMapRenderer>(this, map, featureLevel);
184 			}
185 		}
186 
SetFogColor(spades::Vector3 v)187 		void SWRenderer::SetFogColor(spades::Vector3 v) { fogColor = v; }
188 
BuildProjectionMatrix()189 		void SWRenderer::BuildProjectionMatrix() {
190 			SPADES_MARK_FUNCTION();
191 
192 			float near = sceneDef.zNear;
193 			float far = sceneDef.zFar;
194 			float t = near * tanf(sceneDef.fovY * .5f);
195 			float r = near * tanf(sceneDef.fovX * .5f);
196 			float a = r * 2.f, b = t * 2.f, c = far - near;
197 			Matrix4 mat;
198 			mat.m[0] = near * 2.f / a;
199 			mat.m[1] = 0.f;
200 			mat.m[2] = 0.f;
201 			mat.m[3] = 0.f;
202 			mat.m[4] = 0.f;
203 			mat.m[5] = near * 2.f / b;
204 			mat.m[6] = 0.f;
205 			mat.m[7] = 0.f;
206 			mat.m[8] = 0.f;
207 			mat.m[9] = 0.f;
208 			mat.m[10] = -(far + near) / c;
209 			mat.m[11] = -1.f;
210 			mat.m[12] = 0.f;
211 			mat.m[13] = 0.f;
212 			mat.m[14] = -(far * near * 2.f) / c;
213 			mat.m[15] = 0.f;
214 			projectionMatrix = mat;
215 		}
216 
BuildView()217 		void SWRenderer::BuildView() {
218 			SPADES_MARK_FUNCTION();
219 
220 			Matrix4 mat = Matrix4::Identity();
221 			mat.m[0] = sceneDef.viewAxis[0].x;
222 			mat.m[4] = sceneDef.viewAxis[0].y;
223 			mat.m[8] = sceneDef.viewAxis[0].z;
224 			mat.m[1] = sceneDef.viewAxis[1].x;
225 			mat.m[5] = sceneDef.viewAxis[1].y;
226 			mat.m[9] = sceneDef.viewAxis[1].z;
227 			mat.m[2] = -sceneDef.viewAxis[2].x;
228 			mat.m[6] = -sceneDef.viewAxis[2].y;
229 			mat.m[10] = -sceneDef.viewAxis[2].z;
230 
231 			Vector4 v = mat * sceneDef.viewOrigin;
232 			mat.m[12] = -v.x;
233 			mat.m[13] = -v.y;
234 			mat.m[14] = -v.z;
235 
236 			viewMatrix = mat;
237 		}
238 
BuildFrustrum()239 		void SWRenderer::BuildFrustrum() {
240 			// far/near
241 			frustrum[0] = Plane3::PlaneWithPointOnPlane(sceneDef.viewOrigin, sceneDef.viewAxis[2]);
242 			frustrum[1] = frustrum[0].Flipped();
243 			frustrum[0].w -= sceneDef.zNear;
244 			frustrum[1].w += sceneDef.zFar;
245 
246 			float xCos = cosf(sceneDef.fovX * .5f);
247 			float xSin = sinf(sceneDef.fovX * .5f);
248 			float yCos = cosf(sceneDef.fovY * .5f);
249 			float ySin = sinf(sceneDef.fovY * .5f);
250 
251 			frustrum[2] = Plane3::PlaneWithPointOnPlane(
252 			  sceneDef.viewOrigin, sceneDef.viewAxis[2] * xSin - sceneDef.viewAxis[0] * xCos);
253 			frustrum[3] = Plane3::PlaneWithPointOnPlane(
254 			  sceneDef.viewOrigin, sceneDef.viewAxis[2] * xSin + sceneDef.viewAxis[0] * xCos);
255 			frustrum[4] = Plane3::PlaneWithPointOnPlane(
256 			  sceneDef.viewOrigin, sceneDef.viewAxis[2] * ySin - sceneDef.viewAxis[1] * yCos);
257 			frustrum[5] = Plane3::PlaneWithPointOnPlane(
258 			  sceneDef.viewOrigin, sceneDef.viewAxis[2] * ySin + sceneDef.viewAxis[1] * yCos);
259 		}
260 
ApplyDynamicLight(const DynamicLight & light)261 		template <SWFeatureLevel> void SWRenderer::ApplyDynamicLight(const DynamicLight &light) {
262 			int fw = this->fb->GetWidth();
263 			int fh = this->fb->GetHeight();
264 
265 			float fovX = tanf(sceneDef.fovX * 0.5f);
266 			float fovY = tanf(sceneDef.fovY * 0.5f);
267 
268 			float dvx = -fovX * 2.f / static_cast<float>(fw);
269 			float dvy = -fovY * 2.f / static_cast<float>(fh);
270 
271 			int minX = light.minX;
272 			int minY = light.minY;
273 			int maxX = light.maxX;
274 			int maxY = light.maxY;
275 			int lightHeight = maxY - minY;
276 
277 			SPAssert(minX >= 0);
278 			SPAssert(minY >= 0);
279 			SPAssert(maxX <= fw);
280 			SPAssert(maxY <= fh);
281 
282 			Vector3 lightCenter;
283 			Vector3 diff = light.param.origin - sceneDef.viewOrigin;
284 			lightCenter.x = Vector3::Dot(diff, sceneDef.viewAxis[0]);
285 			lightCenter.y = Vector3::Dot(diff, sceneDef.viewAxis[1]);
286 			lightCenter.z = Vector3::Dot(diff, sceneDef.viewAxis[2]);
287 
288 			int lightR = ToFixedFactor8(light.param.color.x);
289 			int lightG = ToFixedFactor8(light.param.color.y);
290 			int lightB = ToFixedFactor8(light.param.color.z);
291 
292 			float invRadius2 = 1.f / (light.param.radius * light.param.radius);
293 
294 			InvokeParallel2([=](unsigned int threadId, unsigned int numThreads) {
295 				int startY = lightHeight * threadId / numThreads;
296 				int endY = lightHeight * (threadId + 1) / numThreads;
297 				startY += minY;
298 				endY += minY;
299 
300 				auto *fb = this->fb->GetPixels();
301 				float *db = depthBuffer.data();
302 				fb += startY * fw + minX;
303 				db += startY * fw + minX;
304 
305 				float vy = fovY + dvy * startY;
306 				float vx = fovX + dvx * minX;
307 
308 				int lightWidth = maxX - minX;
309 
310 				for (int y = startY; y < endY; y++) {
311 					float vx2 = vx;
312 					auto *fb2 = fb;
313 					auto *db2 = db;
314 
315 					for (int x = lightWidth; x > 0; x--) {
316 						Vector3 pos;
317 
318 						pos.z = *db2;
319 						pos.x = vx2 * pos.z;
320 						pos.y = vy * pos.z;
321 
322 						pos -= lightCenter;
323 
324 						float dist = pos.GetPoweredLength();
325 						dist *= invRadius2;
326 
327 						if (dist < 1.f) {
328 							float strength = 1.f - dist;
329 							strength *= strength;
330 							strength *= 256.f;
331 
332 							int factor = static_cast<int>(strength);
333 
334 							int actualLightR = lightR * factor;
335 							int actualLightG = lightG * factor;
336 							int actualLightB = lightB * factor;
337 
338 							auto srcColor = *fb2;
339 							auto srcColorR = (srcColor >> 16) & 0xff;
340 							auto srcColorG = (srcColor >> 8) & 0xff;
341 							auto srcColorB = srcColor & 0xff;
342 
343 							actualLightR *= srcColorR;
344 							actualLightG *= srcColorG;
345 							actualLightB *= srcColorB;
346 
347 							auto destColorR = actualLightR >> 16;
348 							auto destColorG = actualLightG >> 16;
349 							auto destColorB = actualLightB >> 16;
350 
351 							destColorR = std::min<uint32_t>(destColorR + srcColorR, 255);
352 							destColorG = std::min<uint32_t>(destColorG + srcColorG, 255);
353 							destColorB = std::min<uint32_t>(destColorB + srcColorB, 255);
354 
355 							uint32_t destColor =
356 							  destColorB | (destColorG << 8) | (destColorR << 16);
357 
358 							*fb2 = destColor;
359 						}
360 
361 						vx2 += dvx;
362 						fb2++;
363 						db2++;
364 					}
365 
366 					vy += dvy;
367 					fb += fw;
368 					db += fw;
369 				}
370 			});
371 		}
372 
ApplyFog()373 		template <SWFeatureLevel level> void SWRenderer::ApplyFog() {
374 			int fw = this->fb->GetWidth();
375 			int fh = this->fb->GetHeight();
376 
377 			float fovX = tanf(sceneDef.fovX * 0.5f);
378 			float fovY = tanf(sceneDef.fovY * 0.5f);
379 
380 			float dvx = -fovX * 2.f / static_cast<float>(fw / 4);
381 			float dvy = -fovY * 2.f / static_cast<float>(fh / 4);
382 
383 			int fogR = ToFixed8(fogColor.x);
384 			int fogG = ToFixed8(fogColor.y);
385 			int fogB = ToFixed8(fogColor.z);
386 			uint32_t fog1 = static_cast<uint32_t>(fogB + fogR * 0x10000);
387 			uint32_t fog2 = static_cast<uint32_t>(fogG * 0x100);
388 
389 			float scale = 255.f / fogDistance;
390 
391 			InvokeParallel2([&](unsigned int threadId, unsigned int numThreads) {
392 				int startY = fh * threadId / numThreads;
393 				int endY = fh * (threadId + 1) / numThreads;
394 				startY &= ~3;
395 				endY &= ~3;
396 
397 				float vy = fovY;
398 				auto *fb = this->fb->GetPixels();
399 				float *db = depthBuffer.data();
400 
401 				vy += dvy * (startY >> 2);
402 				fb += fw * startY;
403 				db += fw * startY;
404 
405 				for (int y = startY; y < endY; y += 4) {
406 					float vx = fovX;
407 
408 					for (int x = 0; x < fw; x += 4) {
409 						float depthScale = (1.f + vx * vx + vy * vy);
410 						depthScale *= fastRSqrt(depthScale) * scale;
411 						auto *fb2 = fb + x;
412 						auto *db2 = db + x;
413 						for (int by = 0; by < 4; by++) {
414 							auto *fb3 = fb2;
415 							auto *db3 = db2;
416 
417 							for (int bx = 0; bx < 4; bx++) {
418 
419 								float dist = *db3 * depthScale;
420 								int factor = std::min(static_cast<int>(dist), 256);
421 								factor = std::max(0, factor);
422 								int factor2 = 256 - factor;
423 
424 								uint32_t color = *fb3;
425 								uint32_t v1 = (color & 0xff00ff) * factor2;
426 								uint32_t v2 = (color & 0x00ff00) * factor2;
427 								v1 += fog1 * factor;
428 								v2 += fog2 * factor;
429 								v1 &= 0xff00ff00;
430 								v2 &= 0xff0000;
431 								*fb3 = (v1 | v2) >> 8;
432 
433 								fb3++;
434 								db3++;
435 							}
436 
437 							fb2 += fw;
438 							db2 += fw;
439 						}
440 
441 						vx += dvx;
442 					}
443 
444 					vy += dvy;
445 					fb += fw * 4;
446 					db += fw * 4;
447 				}
448 			});
449 
450 		} // ApplyFog()
451 
452 #if ENABLE_SSE2
453 
ApplyFog()454 		template <> void SWRenderer::ApplyFog<SWFeatureLevel::SSE2>() {
455 			int fw = this->fb->GetWidth();
456 			int fh = this->fb->GetHeight();
457 
458 			float fovX = tanf(sceneDef.fovX * 0.5f);
459 			float fovY = tanf(sceneDef.fovY * 0.5f);
460 
461 			float dvx = -fovX * 2.f / static_cast<float>(fw / 4);
462 			float dvy = -fovY * 2.f / static_cast<float>(fh / 4);
463 
464 			int fogR = ToFixed8(fogColor.x);
465 			int fogG = ToFixed8(fogColor.y);
466 			int fogB = ToFixed8(fogColor.z);
467 			__m128i fog = _mm_setr_epi16(fogB, fogG, fogR, 0, fogB, fogG, fogR, 0);
468 
469 			float scale = 255.f / fogDistance;
470 
471 			InvokeParallel2([&](unsigned int threadId, unsigned int numThreads) {
472 				int startY = fh * threadId / numThreads;
473 				int endY = fh * (threadId + 1) / numThreads;
474 				startY &= ~3;
475 				endY &= ~3;
476 
477 				float vy = fovY;
478 				auto *fb = this->fb->GetPixels();
479 				float *db = depthBuffer.data();
480 
481 				vy += dvy * (startY >> 2);
482 				fb += fw * startY;
483 				db += fw * startY;
484 
485 				for (int y = startY; y < endY; y += 4) {
486 					float vx = fovX;
487 
488 					for (int x = 0; x < fw; x += 4) {
489 						float depthScale = (1.f + vx * vx + vy * vy);
490 						depthScale *= fastRSqrt(depthScale) * scale;
491 						auto depthScale4 = _mm_set1_ps(depthScale);
492 
493 						auto *fb2 = fb + x;
494 						auto *db2 = db + x;
495 						for (int by = 0; by < 4; by++) {
496 							auto *fb3 = fb2;
497 							auto *db3 = db2;
498 
499 							auto dist = _mm_load_ps(db3);
500 							auto color = _mm_load_si128(reinterpret_cast<__m128i *>(fb3));
501 
502 							dist = _mm_mul_ps(dist, depthScale4);
503 							dist = _mm_max_ps(dist, _mm_set1_ps(0.f));
504 							dist = _mm_min_ps(dist, _mm_set1_ps(256.f));
505 							auto factorX = _mm_cvtps_epi32(dist);
506 
507 							auto factorY = _mm_sub_epi32(_mm_set1_epi32(0x100), factorX);
508 
509 							factorX = _mm_shufflelo_epi16(factorX, 0xa0);
510 							factorX = _mm_shufflehi_epi16(factorX, 0xa0);
511 							factorY = _mm_shufflelo_epi16(factorY, 0xa0);
512 							factorY = _mm_shufflehi_epi16(factorY, 0xa0);
513 
514 							// first 2px
515 							auto color1 = _mm_unpacklo_epi8(color, _mm_setzero_si128());
516 							auto factor1X = _mm_shuffle_epi32(factorY, 0x50);
517 							auto factor1Y = _mm_shuffle_epi32(factorX, 0x50);
518 							color1 = _mm_mullo_epi16(color1, factor1X);
519 							auto fog1 = _mm_mullo_epi16(fog, factor1Y);
520 							fog1 = _mm_adds_epu16(fog1, color1);
521 							fog1 = _mm_srli_epi16(fog1, 8);
522 
523 							// next 2px
524 							auto color2 = _mm_unpackhi_epi8(color, _mm_setzero_si128());
525 							auto factor2X = _mm_shuffle_epi32(factorY, 0xfa);
526 							auto factor2Y = _mm_shuffle_epi32(factorX, 0xfa);
527 							color2 = _mm_mullo_epi16(color2, factor2X);
528 							auto fog2 = _mm_mullo_epi16(fog, factor2Y);
529 							fog2 = _mm_adds_epu16(fog2, color2);
530 							fog2 = _mm_srli_epi16(fog2, 8);
531 
532 							auto pack = _mm_packus_epi16(fog1, fog2);
533 							_mm_store_si128(reinterpret_cast<__m128i *>(fb3), pack);
534 
535 							fb2 += fw;
536 							db2 += fw;
537 						}
538 
539 						vx += dvx;
540 					}
541 
542 					vy += dvy;
543 					fb += fw * 4;
544 					db += fw * 4;
545 				}
546 			});
547 
548 		} // ApplyFog()
549 
550 #endif
551 
EnsureSceneStarted()552 		void SWRenderer::EnsureSceneStarted() {
553 			SPADES_MARK_FUNCTION_DEBUG();
554 			if (!duringSceneRendering) {
555 				SPRaise("Illegal call outside of StartScene ... EndScene");
556 			}
557 		}
558 
EnsureSceneNotStarted()559 		void SWRenderer::EnsureSceneNotStarted() {
560 			SPADES_MARK_FUNCTION_DEBUG();
561 			if (duringSceneRendering) {
562 				SPRaise("Illegal call between StartScene ... EndScene");
563 			}
564 		}
565 
EnsureInitialized()566 		void SWRenderer::EnsureInitialized() {
567 			SPADES_MARK_FUNCTION_DEBUG();
568 			if (!inited) {
569 				SPRaise("Renderer is not initialized");
570 			}
571 			EnsureValid();
572 		}
573 
EnsureValid()574 		void SWRenderer::EnsureValid() {
575 			SPADES_MARK_FUNCTION_DEBUG();
576 			if (!port) {
577 				SPRaise("Renderer is not valid");
578 			}
579 		}
580 
StartScene(const client::SceneDefinition & def)581 		void SWRenderer::StartScene(const client::SceneDefinition &def) {
582 			SPADES_MARK_FUNCTION();
583 
584 			EnsureInitialized();
585 			EnsureSceneNotStarted();
586 
587 			sceneDef = def;
588 			duringSceneRendering = true;
589 
590 			BuildProjectionMatrix();
591 			BuildView();
592 			BuildFrustrum();
593 
594 			projectionViewMatrix = projectionMatrix * viewMatrix;
595 		}
596 
RenderModel(client::IModel * model,const client::ModelRenderParam & param)597 		void SWRenderer::RenderModel(client::IModel *model, const client::ModelRenderParam &param) {
598 			SPADES_MARK_FUNCTION();
599 			EnsureInitialized();
600 			EnsureSceneStarted();
601 
602 			auto *mdl = dynamic_cast<SWModel *>(model);
603 			if (mdl == nullptr)
604 				SPInvalidArgument("model");
605 
606 			Model m;
607 			m.model = mdl;
608 			m.param = param;
609 
610 			models.push_back(m);
611 		}
612 
AddLight(const client::DynamicLightParam & param)613 		void SWRenderer::AddLight(const client::DynamicLightParam &param) {
614 			SPADES_MARK_FUNCTION();
615 			EnsureInitialized();
616 			EnsureSceneStarted();
617 
618 			if (param.type != client::DynamicLightTypePoint) {
619 				// TODO: support non-point lights
620 				return;
621 			}
622 
623 			auto diff = param.origin - sceneDef.viewOrigin;
624 			float rad2 = param.radius * param.radius;
625 			float poweredLength = diff.GetPoweredLength();
626 
627 			float fogCullRange = param.radius + fogDistance;
628 			if (poweredLength > fogCullRange * fogCullRange) {
629 				// fog cull
630 				return;
631 			}
632 
633 			DynamicLight light;
634 			if (poweredLength < rad2) {
635 				light.minX = 0;
636 				light.minY = 0;
637 				light.maxX = fb->GetWidth();
638 				light.maxY = fb->GetHeight();
639 			} else if (Vector3::Dot(diff, sceneDef.viewAxis[2]) < 0.f) {
640 				// view plane cull
641 				return;
642 			} else {
643 				auto viewRange = [](float cx, float cy, float fov,
644 				                    float screenSize) -> std::array<int, 2> {
645 					auto trans = [screenSize, fov](float v) {
646 						v = (v / fov) * 0.5f + 0.5f;
647 						v = std::max(v, 0.f);
648 						v = std::min(v, 1.f);
649 						v *= screenSize;
650 						return static_cast<int>(v);
651 					};
652 					auto dist = cx * cx + cy * cy - 1.f;
653 					if (dist <= 0.f) {
654 						return std::array<int, 2>{{0, static_cast<int>(screenSize)}};
655 					}
656 
657 					auto denom = cx * cx - 1.f;
658 					if (fabsf(denom) < 1.e-10f) {
659 						denom = 1.e-8f;
660 					}
661 					denom = 1.f / denom;
662 
663 					dist = sqrtf(dist);
664 
665 					if (cx <= 1.f) {
666 						if (cy > 0.f) {
667 							return std::array<int, 2>{
668 							  {trans(cx * cy - dist), static_cast<int>(screenSize)}};
669 						} else {
670 							return std::array<int, 2>{{0, trans(cx * cy + dist)}};
671 						}
672 					} else {
673 						return std::array<int, 2>{{trans(cx * cy - dist), trans(cx * cy + dist)}};
674 					}
675 				};
676 				auto invRad = 1.f / param.radius;
677 				auto rangeX = viewRange(Vector3::Dot(diff, sceneDef.viewAxis[2]) * invRad,
678 				                        Vector3::Dot(diff, sceneDef.viewAxis[0]) * invRad,
679 				                        tanf(sceneDef.fovX * 0.5f), ScreenWidth());
680 				auto rangeY = viewRange(Vector3::Dot(diff, sceneDef.viewAxis[1]) * invRad,
681 				                        Vector3::Dot(diff, sceneDef.viewAxis[0]) * invRad,
682 				                        tanf(sceneDef.fovY * 0.5f), ScreenHeight());
683 				light.minX = rangeX[0];
684 				light.maxX = rangeX[1];
685 				light.minY = rangeY[0];
686 				light.maxY = rangeY[1];
687 			}
688 
689 			light.param = param;
690 			lights.push_back(light);
691 		}
692 
AddDebugLine(spades::Vector3 a,spades::Vector3 b,spades::Vector4 color)693 		void SWRenderer::AddDebugLine(spades::Vector3 a, spades::Vector3 b, spades::Vector4 color) {
694 			EnsureInitialized();
695 			EnsureSceneStarted();
696 
697 			DebugLine l = {a, b, color};
698 			debugLines.push_back(l);
699 		}
700 
AddSprite(client::IImage * image,spades::Vector3 center,float radius,float rotation)701 		void SWRenderer::AddSprite(client::IImage *image, spades::Vector3 center, float radius,
702 		                           float rotation) {
703 			SPADES_MARK_FUNCTION();
704 			EnsureInitialized();
705 			EnsureSceneStarted();
706 
707 			if (!SphereFrustrumCull(center, radius * 1.5f))
708 				return;
709 
710 			SWImage *img = dynamic_cast<SWImage *>(image);
711 			if (!img) {
712 				SPInvalidArgument("image");
713 			}
714 
715 			sprites.push_back(Sprite());
716 			auto &spr = sprites.back();
717 
718 			spr.img = img;
719 			spr.center = center;
720 			spr.radius = radius;
721 			spr.rotation = rotation;
722 			spr.color = drawColorAlphaPremultiplied;
723 		}
724 
AddLongSprite(client::IImage *,spades::Vector3 p1,spades::Vector3 p2,float radius)725 		void SWRenderer::AddLongSprite(client::IImage *, spades::Vector3 p1, spades::Vector3 p2,
726 		                               float radius) {
727 			SPADES_MARK_FUNCTION();
728 			EnsureInitialized();
729 			EnsureSceneStarted();
730 			// TODO: long sprite
731 		}
732 
ConvertColor32(Vector4 col)733 		static uint32_t ConvertColor32(Vector4 col) {
734 			auto convertColor = [](float f) {
735 				int i = static_cast<int>(f * 255.f + .5f);
736 				return static_cast<uint32_t>(std::max(std::min(i, 255), 0));
737 			};
738 			uint32_t c;
739 			c = convertColor(col.x);
740 			c |= convertColor(col.y) << 8;
741 			c |= convertColor(col.z) << 16;
742 			c |= convertColor(col.w) << 24;
743 			return c;
744 		}
745 
EndScene()746 		void SWRenderer::EndScene() {
747 			EnsureInitialized();
748 			EnsureSceneStarted();
749 
750 			// clear scene
751 			std::fill(fb->GetPixels(), fb->GetPixels() + fb->GetWidth() * fb->GetHeight(),
752 			          ConvertColor32(MakeVector4(fogColor.x, fogColor.y, fogColor.z, 1.f)));
753 			std::fill(fb->GetPixels(), fb->GetPixels() + fb->GetWidth() * fb->GetHeight(),
754 			          0x7f7f7f);
755 
756 			// draw map
757 			if (mapRenderer) {
758 				// flat map renderer sends 'Update RLE' to map renderer.
759 				// rendering map before this leads to the corrupted renderer image.
760 				flatMapRenderer->Update();
761 				mapRenderer->Render(sceneDef, fb, depthBuffer.data());
762 			}
763 
764 			// draw models
765 			for (auto &m : models) {
766 				modelRenderer->Render(m.model, m.param);
767 			}
768 			models.clear();
769 
770 			// deferred lighting
771 			for (const auto &light : lights) {
772 				ApplyDynamicLight<SWFeatureLevel::None>(light);
773 			}
774 			lights.clear();
775 
776 #if ENABLE_SSE2
777 			if (static_cast<int>(featureLevel) >= static_cast<int>(SWFeatureLevel::SSE2))
778 				ApplyFog<SWFeatureLevel::SSE2>();
779 			else
780 #endif
781 				ApplyFog<SWFeatureLevel::None>();
782 
783 			// render sprites
784 			{
785 				imageRenderer->SetShaderType(SWImageRenderer::ShaderType::Sprite);
786 				imageRenderer->SetMatrix(projectionViewMatrix);
787 				imageRenderer->SetZRange(sceneDef.zNear, sceneDef.zFar);
788 
789 				auto right = sceneDef.viewAxis[0];
790 				auto up = sceneDef.viewAxis[1];
791 				for (std::size_t i = 0; i < sprites.size(); i++) {
792 					auto &spr = sprites[i];
793 					float s = sinf(spr.rotation) * spr.radius;
794 					float c = cosf(spr.rotation) * spr.radius;
795 					auto trans = [s, c, &spr, right, up](float x, float y) {
796 						auto v = spr.center;
797 						v += right * (c * x - s * y);
798 						v += up * (s * x + c * y);
799 						return MakeVector4(v.x, v.y, v.z, 1.f);
800 					};
801 					auto x1 = trans(-1.f, -1.f);
802 					auto x2 = trans(1.f, -1.f);
803 					auto x3 = trans(-1.f, 1.f);
804 					auto x4 = trans(1.f, 1.f);
805 					SWImageRenderer::Vertex v1, v2, v3;
806 					v1.color = v2.color = v3.color = spr.color;
807 					v1.uv = MakeVector2(0.f, 0.f);
808 					v1.position = x1;
809 					v2.uv = MakeVector2(1.f, 0.f);
810 					v2.position = x2;
811 					v3.uv = MakeVector2(0.f, 1.f);
812 					v3.position = x3;
813 					imageRenderer->DrawPolygon(spr.img, v1, v2, v3);
814 					v1.uv = MakeVector2(1.f, 0.f);
815 					v1.position = x2;
816 					v2.uv = MakeVector2(1.f, 1.f);
817 					v2.position = x4;
818 					v3.uv = MakeVector2(0.f, 1.f);
819 					v3.position = x3;
820 					imageRenderer->DrawPolygon(spr.img, v1, v2, v3);
821 				}
822 				sprites.clear();
823 			}
824 
825 			// render debug lines
826 			{
827 				float cw = fb->GetWidth() * 0.5f;
828 				float ch = fb->GetHeight() * 0.5f;
829 				for (size_t i = 0; i < debugLines.size(); i++) {
830 					auto &l = debugLines[i];
831 					auto v1 = projectionViewMatrix * l.v1;
832 					auto v2 = projectionViewMatrix * l.v2;
833 					if (v1.z < 0.001f || v2.z < 0.001f)
834 						continue;
835 
836 					// SWRenderer's depth value is based on view Z coord
837 					float d1 = Vector3::Dot(l.v1 - sceneDef.viewOrigin, sceneDef.viewAxis[2]);
838 					float d2 = Vector3::Dot(l.v2 - sceneDef.viewOrigin, sceneDef.viewAxis[2]);
839 					d1 = fastRcp(d1);
840 					d2 = fastRcp(d2);
841 
842 					v1 *= fastRcp(v1.w);
843 					v2 *= fastRcp(v2.w);
844 
845 					int x1 = static_cast<int>(v1.x * cw + cw);
846 					int y1 = static_cast<int>(ch - v1.y * ch);
847 					int x2 = static_cast<int>(v2.x * cw + cw);
848 					int y2 = static_cast<int>(ch - v2.y * ch);
849 
850 					auto *fb = this->fb->GetPixels();
851 					auto *db = this->depthBuffer.data();
852 					int fw = this->fb->GetWidth();
853 					int fh = this->fb->GetHeight();
854 
855 					uint32_t col;
856 					col = ToFixed8(l.color.z) | (ToFixed8(l.color.y) << 8) |
857 					      (ToFixed8(l.color.x) << 16);
858 
859 					if (x1 == x2 && y1 == y2) {
860 						if (x1 >= 0 && y1 >= 0 && x1 < fw && y1 < fh) {
861 							d1 = fastRcp(d1);
862 							if (d1 < db[x1 + y1 * fw]) {
863 								fb[x1 + y1 * fw] = col;
864 							}
865 						}
866 						continue;
867 					}
868 
869 					if (abs(x2 - x1) > abs(y2 - y1)) {
870 						if (x1 >= x2) {
871 							std::swap(x1, x2);
872 							std::swap(y1, y2);
873 							std::swap(d1, d2);
874 						}
875 						int sgn = (y2 > y1) ? 1 : -1;
876 						int cy = y1;
877 						int dy = abs(y2 - y1);
878 						int fract = 0;
879 						int divisor = x2 - x1;
880 						int minX = std::max(x1, 0);
881 						int maxX = std::min(x2, fw - 1);
882 						float depth = d1, ddepth = (d2 - d1) * fastRcp(x2 - x1 + 1);
883 						if (x1 < 0) {
884 							long long v = dy;
885 							v *= -x1;
886 							cy += sgn * (v / divisor);
887 							fract = v % divisor;
888 							depth -= x1 * ddepth;
889 						}
890 						fb += minX;
891 						db += minX;
892 						for (int x = minX; x <= maxX; x++) {
893 							if (cy >= 0 && cy < fh) {
894 								float d = fastRcp(depth);
895 								if (d < db[fw * cy]) {
896 									fb[fw * cy] = col;
897 								}
898 							}
899 							fract += dy;
900 							if (fract >= divisor) {
901 								cy += sgn;
902 								fract -= divisor;
903 							}
904 							depth += ddepth;
905 							fb++;
906 							db++;
907 						}
908 					} else {
909 						if (y1 >= y2) {
910 							std::swap(x1, x2);
911 							std::swap(y1, y2);
912 							std::swap(d1, d2);
913 						}
914 						int sgn = (x2 > x1) ? 1 : -1;
915 						int cx = x1;
916 						int dx = abs(x2 - x1);
917 						int fract = 0;
918 						int divisor = y2 - y1;
919 						int minY = std::max(y1, 0);
920 						int maxY = std::min(y2, fh - 1);
921 						float depth = d1, ddepth = (d2 - d1) * fastRcp(y2 - y1 + 1);
922 						if (y1 < 0) {
923 							long long v = dx;
924 							v *= -y1;
925 							cx += sgn * (v / divisor);
926 							fract = v % divisor;
927 							depth -= x1 * ddepth;
928 						}
929 						fb += minY * fw;
930 						db += minY * fw;
931 						for (int y = minY; y <= maxY; y++) {
932 							if (cx >= 0 && cx < fw) {
933 								float d = fastRcp(depth);
934 								if (d < db[cx]) {
935 									fb[cx] = col;
936 								}
937 							}
938 							fract += dx;
939 							if (fract >= divisor) {
940 								cx += sgn;
941 								fract -= divisor;
942 							}
943 							depth += ddepth;
944 							fb += fw;
945 							db += fw;
946 						}
947 					}
948 				}
949 				debugLines.clear();
950 			}
951 
952 			// all objects were rendered
953 
954 			duringSceneRendering = false;
955 		}
956 
MultiplyScreenColor(spades::Vector3 v)957 		void SWRenderer::MultiplyScreenColor(spades::Vector3 v) { EnsureSceneNotStarted(); }
958 
SetColor(spades::Vector4 col)959 		void SWRenderer::SetColor(spades::Vector4 col) {
960 			EnsureValid();
961 			drawColorAlphaPremultiplied = col;
962 			legacyColorPremultiply = true;
963 		}
964 
SetColorAlphaPremultiplied(spades::Vector4 col)965 		void SWRenderer::SetColorAlphaPremultiplied(spades::Vector4 col) {
966 			EnsureValid();
967 			legacyColorPremultiply = false;
968 			drawColorAlphaPremultiplied = col;
969 		}
970 
DrawImage(client::IImage * image,const spades::Vector2 & outTopLeft)971 		void SWRenderer::DrawImage(client::IImage *image, const spades::Vector2 &outTopLeft) {
972 			SPADES_MARK_FUNCTION();
973 
974 			if (image == nullptr) {
975 				SPRaise("Size must be specified when null image is provided");
976 			}
977 
978 			DrawImage(image,
979 			          AABB2(outTopLeft.x, outTopLeft.y, image->GetWidth(), image->GetHeight()),
980 			          AABB2(0, 0, image->GetWidth(), image->GetHeight()));
981 		}
982 
DrawImage(client::IImage * image,const spades::AABB2 & outRect)983 		void SWRenderer::DrawImage(client::IImage *image, const spades::AABB2 &outRect) {
984 			SPADES_MARK_FUNCTION();
985 
986 			DrawImage(image, outRect,
987 			          AABB2(0, 0, image ? image->GetWidth() : 0, image ? image->GetHeight() : 0));
988 		}
989 
DrawImage(client::IImage * image,const spades::Vector2 & outTopLeft,const spades::AABB2 & inRect)990 		void SWRenderer::DrawImage(client::IImage *image, const spades::Vector2 &outTopLeft,
991 		                           const spades::AABB2 &inRect) {
992 			SPADES_MARK_FUNCTION();
993 
994 			DrawImage(image,
995 			          AABB2(outTopLeft.x, outTopLeft.y, inRect.GetWidth(), inRect.GetHeight()),
996 			          inRect);
997 		}
998 
DrawImage(client::IImage * image,const spades::AABB2 & outRect,const spades::AABB2 & inRect)999 		void SWRenderer::DrawImage(client::IImage *image, const spades::AABB2 &outRect,
1000 		                           const spades::AABB2 &inRect) {
1001 			SPADES_MARK_FUNCTION();
1002 
1003 			DrawImage(image, Vector2::Make(outRect.GetMinX(), outRect.GetMinY()),
1004 			          Vector2::Make(outRect.GetMaxX(), outRect.GetMinY()),
1005 			          Vector2::Make(outRect.GetMinX(), outRect.GetMaxY()), inRect);
1006 		}
1007 
DrawImage(client::IImage * image,const spades::Vector2 & outTopLeft,const spades::Vector2 & outTopRight,const spades::Vector2 & outBottomLeft,const spades::AABB2 & inRect)1008 		void SWRenderer::DrawImage(client::IImage *image, const spades::Vector2 &outTopLeft,
1009 		                           const spades::Vector2 &outTopRight,
1010 		                           const spades::Vector2 &outBottomLeft,
1011 		                           const spades::AABB2 &inRect) {
1012 			SPADES_MARK_FUNCTION();
1013 
1014 			EnsureValid();
1015 			EnsureSceneNotStarted();
1016 
1017 			// d = a + (b - a) + (c - a)
1018 			//   = b + c - a
1019 			Vector2 outBottomRight = outTopRight + outBottomLeft - outTopLeft;
1020 
1021 			SWImage *img = dynamic_cast<SWImage *>(image);
1022 			if (img == nullptr && image != nullptr) {
1023 				// not SWImage
1024 				SPInvalidArgument("image");
1025 			}
1026 
1027 			imageRenderer->SetShaderType(SWImageRenderer::ShaderType::Image);
1028 
1029 			Vector4 col = drawColorAlphaPremultiplied;
1030 			if (legacyColorPremultiply) {
1031 				// in legacy mode, image color is
1032 				// non alpha-premultiplied.
1033 				col.x *= col.w;
1034 				col.y *= col.w;
1035 				col.z *= col.w;
1036 			}
1037 
1038 			std::array<SWImageRenderer::Vertex, 4> vtx;
1039 			vtx[0].color = col;
1040 			vtx[1].color = col;
1041 			vtx[2].color = col;
1042 			vtx[3].color = col;
1043 
1044 			vtx[0].position = MakeVector4(outTopLeft.x, outTopLeft.y, 1.f, 1.f);
1045 			vtx[1].position = MakeVector4(outTopRight.x, outTopRight.y, 1.f, 1.f);
1046 			vtx[2].position = MakeVector4(outBottomLeft.x, outBottomLeft.y, 1.f, 1.f);
1047 			vtx[3].position = MakeVector4(outBottomRight.x, outBottomRight.y, 1.f, 1.f);
1048 			if (img) {
1049 				Vector2 scl = {img->GetInvWidth(), img->GetInvHeight()};
1050 				vtx[0].uv = MakeVector2(inRect.min.x, inRect.min.y) * scl;
1051 				vtx[1].uv = MakeVector2(inRect.max.x, inRect.min.y) * scl;
1052 				vtx[2].uv = MakeVector2(inRect.min.x, inRect.max.y) * scl;
1053 				vtx[3].uv = MakeVector2(inRect.max.x, inRect.max.y) * scl;
1054 			}
1055 
1056 			imageRenderer->DrawPolygon(img, vtx[0], vtx[1], vtx[2]);
1057 			imageRenderer->DrawPolygon(img, vtx[1], vtx[3], vtx[2]);
1058 		}
1059 
DrawFlatGameMap(const spades::AABB2 & outRect,const spades::AABB2 & inRect)1060 		void SWRenderer::DrawFlatGameMap(const spades::AABB2 &outRect,
1061 		                                 const spades::AABB2 &inRect) {
1062 			SPADES_MARK_FUNCTION();
1063 			EnsureValid();
1064 			EnsureSceneNotStarted();
1065 
1066 			if (!flatMapRenderer) {
1067 				SPRaise("DrawFlatGameMap was called without an active map.");
1068 			}
1069 
1070 			DrawImage(flatMapRenderer->GetImage(), outRect, inRect);
1071 		}
1072 
FrameDone()1073 		void SWRenderer::FrameDone() {
1074 			SPADES_MARK_FUNCTION();
1075 			EnsureValid();
1076 			EnsureSceneNotStarted();
1077 		}
1078 
Flip()1079 		void SWRenderer::Flip() {
1080 			SPADES_MARK_FUNCTION();
1081 			EnsureValid();
1082 			EnsureSceneNotStarted();
1083 
1084 			if (r_swStatistics) {
1085 				double dur = renderStopwatch.GetTime();
1086 				SPLog("==== SWRenderer Statistics ====");
1087 				SPLog("Elapsed Time: %.3fus", dur * 1000000.0);
1088 				SPLog("Polygon pixels drawn: %llu", imageRenderer->GetPixelsDrawn());
1089 			}
1090 
1091 			imageRenderer->ResetPixelStatistics();
1092 			renderStopwatch.Reset();
1093 			/*
1094 			{
1095 			    uint32_t rdb = mt_engine();
1096 			    uint32_t *ptr = fb->GetPixels();
1097 			    for(int pixels = fb->GetWidth() * fb->GetHeight() / 10;
1098 			        pixels > 0; pixels--) {
1099 			        *ptr = ((rdb >> 16) & 0xff) * 0x10101;
1100 			        rdb = (rdb * 34563511) ^ 1245525;
1101 			        rdb += (pixels >> 10) + 1;
1102 			        ptr++;
1103 			    }
1104 			}
1105 			*/
1106 			port->Swap();
1107 
1108 			// next frame's framebuffer
1109 			SetFramebuffer(port->GetFramebuffer());
1110 		}
1111 
ReadBitmap()1112 		Bitmap *SWRenderer::ReadBitmap() {
1113 			SPADES_MARK_FUNCTION();
1114 			EnsureValid();
1115 			EnsureSceneNotStarted();
1116 
1117 			int w = fb->GetWidth();
1118 			int h = fb->GetHeight();
1119 			uint32_t *inPix = fb->GetPixels();
1120 			Bitmap *bm = new Bitmap(w, h);
1121 			uint32_t *outPix = bm->GetPixels();
1122 			for (int y = 0; y < h; y++) {
1123 				uint32_t *src = inPix + y * w;
1124 				uint32_t *dest = outPix + (h - 1 - y) * w;
1125 				for (int x = w; x != 0; x--) {
1126 					auto c = *(src++);
1127 					c = 0xff000000 | (c & 0xff00) | ((c & 0xff) << 16) | ((c & 0xff0000) >> 16);
1128 					*(dest++) = c;
1129 				}
1130 			}
1131 			return bm;
1132 		}
1133 
ScreenWidth()1134 		float SWRenderer::ScreenWidth() {
1135 			SPADES_MARK_FUNCTION();
1136 			EnsureValid();
1137 			return static_cast<float>(fb->GetWidth());
1138 		}
1139 
ScreenHeight()1140 		float SWRenderer::ScreenHeight() {
1141 			SPADES_MARK_FUNCTION();
1142 			EnsureValid();
1143 			return static_cast<float>(fb->GetHeight());
1144 		}
1145 
BoxFrustrumCull(const AABB3 & box)1146 		bool SWRenderer::BoxFrustrumCull(const AABB3 &box) {
1147 			/*if(IsRenderingMirror()) {
1148 			    // reflect
1149 			    AABB3 bx = box;
1150 			    std::swap(bx.min.z, bx.max.z);
1151 			    bx.min.z = 63.f * 2.f - bx.min.z;
1152 			    bx.max.z = 63.f * 2.f - bx.max.z;
1153 			    return PlaneCullTest(frustrum[0], bx) &&
1154 			    PlaneCullTest(frustrum[1], bx) &&
1155 			    PlaneCullTest(frustrum[2], bx) &&
1156 			    PlaneCullTest(frustrum[3], bx) &&
1157 			    PlaneCullTest(frustrum[4], bx) &&
1158 			    PlaneCullTest(frustrum[5], bx);
1159 			}*/
1160 			return PlaneCullTest(frustrum[0], box) && PlaneCullTest(frustrum[1], box) &&
1161 			       PlaneCullTest(frustrum[2], box) && PlaneCullTest(frustrum[3], box) &&
1162 			       PlaneCullTest(frustrum[4], box) && PlaneCullTest(frustrum[5], box);
1163 		}
SphereFrustrumCull(const Vector3 & center,float radius)1164 		bool SWRenderer::SphereFrustrumCull(const Vector3 &center, float radius) {
1165 			/*if(IsRenderingMirror()) {
1166 			    // reflect
1167 			    Vector3 vx = center;
1168 			    vx.z = 63.f * 2.f - vx.z;
1169 			    for(int i = 0; i < 6; i++){
1170 			        if(frustrum[i].GetDistanceTo(vx) < -radius)
1171 			            return false;
1172 			    }
1173 			    return true;
1174 			}*/
1175 			for (int i = 0; i < 6; i++) {
1176 				if (frustrum[i].GetDistanceTo(center) < -radius)
1177 					return false;
1178 			}
1179 			return true;
1180 		}
1181 
GameMapChanged(int x,int y,int z,client::GameMap * map)1182 		void SWRenderer::GameMapChanged(int x, int y, int z, client::GameMap *map) {
1183 			if (map != this->map) {
1184 				return;
1185 			}
1186 
1187 			flatMapRenderer->SetNeedsUpdate(x, y);
1188 		}
1189 	}
1190 }
1191