void StartGenerationThreads() { if (effectiveMultithreadGeneration) { generationThreadsRunning = true; for (int k = 0; k < meshingThreads.Length; k++) { MeshingThread thread = meshingThreads [k]; thread.waitEvent = new AutoResetEvent(false); thread.meshGenerationThread = new Thread(() => GenerateChunkMeshDataInBackgroundThread(thread)); thread.meshGenerationThread.Start(); } } }
void GenerateChunkMeshDataInMainThread(long endTime) { long elapsed; MeshingThread thread = meshingThreads [0]; do { if (thread.meshJobMeshDataGenerationIndex == thread.meshJobMeshLastIndex) { return; } GenerateChunkMeshDataOneJob(thread); thread.meshJobMeshDataGenerationReadyIndex = thread.meshJobMeshDataGenerationIndex; elapsed = stopWatch.ElapsedMilliseconds; } while (elapsed < endTime); }
void GenerateChunkMeshDataInBackgroundThread(MeshingThread thread) { try { while (generationThreadsRunning) { bool idle; lock (thread.indicesUpdating) { idle = thread.meshJobMeshDataGenerationIndex == thread.meshJobMeshLastIndex; } if (idle) { thread.waitEvent.WaitOne(); continue; } GenerateChunkMeshDataOneJob(thread); lock (thread.indicesUpdating) { thread.meshJobMeshDataGenerationReadyIndex = thread.meshJobMeshDataGenerationIndex; } } } catch (Exception ex) { ShowExceptionMessage(ex); } }
void StopGenerationThreads() { generationThreadsRunning = false; if (meshingThreads == null) { return; } for (int k = 0; k < meshingThreads.Length; k++) { MeshingThread meshingThread = meshingThreads [k]; if (meshingThread != null && meshingThread.meshGenerationThread != null) { meshingThread.waitEvent.Set(); } } for (int t = 0; t < meshingThreads.Length; t++) { MeshingThread meshingThread = meshingThreads [t]; if (meshingThread != null && meshingThread.meshGenerationThread != null) { for (int k = 0; k < 100; k++) { bool wait = false; if (meshingThread.meshGenerationThread.IsAlive) { wait = true; } if (!wait) { break; } Thread.Sleep(10); } } } }
void UpdateAndNotifyChunkChanges(MeshingThread thread) { // The background thread generation is working... process whatever is ready to be uploaded to the GPU bool finishedUploading = false; for (int k = 0; k < 100; k++) { lock (thread.indicesUpdating) { if (thread.meshJobMeshUploadIndex == thread.meshJobMeshDataGenerationReadyIndex) { finishedUploading = true; break; } ++thread.meshJobMeshUploadIndex; if (thread.meshJobMeshUploadIndex >= thread.meshJobs.Length) { thread.meshJobMeshUploadIndex = 0; } } VoxelChunk chunk = thread.meshJobs [thread.meshJobMeshUploadIndex].chunk; chunk.needsMeshRebuild = false; UploadMeshData(thread, thread.meshJobMeshUploadIndex); if (chunk.needsLightmapRebuild) { chunk.needsLightmapRebuild = false; RefreshNineChunks(chunk); } meshingIdle = false; } if (finishedUploading) { int updatedChunksCount = updatedChunks.Count; if (updatedChunksCount > 0) { // Send marked chunks to background thread generation for (int k = 0; k < updatedChunksCount; k++) { VoxelChunk chunk = updatedChunks [k]; if (chunk.inqueue) { chunk.needsMeshRebuild = true; // delay mesh update till next frame due to new chunk or nearby chunks lightmap changes or modifications } else { meshingIdle = false; chunk.transform.position = chunk.position; if (!CreateChunkMeshJob(chunk)) { // can't create more mesh jobs - remove processed requests from the update list and exit for (int j = 0; j < k; j++) { updatedChunks.RemoveAt(0); } return; } } } updatedChunks.Clear(); } } }
void UploadMeshData(MeshingThread thread, int jobIndex) { MeshJobData [] meshJobs = thread.meshJobs; VoxelChunk chunk = meshJobs [jobIndex].chunk; int diviser = 10; // Update collider? if (enableColliders && meshJobs [jobIndex].needsColliderRebuild) { int colliderVerticesCount = meshJobs [jobIndex].colliderVertices.Count; Mesh colliderMesh = chunk.mc.sharedMesh; #if UNITY_EDITOR if (!applicationIsPlaying && editorRenderDetail != EditorRenderDetail.StandardPlusColliders) { colliderVerticesCount = 0; } #endif if (colliderVerticesCount == 0) { chunk.mc.enabled = false; } else { if (colliderMesh == null) { colliderMesh = new Mesh(); } else { colliderMesh.Clear(); } //for(int l = 0; l < meshJobs[jobIndex].colliderVertices.Count; l++) //{ // meshJobs[jobIndex].colliderVertices[l] = new Vector3(meshJobs[jobIndex].colliderVertices[l].x / meshJobs[jobIndex].colliderVertices[l].y / diviser, meshJobs[jobIndex].colliderVertices[l].z / diviser); //} colliderMesh.SetVertices(meshJobs [jobIndex].colliderVertices); colliderMesh.SetTriangles(meshJobs [jobIndex].colliderIndices, 0); chunk.mc.sharedMesh = colliderMesh; chunk.mc.enabled = true; } // Update navmesh if (enableNavMesh) { int navMeshVerticesCount = meshJobs [jobIndex].navMeshVertices.Count; Mesh navMesh = chunk.navMesh; if (navMesh == null) { navMesh = new Mesh(); } else { navMesh.Clear(); } navMesh.SetVertices(meshJobs [jobIndex].navMeshVertices); navMesh.SetTriangles(meshJobs [jobIndex].navMeshIndices, 0); chunk.navMesh = navMesh; AddChunkNavMesh(chunk); } } // Update mesh? if (meshJobs [jobIndex].totalVoxels == 0) { if (chunk.mf.sharedMesh != null) { chunk.mf.sharedMesh.Clear(false); } chunk.renderState = ChunkRenderState.RenderingComplete; return; } // Create mesh Mesh mesh = chunk.mf.sharedMesh; #if !UNITY_EDITOR if (isMobilePlatform) { if (mesh != null) { DestroyImmediate(mesh); } mesh = new Mesh(); // on mobile will be released mesh data upon uploading to the GPU so the mesh is no longer readable; need to recreate it everytime the chunk is rendered } else { if (mesh == null) { mesh = new Mesh(); } else { mesh.Clear(); } } #else if (mesh == null) { mesh = new Mesh(); chunksDrawn++; } else { voxelsCreatedCount -= chunk.totalVisibleVoxelsCount; mesh.Clear(); } chunk.totalVisibleVoxelsCount = meshJobs [jobIndex].totalVoxels; voxelsCreatedCount += chunk.totalVisibleVoxelsCount; #endif // Assign materials and submeshes mesh.subMeshCount = meshJobs [jobIndex].subMeshCount; if (mesh.subMeshCount > 0) { //todo: Changing vertices.. correct this //for (int l= 0; l< meshJobs[jobIndex].vertices.Count; l++) //{ // meshJobs[jobIndex].vertices[l] = new Vector3 (meshJobs[jobIndex].vertices[l].x / diviser, meshJobs[jobIndex].vertices[l].y/ diviser, meshJobs[jobIndex].vertices[l].z/ diviser); //} // Vertices mesh.SetVertices(meshJobs [jobIndex].vertices); // UVs, normals, colors mesh.SetUVs(0, meshJobs [jobIndex].uv0); mesh.SetNormals(meshJobs [jobIndex].normals); if (enableTinting) { mesh.SetColors(meshJobs [jobIndex].colors); } int subMeshIndex = -1; int matIndex = 0; for (int k = 0; k < MAX_MATERIALS_PER_CHUNK; k++) { if (meshJobs [jobIndex].buffers [k].indicesCount > 0) { subMeshIndex++; mesh.SetTriangles(meshJobs [jobIndex].buffers [k].indices, subMeshIndex, false); matIndex += 1 << k; } } // Compute material array Material [] matArray; if (!materialsDict.TryGetValue(matIndex, out matArray)) { matArray = new Material [mesh.subMeshCount]; for (int k = 0, j = 0; k < MAX_MATERIALS_PER_CHUNK; k++) { if (meshJobs [jobIndex].buffers [k].indicesCount > 0) { matArray [j++] = renderingMaterials [k].material; } } materialsDict [matIndex] = matArray; } chunk.mr.sharedMaterials = matArray; if (chunk.isCloud) { mesh.bounds = new Bounds(Misc.vector3zero, new Vector3(CHUNK_SIZE * 4, CHUNK_SIZE * 2, CHUNK_SIZE * 4)); } else if (enableCurvature) { mesh.bounds = new Bounds(Misc.vector3zero, new Vector3(CHUNK_SIZE, CHUNK_SIZE + 32, CHUNK_SIZE)); } else { mesh.bounds = new Bounds(Misc.vector3zero, new Vector3(CHUNK_SIZE, CHUNK_SIZE, CHUNK_SIZE)); } chunk.mf.sharedMesh = mesh; #if !UNITY_EDITOR if (isMobilePlatform) { mesh.UploadMeshData(true); } #endif if (!chunk.mr.enabled) { chunk.mr.enabled = true; } } RenderModelsInVoxels(chunk, meshJobs [jobIndex].mivs); if (chunk.renderState != ChunkRenderState.RenderingComplete) { chunk.renderState = ChunkRenderState.RenderingComplete; if (OnChunkAfterFirstRender != null) { OnChunkAfterFirstRender(chunk); } } if (OnChunkRender != null) { OnChunkRender(chunk); } shouldUpdateParticlesLighting = true; }
void GenerateChunkMeshDataOneJob(MeshingThread thread) { lock (thread.indicesUpdating) { thread.meshJobMeshDataGenerationIndex++; if (thread.meshJobMeshDataGenerationIndex >= thread.meshJobs.Length) { thread.meshJobMeshDataGenerationIndex = 0; } } VoxelChunk chunk = thread.meshJobs [thread.meshJobMeshDataGenerationIndex].chunk; Voxel [] [] chunk9 = thread.chunk9; chunk9 [13] = chunk.voxels; Voxel [] emptyChunk = chunk.isAboveSurface ? emptyChunkAboveTerrain : emptyChunkUnderground; int chunkX, chunkY, chunkZ; FastMath.FloorToInt(chunk.position.x / CHUNK_SIZE, chunk.position.y / CHUNK_SIZE, chunk.position.z / CHUNK_SIZE, out chunkX, out chunkY, out chunkZ); // Reset bit field; inconclusive neighbours are those neighbours which are undefined when an adjacent chunk is rendered. We mark it so then it's finally rendered, we re-render the adjacent chunk. This is required if the new chunk can // break holes on the edge of the chunk while no lighting is entering the chunk or global illumination is disabled (yes, it's an edge case but must be addressed to avoid gaps in those cases). chunk.inconclusiveNeighbours = 0; //(byte)(chunk.inconclusiveNeighbours & ~CHUNK_IS_INCONCLUSIVE); VoxelChunk [] neighbourChunks = thread.neighbourChunks; neighbourChunks [13] = chunk; thread.allowAO = enableSmoothLighting && !draftModeActive; // AO is disabled on edges of world to reduce vertex count for (int c = 0, y = -1; y <= 1; y++) { int yy = chunkY + y; for (int z = -1; z <= 1; z++) { int zz = chunkZ + z; for (int x = -1; x <= 1; x++, c++) { if (y == 0 && z == 0 && x == 0) { continue; } int xx = chunkX + x; VoxelChunk neighbour; if (GetChunkFast(xx, yy, zz, out neighbour, false) && (neighbour.isPopulated || neighbour.isRendered)) { chunk9 [c] = neighbour.voxels; } else { chunk.inconclusiveNeighbours |= inconclusiveNeighbourTable [c]; chunk9 [c] = emptyChunk; if (y == 0 && !chunk.modified) { // if this chunk has no neighbours horizontally and is not modified, it's probably an edge chunk. // in this case, disable AO so the entire edge wall can be rendered using a single quad thanks to greedy meshing thread.allowAO = false; } } neighbourChunks [c] = neighbour; } } } #if USES_SEE_THROUGH lock (seeThroughLock) { // Hide voxels marked as hidden for (int c = 0; c < neighbourChunks.Length; c++) { ToggleHiddenVoxels(neighbourChunks [c], false); } #endif thread.GenerateMeshData(); #if USES_SEE_THROUGH // Reactivate hidden voxels for (int c = 0; c < neighbourChunks.Length; c++) { ToggleHiddenVoxels(neighbourChunks [c], true); } } #endif }