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