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 <atomic>
22 #include <cstdlib>
23 
24 #include <Client/GameMap.h>
25 #include "GLAmbientShadowRenderer.h"
26 #include "GLProfiler.h"
27 #include "GLRenderer.h"
28 
29 #include <Core/ConcurrentDispatch.h>
30 
31 namespace spades {
32 	namespace draw {
33 		class GLAmbientShadowRenderer::UpdateDispatch : public ConcurrentDispatch {
34 			GLAmbientShadowRenderer *renderer;
35 
36 		public:
37 			std::atomic<bool> done {false};
UpdateDispatch(GLAmbientShadowRenderer * r)38 			UpdateDispatch(GLAmbientShadowRenderer *r) : renderer(r) { }
Run()39 			void Run() override {
40 				SPADES_MARK_FUNCTION();
41 
42 				renderer->UpdateDirtyChunks();
43 
44 				done = true;
45 			}
46 		};
47 
GLAmbientShadowRenderer(GLRenderer * r,client::GameMap * m)48 		GLAmbientShadowRenderer::GLAmbientShadowRenderer(GLRenderer *r, client::GameMap *m)
49 		    : renderer(r), device(r->GetGLDevice()), map(m) {
50 			SPADES_MARK_FUNCTION();
51 
52 			for (int i = 0; i < NumRays; i++) {
53 				Vector3 dir = MakeVector3(SampleRandomFloat(), SampleRandomFloat(), SampleRandomFloat());
54 				dir = dir.Normalize();
55 				dir += 0.01f;
56 				rays[i] = dir;
57 			}
58 
59 			w = map->Width();
60 			h = map->Height();
61 			d = map->Depth();
62 
63 			chunkW = w / ChunkSize;
64 			chunkH = h / ChunkSize;
65 			chunkD = d / ChunkSize;
66 
67 			chunks = std::vector<Chunk>{static_cast<std::size_t>(chunkW * chunkH * chunkD)};
68 
69 			for (size_t i = 0; i < chunks.size(); i++) {
70 				Chunk &c = chunks[i];
71 				float *data = (float *)c.data;
72 				std::fill(data, data + ChunkSize * ChunkSize * ChunkSize, 1.f);
73 			}
74 
75 			for (int x = 0; x < chunkW; x++)
76 				for (int y = 0; y < chunkH; y++)
77 					for (int z = 0; z < chunkD; z++) {
78 						Chunk &c = GetChunk(x, y, z);
79 						c.cx = x;
80 						c.cy = y;
81 						c.cz = z;
82 					}
83 
84 			SPLog("Chunk buffer allocated (%d bytes)", (int) sizeof(Chunk) * chunkW * chunkH * chunkD);
85 
86 			// make texture
87 			texture = device->GenTexture();
88 			device->BindTexture(IGLDevice::Texture3D, texture);
89 			device->TexParamater(IGLDevice::Texture3D, IGLDevice::TextureMagFilter,
90 			                     IGLDevice::Linear);
91 			device->TexParamater(IGLDevice::Texture3D, IGLDevice::TextureMinFilter,
92 			                     IGLDevice::Linear);
93 			device->TexParamater(IGLDevice::Texture3D, IGLDevice::TextureWrapS, IGLDevice::Repeat);
94 			device->TexParamater(IGLDevice::Texture3D, IGLDevice::TextureWrapT, IGLDevice::Repeat);
95 			device->TexParamater(IGLDevice::Texture3D, IGLDevice::TextureWrapR,
96 			                     IGLDevice::ClampToEdge);
97 			device->TexImage3D(IGLDevice::Texture3D, 0, IGLDevice::Red, w, h, d + 1, 0,
98 			                   IGLDevice::Red, IGLDevice::FloatType, NULL);
99 
100 			SPLog("Chunk texture allocated");
101 
102 			std::vector<float> v;
103 			v.resize(w * h);
104 			std::fill(v.begin(), v.end(), 1.f);
105 			for (int i = 0; i < d + 1; i++) {
106 				device->TexSubImage3D(IGLDevice::Texture3D, 0, 0, 0, i, w, h, 1, IGLDevice::Red,
107 				                      IGLDevice::FloatType, v.data());
108 			}
109 
110 			SPLog("Chunk texture initialized");
111 
112 			dispatch = NULL;
113 		}
114 
~GLAmbientShadowRenderer()115 		GLAmbientShadowRenderer::~GLAmbientShadowRenderer() {
116 			SPADES_MARK_FUNCTION();
117 			if (dispatch) {
118 				dispatch->Join();
119 				delete dispatch;
120 			}
121 			device->DeleteTexture(texture);
122 		}
123 
Evaluate(IntVector3 ipos)124 		float GLAmbientShadowRenderer::Evaluate(IntVector3 ipos) {
125 			SPADES_MARK_FUNCTION_DEBUG();
126 
127 			float sum = 0;
128 			Vector3 pos = MakeVector3((float)ipos.x, (float)ipos.y, (float)ipos.z);
129 
130 			float muzzleDiff = 0.02f;
131 
132 			// check allowed ray direction
133 			uint8_t directions[8] = {0, 1, 2, 3, 4, 5, 6, 7};
134 			int numDirections = 0;
135 			for (int x = -1; x <= 0; x++)
136 				for (int y = -1; y <= 0; y++)
137 					for (int z = -1; z <= 0; z++) {
138 						if (!map->IsSolidWrapped(ipos.x + x, ipos.y + y, ipos.z + z)) {
139 							unsigned int bits = 0;
140 							if (x)
141 								bits |= 1;
142 							if (y)
143 								bits |= 2;
144 							if (z)
145 								bits |= 4;
146 							directions[numDirections++] = bits;
147 						}
148 					}
149 			if (numDirections == 0)
150 				numDirections = 8;
151 
152 			int dirId = 0;
153 
154 			for (int i = 0; i < NumRays; i++) {
155 				Vector3 dir = rays[i];
156 
157 				unsigned int bits = directions[dirId];
158 				if (bits & 1)
159 					dir.x = -dir.x;
160 				if (bits & 2)
161 					dir.y = -dir.y;
162 				if (bits & 4)
163 					dir.z = -dir.z;
164 
165 				dirId++;
166 				if (dirId >= numDirections)
167 					dirId = 0;
168 
169 				Vector3 muzzle = pos + dir * muzzleDiff;
170 				IntVector3 hitBlock;
171 
172 				float brightness = 1.f;
173 				if (map->IsSolidWrapped((int)floorf(muzzle.x), (int)floorf(muzzle.y),
174 				                        (int)floorf(muzzle.z))) {
175 					if (numDirections < 8)
176 						SPAssert(false);
177 					continue;
178 				}
179 				if (map->CastRay(muzzle, dir, 18.f, hitBlock)) {
180 					Vector3 centerPos =
181 					  MakeVector3(hitBlock.x + .5f, hitBlock.y + .5f, hitBlock.z + .5f);
182 					float dist = (centerPos - muzzle).GetPoweredLength();
183 					brightness = dist * 0.02f; // 1/7/7
184 					if (brightness > 1.f)
185 						brightness = 1.f;
186 				}
187 
188 				sum += brightness;
189 			}
190 
191 			sum *= 1.f / (float)NumRays;
192 			sum *= (float)numDirections / 4.f;
193 
194 			return sum;
195 		}
196 
GameMapChanged(int x,int y,int z,client::GameMap * map)197 		void GLAmbientShadowRenderer::GameMapChanged(int x, int y, int z, client::GameMap *map) {
198 			SPADES_MARK_FUNCTION_DEBUG();
199 			if (map != this->map)
200 				return;
201 
202 			Invalidate(x - 8, y - 8, z - 8, x + 8, y + 8, z + 8);
203 		}
204 
Invalidate(int minX,int minY,int minZ,int maxX,int maxY,int maxZ)205 		void GLAmbientShadowRenderer::Invalidate(int minX, int minY, int minZ, int maxX, int maxY,
206 		                                         int maxZ) {
207 			SPADES_MARK_FUNCTION_DEBUG();
208 			if (minZ < 0)
209 				minZ = 0;
210 			if (maxZ > d - 1)
211 				maxZ = d - 1;
212 			if (minX > maxX || minY > maxY || minZ > maxZ)
213 				return;
214 
215 			// these should be floor div
216 			int cx1 = minX >> ChunkSizeBits;
217 			int cy1 = minY >> ChunkSizeBits;
218 			int cz1 = minZ >> ChunkSizeBits;
219 			int cx2 = maxX >> ChunkSizeBits;
220 			int cy2 = maxY >> ChunkSizeBits;
221 			int cz2 = maxZ >> ChunkSizeBits;
222 
223 			for (int cx = cx1; cx <= cx2; cx++)
224 				for (int cy = cy1; cy <= cy2; cy++)
225 					for (int cz = cz1; cz <= cz2; cz++) {
226 						Chunk &c = GetChunkWrapped(cx, cy, cz);
227 						int originX = cx * ChunkSize;
228 						int originY = cy * ChunkSize;
229 						int originZ = cz * ChunkSize;
230 
231 						int inMinX = std::max(minX - originX, 0);
232 						int inMinY = std::max(minY - originY, 0);
233 						int inMinZ = std::max(minZ - originZ, 0);
234 						int inMaxX = std::min(maxX - originX, ChunkSize - 1);
235 						int inMaxY = std::min(maxY - originY, ChunkSize - 1);
236 						int inMaxZ = std::min(maxZ - originZ, ChunkSize - 1);
237 
238 						if (!c.dirty) {
239 							c.dirtyMinX = inMinX;
240 							c.dirtyMinY = inMinY;
241 							c.dirtyMinZ = inMinZ;
242 							c.dirtyMaxX = inMaxX;
243 							c.dirtyMaxY = inMaxY;
244 							c.dirtyMaxZ = inMaxZ;
245 							c.dirty = true;
246 						} else {
247 							c.dirtyMinX = std::min(inMinX, c.dirtyMinX);
248 							c.dirtyMinY = std::min(inMinY, c.dirtyMinY);
249 							c.dirtyMinZ = std::min(inMinZ, c.dirtyMinZ);
250 							c.dirtyMaxX = std::max(inMaxX, c.dirtyMaxX);
251 							c.dirtyMaxY = std::max(inMaxY, c.dirtyMaxY);
252 							c.dirtyMaxZ = std::max(inMaxZ, c.dirtyMaxZ);
253 						}
254 					}
255 		}
256 
GetNumDirtyChunks()257 		int GLAmbientShadowRenderer::GetNumDirtyChunks() {
258 			int cnt = 0;
259 			for (size_t i = 0; i < chunks.size(); i++) {
260 				Chunk &c = chunks[i];
261 				if (c.dirty)
262 					cnt++;
263 			}
264 			return cnt;
265 		}
266 
Update()267 		void GLAmbientShadowRenderer::Update() {
268 			if (GetNumDirtyChunks() > 0 && (dispatch == NULL || dispatch->done)) {
269 				if (dispatch) {
270 					dispatch->Join();
271 					delete dispatch;
272 				}
273 				dispatch = new UpdateDispatch(this);
274 				dispatch->Start();
275 			}
276 
277 			// Count the number of chunks that need to be uploaded to GPU.
278 			// This value is approximate but it should be okay for profiling use
279 			int cnt = 0;
280 			for (size_t i = 0; i < chunks.size(); i++) {
281 				if (!chunks[i].transferDone.load())
282 					cnt++;
283 			}
284 			GLProfiler::Context profiler(renderer->GetGLProfiler(), "Large Ambient Occlusion [>= %d chunk(s)]", cnt);
285 
286 			device->BindTexture(IGLDevice::Texture3D, texture);
287 			for (size_t i = 0; i < chunks.size(); i++) {
288 				Chunk &c = chunks[i];
289 				if (!c.transferDone.exchange(true)) {
290 					device->TexSubImage3D(IGLDevice::Texture3D, 0, c.cx * ChunkSize,
291 					                      c.cy * ChunkSize, c.cz * ChunkSize + 1, ChunkSize,
292 					                      ChunkSize, ChunkSize, IGLDevice::Red,
293 					                      IGLDevice::FloatType, c.data);
294 				}
295 			}
296 		}
297 
UpdateDirtyChunks()298 		void GLAmbientShadowRenderer::UpdateDirtyChunks() {
299 			int dirtyChunkIds[256];
300 			int numDirtyChunks = 0;
301 			int nearDirtyChunks = 0;
302 
303 			// first, check only chunks in near range
304 			Vector3 eyePos = renderer->GetSceneDef().viewOrigin;
305 			int eyeX = (int)(eyePos.x) >> ChunkSizeBits;
306 			int eyeY = (int)(eyePos.y) >> ChunkSizeBits;
307 			int eyeZ = (int)(eyePos.z) >> ChunkSizeBits;
308 
309 			for (size_t i = 0; i < chunks.size(); i++) {
310 				Chunk &c = chunks[i];
311 				int dx = (c.cx - eyeX) & (chunkW - 1);
312 				int dy = (c.cy - eyeY) & (chunkH - 1);
313 				int dz = (c.cz - eyeZ);
314 				if (dx >= 6 && dx <= chunkW - 6)
315 					continue;
316 				if (dy >= 6 && dy <= chunkW - 6)
317 					continue;
318 				if (dz >= 6 || dz <= -6)
319 					continue;
320 				if (c.dirty) {
321 					dirtyChunkIds[numDirtyChunks++] = static_cast<int>(i);
322 					nearDirtyChunks++;
323 					if (numDirtyChunks >= 256)
324 						break;
325 				}
326 			}
327 
328 			// far chunks
329 			if (numDirtyChunks == 0) {
330 				for (size_t i = 0; i < chunks.size(); i++) {
331 					Chunk &c = chunks[i];
332 					if (c.dirty) {
333 						dirtyChunkIds[numDirtyChunks++] = static_cast<int>(i);
334 						if (numDirtyChunks >= 256)
335 							break;
336 					}
337 				}
338 			}
339 
340 			// limit update count per frame
341 			for (int i = 0; i < 8; i++) {
342 				if (numDirtyChunks <= 0)
343 					break;
344                 int idx = SampleRandomInt(0, numDirtyChunks - 1);
345 				Chunk &c = chunks[dirtyChunkIds[idx]];
346 
347 				// remove from list (fast)
348 				if (idx < numDirtyChunks - 1) {
349 					std::swap(dirtyChunkIds[idx], dirtyChunkIds[numDirtyChunks - 1]);
350 				}
351 				numDirtyChunks--;
352 
353 				UpdateChunk(c.cx, c.cy, c.cz);
354 			}
355 			/*
356 			printf("%d (%d near) chunk update left\n",
357 			       GetNumDirtyChunks(), nearDirtyChunks);*/
358 		}
359 
UpdateChunk(int cx,int cy,int cz)360 		void GLAmbientShadowRenderer::UpdateChunk(int cx, int cy, int cz) {
361 			Chunk &c = GetChunk(cx, cy, cz);
362 			if (!c.dirty)
363 				return;
364 
365 			int originX = cx * ChunkSize;
366 			int originY = cy * ChunkSize;
367 			int originZ = cz * ChunkSize;
368 
369 			for (int z = c.dirtyMinZ; z <= c.dirtyMaxZ; z++)
370 				for (int y = c.dirtyMinY; y <= c.dirtyMaxY; y++)
371 					for (int x = c.dirtyMinX; x <= c.dirtyMaxX; x++) {
372 						IntVector3 pos;
373 						pos.x = (x + originX);
374 						pos.y = (y + originY);
375 						pos.z = (z + originZ);
376 
377 						c.data[z][y][x] = Evaluate(pos);
378 					}
379 
380 			c.dirty = false;
381 			c.transferDone = false;
382 		}
383 	}
384 }
385