From 1de0f2cd438c1a32cf70630819e0a171c96434d1 Mon Sep 17 00:00:00 2001 From: Cassunshine Date: Thu, 30 Nov 2023 15:25:44 -0500 Subject: [PATCH] -Add rolling array to chunk render slots --- Client/Rendering/World/ChunkRenderSlot.cs | 85 ++++++++++++-------- Client/Rendering/World/ChunkRenderer.cs | 96 +++++++++++++++-------- Client/VoxelClient.cs | 2 +- Common/Util/MathHelper.cs | 15 +++- Common/World/LoadedChunkSection.cs | 31 +++++--- 5 files changed, 149 insertions(+), 80 deletions(-) diff --git a/Client/Rendering/World/ChunkRenderSlot.cs b/Client/Rendering/World/ChunkRenderSlot.cs index 6ce6ce1..b71c4b7 100644 --- a/Client/Rendering/World/ChunkRenderSlot.cs +++ b/Client/Rendering/World/ChunkRenderSlot.cs @@ -13,70 +13,81 @@ namespace Voxel.Client.Rendering.World; /// /// Holds the mesh data for a single chunk, its render state, etc, etc. /// -public class ChunkRenderSlot : Renderer { - - public readonly ivec3 RelativePosition; +public class ChunkRenderSlot : Renderer +{ private readonly object MeshLock = new(); - + public uint? lastVersion; public Chunk? targetChunk { get; private set; } - - private ivec3 realPosition; + + public ivec3 RealPosition { get; private set; } private ChunkMesh? mesh; - public ChunkRenderSlot(VoxelClient client, ivec3 relativePosition) : base(client) { - RelativePosition = relativePosition; + public ChunkRenderSlot(VoxelClient client) : base(client) + { } - public override void Render(double delta) { - + public override void Render(double delta) + { //Do nothing if this chunk render slot doesn't have a chunk yet, or if the chunk it does have is empty. if (targetChunk == null || targetChunk.IsEmpty) return; //Console.Out.Write(lastVersion); - + if (lastVersion != targetChunk.GetVersion()) Rebuild(); //Store this to prevent race conditions between == null and .render - lock (MeshLock) { + lock (MeshLock) + { if (mesh == null) return; mesh.Render(); } } - public void Move(ivec3 newCenterPos, LoadedChunkSection chunks) { - realPosition = newCenterPos + RelativePosition; + public void Move(ivec3 absolutePos, LoadedChunkSection chunks) + { + if (RealPosition == absolutePos) + return; + RealPosition = absolutePos; //Should never be null bc this only has 1 callsite that already null checks it - targetChunk = chunks.GetChunkRelative(RelativePosition); + targetChunk = chunks.GetChunkAbsolute(RealPosition); lastVersion = null; + + if (targetChunk == null || targetChunk.ChunkPosition != absolutePos) + throw new InvalidOperationException(""); } - private void Rebuild() { - if (!ChunkMeshBuilder.Rebuild(this, realPosition)) + private void Rebuild() + { + if (!ChunkMeshBuilder.Rebuild(this, RealPosition)) return; //Console.Out.WriteLine("Rebuild"); - + lastVersion = targetChunk!.GetVersion(); } - public void SetMesh(ChunkMesh mesh) { - lock (MeshLock) { + public void SetMesh(ChunkMesh mesh) + { + lock (MeshLock) + { this.mesh?.Dispose(); //Dispose of old, if it exists. this.mesh = mesh; //Slot in new. } } - public override void Dispose() { + public override void Dispose() + { mesh?.Dispose(); } - public class ChunkMesh : IDisposable { + public class ChunkMesh : IDisposable + { public readonly VoxelClient Client; public readonly RenderSystem RenderSystem; @@ -88,11 +99,13 @@ public class ChunkMesh : IDisposable { private readonly TypedDeviceBuffer UniformBuffer; private readonly ResourceSet UniformResourceSet; - public ChunkMesh(VoxelClient client, Span packedVertices, uint indexCount, ivec3 position) { + public ChunkMesh(VoxelClient client, Span packedVertices, uint indexCount, ivec3 position) + { Client = client; RenderSystem = Client.RenderSystem; - Buffer = RenderSystem.ResourceFactory.CreateBuffer(new() { + Buffer = RenderSystem.ResourceFactory.CreateBuffer(new() + { SizeInBytes = (uint)Marshal.SizeOf() * (uint)packedVertices.Length, Usage = BufferUsage.VertexBuffer }); @@ -102,24 +115,29 @@ public ChunkMesh(VoxelClient client, Span packedVertices, ui Position = position; WorldPosition = position.ChunkToWorldPosition(); - UniformBuffer = new(new() { + UniformBuffer = new(new() + { Usage = BufferUsage.Dynamic | BufferUsage.UniformBuffer }, RenderSystem); - UniformResourceSet = RenderSystem.ResourceFactory.CreateResourceSet(new() { + UniformResourceSet = RenderSystem.ResourceFactory.CreateResourceSet(new() + { Layout = Client.GameRenderer.WorldRenderer.ChunkRenderer.ChunkResourceLayout, - BoundResources = new BindableResource[] { + BoundResources = new BindableResource[] + { UniformBuffer.BackingBuffer } }); } - public void Render() { + public void Render() + { //Just in case... if (Buffer == null) return; //Set up chunk transform relative to camera. - UniformBuffer.SetValue(new ChunkMeshUniform { + UniformBuffer.SetValue(new ChunkMeshUniform + { modelMatrix = mat4.Translate((vec3)(WorldPosition - CameraStateManager.currentCameraPosition)).Transposed }); RenderSystem.MainCommandList.SetGraphicsResourceSet(2, UniformResourceSet); @@ -128,16 +146,19 @@ public void Render() { RenderSystem.MainCommandList.DrawIndexed(IndexCount); } - public void Dispose() { + public void Dispose() + { RenderSystem.GraphicsDevice.DisposeWhenIdle(Buffer); } - private struct ChunkMeshUniform { + private struct ChunkMeshUniform + { public mat4 modelMatrix = mat4.Identity.Transposed; - public ChunkMeshUniform() { + public ChunkMeshUniform() + { } } } diff --git a/Client/Rendering/World/ChunkRenderer.cs b/Client/Rendering/World/ChunkRenderer.cs index 0421030..e3fd20f 100644 --- a/Client/Rendering/World/ChunkRenderer.cs +++ b/Client/Rendering/World/ChunkRenderer.cs @@ -9,8 +9,8 @@ namespace Voxel.Client.Rendering.World; -public class ChunkRenderer : Renderer { - +public class ChunkRenderer : Renderer +{ public readonly Pipeline ChunkPipeline; public readonly ResourceLayout ChunkResourceLayout; @@ -24,15 +24,18 @@ public class ChunkRenderer : Renderer { private ivec3 renderPosition = ivec3.Zero; - private ChunkRenderSlot? this[int x, int y, int z] { - get { + private ChunkRenderSlot? this[int x, int y, int z] + { + get + { if (renderSlots == null) return null; var index = z + y * realRenderDistance + x * realRenderDistance * realRenderDistance; return renderSlots[index]; } - set { + set + { if (renderSlots == null) return; var index = z + y * realRenderDistance + x * realRenderDistance * realRenderDistance; @@ -41,15 +44,17 @@ public class ChunkRenderer : Renderer { } } - private ChunkRenderSlot? this[ivec3 pos] { + private ChunkRenderSlot? this[ivec3 pos] + { get => this[pos.x, pos.y, pos.z]; set => this[pos.x, pos.y, pos.z] = value; } - public ChunkRenderer(VoxelClient client) : base(client) { + public ChunkRenderer(VoxelClient client) : base(client) + { chunks = new(client.world!, renderPosition, ClientConfig.General.renderDistance, ClientConfig.General.renderDistance); SetRenderDistance(ClientConfig.General.renderDistance); - + TerrainAtlas = new("main", client.RenderSystem); AtlasLoader.LoadAtlas(RenderSystem.Game.AssetReader, TerrainAtlas, RenderSystem); BlockModelManager.Init(RenderSystem.Game.AssetReader, TerrainAtlas); @@ -62,37 +67,44 @@ public ChunkRenderer(VoxelClient client) : base(client) { if (!client.RenderSystem.ShaderManager.GetShaders("shaders/simple", out var shaders)) throw new("Shaders not present."); - ChunkPipeline = ResourceFactory.CreateGraphicsPipeline(new() { + ChunkPipeline = ResourceFactory.CreateGraphicsPipeline(new() + { BlendState = BlendStateDescription.SingleOverrideBlend, - DepthStencilState = new() { + DepthStencilState = new() + { DepthComparison = ComparisonKind.LessEqual, DepthTestEnabled = true, DepthWriteEnabled = true, }, Outputs = RenderSystem.GraphicsDevice.SwapchainFramebuffer.OutputDescription, PrimitiveTopology = PrimitiveTopology.TriangleList, - RasterizerState = new() { + RasterizerState = new() + { CullMode = FaceCullMode.Back, DepthClipEnabled = true, FillMode = PolygonFillMode.Solid, FrontFace = FrontFace.CounterClockwise, ScissorTestEnabled = false }, - ResourceLayouts = new[] { + ResourceLayouts = new[] + { Client.GameRenderer.CameraStateManager.CameraResourceLayout, RenderSystem.TextureManager.TextureResourceLayout, ChunkResourceLayout }, - ShaderSet = new() { - VertexLayouts = new[] { + ShaderSet = new() + { + VertexLayouts = new[] + { BasicVertex.Packed.Layout }, Shaders = shaders } }); } - - public void Reload() { + + public void Reload() + { if (renderSlots == null) return; @@ -100,7 +112,8 @@ public void Reload() { slot.lastVersion = null; } - public override void Render(double delta) { + public override void Render(double delta) + { if (renderSlots == null) return; @@ -112,11 +125,12 @@ public override void Render(double delta) { CommandList.SetIndexBuffer(RenderSystem.CommonIndexBuffer, IndexFormat.UInt32); foreach (var slot in renderSlots) slot.Render(delta); - + //Console.Out.WriteLine(); } - public void SetRenderDistance(int distance) { + public void SetRenderDistance(int distance) + { chunks.Resize(distance, distance); if (renderSlots != null) foreach (var slot in renderSlots) @@ -129,35 +143,51 @@ public void SetRenderDistance(int distance) { for (int x = 0; x < realRenderDistance; x++) for (int y = 0; y < realRenderDistance; y++) - for (int z = 0; z < realRenderDistance; z++) { - var slot = new ChunkRenderSlot(Client, new ivec3(x, y, z) - distance); - slot.Move(renderPosition, chunks); - - this[x, y, z] = slot; + for (int z = 0; z < realRenderDistance; z++) + { + var absolutePos = (new ivec3(x, y, z) - renderDistance) + renderPosition; + var slot = new ChunkRenderSlot(Client); + renderSlots[GetLoopedArrayIndex(absolutePos)] = slot; + slot.Move(absolutePos, chunks); } //Sort by distance so that closer chunks are rebuilt first. - Array.Sort(renderSlots, (a, b) => a.RelativePosition.LengthSqr.CompareTo(b.RelativePosition.LengthSqr)); + Array.Sort(renderSlots, (a, b) => (a.RealPosition - renderPosition).LengthSqr.CompareTo((b.RealPosition - renderPosition).LengthSqr)); } - public void SetRenderPosition(dvec3 worldPosition) { + public void SetRenderPosition(dvec3 worldPosition) + { var newPos = worldPosition.WorldToChunkPosition(); if (newPos == renderPosition || renderSlots == null) return; - + renderPosition = newPos; - + chunks.Move(newPos); - foreach (var slot in renderSlots) - slot.Move(renderPosition, chunks); - + for (int x = 0; x < realRenderDistance; x++) + for (int y = 0; y < realRenderDistance; y++) + for (int z = 0; z < realRenderDistance; z++) + { + var absolutePos = (new ivec3(x, y, z) - renderDistance) + renderPosition; + var slot = renderSlots[GetLoopedArrayIndex(absolutePos)]; + slot.Move(absolutePos, chunks); + } + //Sort by distance so that closer chunks are rebuilt first. - Array.Sort(renderSlots, (a, b) => a.RelativePosition.LengthSqr.CompareTo(b.RelativePosition.LengthSqr)); + Array.Sort(renderSlots, (a, b) => (a.RealPosition - renderPosition).LengthSqr.CompareTo((b.RealPosition - renderPosition).LengthSqr)); + } + + + private int GetLoopedArrayIndex(ivec3 pos) + { + pos = new ivec3(MathHelper.Repeat(pos.x, realRenderDistance), MathHelper.Repeat(pos.y, realRenderDistance), MathHelper.Repeat(pos.z, realRenderDistance)); + return pos.z + pos.y * realRenderDistance + pos.x * realRenderDistance * realRenderDistance; } - public override void Dispose() { + public override void Dispose() + { if (renderSlots == null) return; diff --git a/Client/VoxelClient.cs b/Client/VoxelClient.cs index 832723a..25aac7c 100644 --- a/Client/VoxelClient.cs +++ b/Client/VoxelClient.cs @@ -97,7 +97,7 @@ public override void OnTick() { camera.MoveAndSlide(world!, inputDir); - // GameRenderer.WorldRenderer.ChunkRenderer.SetRenderPosition(camera.position); + GameRenderer.WorldRenderer.ChunkRenderer.SetRenderPosition(camera.position); } public override void OnWindowResize() { diff --git a/Common/Util/MathHelper.cs b/Common/Util/MathHelper.cs index ee143ac..7c200b7 100644 --- a/Common/Util/MathHelper.cs +++ b/Common/Util/MathHelper.cs @@ -1,12 +1,23 @@ using System.Runtime.CompilerServices; -namespace Voxel.Common.Util; +namespace Voxel.Common.Util; -public static class MathHelper { +public static class MathHelper +{ [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static float LerpF(float from, float to, float amount) => from + (to - from) * amount; + [MethodImpl(MethodImplOptions.AggressiveInlining | MethodImplOptions.AggressiveOptimization)] public static double LerpD(double from, double to, double amount) => from + (from - to) * amount; + + public static float Clamp(float t, float min, float max) => + MathF.Max(MathF.Min(t, max), min); + + public static float Repeat(float t, float length) => + Clamp(t - MathF.Floor(t / length) * length, 0.0f, length); + + public static int Repeat(int t, int length) => + (int)MathF.Floor(Clamp(t - MathF.Floor((float)t / length) * length, 0.0f, length)); } diff --git a/Common/World/LoadedChunkSection.cs b/Common/World/LoadedChunkSection.cs index d12107b..3848f3d 100644 --- a/Common/World/LoadedChunkSection.cs +++ b/Common/World/LoadedChunkSection.cs @@ -2,21 +2,23 @@ using Voxel.Common.Util; using Voxel.Common.World.Views; -namespace Voxel.Common.World; +namespace Voxel.Common.World; -public class LoadedChunkSection { +public class LoadedChunkSection +{ private readonly VoxelWorld World; - + public ivec3 centerPos { get; private set; } public int halfWidth { get; private set; } public int halfHeight { get; private set; } private ivec3 min => new(-halfWidth, -halfHeight, -halfWidth); private ivec3 max => new(halfWidth + 1, halfHeight + 1, halfWidth + 1); - + private Dictionary views = new(); - - public LoadedChunkSection(VoxelWorld world, ivec3 centerPos, int halfWidth, int halfHeight) { + + public LoadedChunkSection(VoxelWorld world, ivec3 centerPos, int halfWidth, int halfHeight) + { World = world; this.centerPos = centerPos; this.halfWidth = halfWidth; @@ -26,18 +28,20 @@ public LoadedChunkSection(VoxelWorld world, ivec3 centerPos, int halfWidth, int public Chunk? GetChunkRelative(ivec3 relativePos) => views.TryGetValue(relativePos, out var view) ? view.Chunk : null; - + public Chunk? GetChunkAbsolute(ivec3 absolutePos) - => GetChunkRelative(centerPos - absolutePos); + => GetChunkRelative(absolutePos - centerPos); - public void Move(ivec3 centerPos) { + public void Move(ivec3 centerPos) + { if (this.centerPos == centerPos) return; this.centerPos = centerPos; Update(); } - public void Resize(int halfWidth, int halfHeight) { + public void Resize(int halfWidth, int halfHeight) + { if (this.halfWidth == halfWidth && this.halfHeight == halfHeight) return; this.halfWidth = halfWidth; @@ -45,11 +49,14 @@ public void Resize(int halfWidth, int halfHeight) { Update(); } - private void Update() { + private void Update() + { var map = new Dictionary(); - foreach (var pos in Iteration.Cubic(min, max)) { + foreach (var pos in Iteration.Cubic(min, max)) + { map[pos] = World.GetOrCreateChunkView(centerPos + pos); } + views = map; } }