diff --git a/assets/cubyz/shaders/chunks/chunk_vertex.vs b/assets/cubyz/shaders/chunks/chunk_vertex.vs index 5fa8abca..756fd719 100644 --- a/assets/cubyz/shaders/chunks/chunk_vertex.vs +++ b/assets/cubyz/shaders/chunks/chunk_vertex.vs @@ -28,7 +28,6 @@ struct FaceData { }; layout(std430, binding = 3) buffer _faceData { - int voxelSize; FaceData faceData[]; }; @@ -46,6 +45,7 @@ layout(std430, binding = 4) buffer _voxelModels }; uniform int time; +uniform int voxelSize; const vec3[6] normals = vec3[6]( vec3(0, 1, 0), diff --git a/src/chunk.zig b/src/chunk.zig index f5ef59e2..6d1b687e 100644 --- a/src/chunk.zig +++ b/src/chunk.zig @@ -393,10 +393,12 @@ pub const meshing = struct { @"waterFog.density": c_int, time: c_int, visibilityMask: c_int, + voxelSize: c_int, } = undefined; var vao: c_uint = undefined; var vbo: c_uint = undefined; var faces: std.ArrayList(u32) = undefined; + var faceData: graphics.LargeBuffer = undefined; pub fn init() !void { shader = try Shader.create("assets/cubyz/shaders/chunks/chunk_vertex.vs", "assets/cubyz/shaders/chunks/chunk_fragment.fs"); @@ -417,6 +419,7 @@ pub const meshing = struct { c.glBindVertexArray(0); faces = try std.ArrayList(u32).initCapacity(std.heap.page_allocator, 65536); + try faceData.init(main.globalAllocator, 64 << 20, 3); } pub fn deinit() void { @@ -424,6 +427,7 @@ pub const meshing = struct { c.glDeleteVertexArrays(1, &vao); c.glDeleteBuffers(1, &vbo); faces.deinit(); + faceData.deinit(); } pub fn bindShaderAndUniforms(projMatrix: Mat4f, ambient: Vec3f, time: u32) void { @@ -452,7 +456,7 @@ pub const meshing = struct { size: ChunkCoordinate, chunk: std.atomic.Atomic(?*Chunk), faces: std.ArrayList(u32), - faceData: SSBO, + bufferAllocation: graphics.LargeBuffer.Allocation = .{.start = 0, .len = 0}, coreCount: u31 = 0, neighborStart: [7]u31 = [_]u31{0} ** 7, vertexCount: u31 = 0, @@ -466,12 +470,11 @@ pub const meshing = struct { .size = chunkSize*pos.voxelSize, .faces = std.ArrayList(u32).init(allocator), .chunk = std.atomic.Atomic(?*Chunk).init(null), - .faceData = SSBO.init(), }; } pub fn deinit(self: *ChunkMesh) void { - self.faceData.deinit(); + faceData.free(self.bufferAllocation) catch unreachable; self.faces.deinit(); if(self.chunk.load(.Monotonic)) |ch| { renderer.RenderStructure.allocator.destroy(ch); @@ -508,13 +511,13 @@ pub const meshing = struct { or other.typ == 0 or false // TODO: Blocks.mode(other).checkTransparency(other, neighbor) // TODO: make blocks.meshes.modelIndices(other) != 0 more strict to avoid overdraw. or (!std.meta.eql(block, other) and other.viewThrough()) - or blocks.meshes.modelIndices(other) != 0); + or blocks.meshes.modelIndices(other) != 0 + ); } pub fn regenerateMainMesh(self: *ChunkMesh, chunk: *Chunk) !void { std.debug.assert(!self.mutex.tryLock()); // The mutex should be locked when calling this function. self.faces.clearRetainingCapacity(); - try self.faces.append(chunk.pos.voxelSize); var n: u32 = 0; var x: u8 = 0; while(x < chunkSize): (x += 1) { @@ -582,7 +585,7 @@ pub const meshing = struct { start.* -= 2; } } else { - searchStart = 1; + searchStart = 0; searchEnd = self.coreCount; self.coreCount -= 2; for(self.neighborStart) |*start| { @@ -606,7 +609,7 @@ pub const meshing = struct { if(fromNeighborChunk) |neighbor| { searchRange = self.faces.items[self.neighborStart[neighbor]..self.neighborStart[neighbor+1]]; } else { - searchRange = self.faces.items[1..self.coreCount]; + searchRange = self.faces.items[0..self.coreCount]; } var i: u32 = 0; while(i < searchRange.len): (i += 2) { @@ -699,15 +702,16 @@ pub const meshing = struct { } } } - if(neighborMesh != self) neighborMesh.uploadData(); + if(neighborMesh != self) try neighborMesh.uploadData(); } self.chunk.load(.Monotonic).?.blocks[getIndex(x, y, z)] = newBlock; - self.uploadData(); + try self.uploadData(); } - fn uploadData(self: *ChunkMesh) void { - self.vertexCount = @intCast(u31, 6*(self.faces.items.len-1)/2); - self.faceData.bufferData(u32, self.faces.items); + fn uploadData(self: *ChunkMesh) !void { + self.vertexCount = @intCast(u31, 6*self.faces.items.len/2); + try faceData.realloc(&self.bufferAllocation, @intCast(u31, 4*self.faces.items.len)); + faceData.bufferSubData(self.bufferAllocation.start, u32, self.faces.items); } pub fn uploadDataAndFinishNeighbors(self: *ChunkMesh) !void { @@ -772,8 +776,7 @@ pub const meshing = struct { for(neighborMesh.neighborStart[1+(neighbor ^ 1)..]) |*neighborStart| { neighborStart.* = neighborStart.* - (rangeEnd - rangeStart) + @intCast(u31, additionalNeighborFaces.items.len); } - neighborMesh.vertexCount = @intCast(u31, 6*(neighborMesh.faces.items.len-1)/2); - neighborMesh.faceData.bufferData(u32, neighborMesh.faces.items); + try neighborMesh.uploadData(); continue; } } @@ -826,7 +829,7 @@ pub const meshing = struct { } } self.neighborStart[6] = @intCast(u31, self.faces.items.len); - self.uploadData(); + try self.uploadData(); self.generated = true; } @@ -842,8 +845,8 @@ pub const meshing = struct { @floatCast(f32, @intToFloat(f64, self.pos.wz) - playerPosition[2]) ); c.glUniform1i(uniforms.visibilityMask, self.visibilityMask); - self.faceData.bind(3); - c.glDrawElements(c.GL_TRIANGLES, self.vertexCount, c.GL_UNSIGNED_INT, null); + c.glUniform1i(uniforms.voxelSize, self.pos.voxelSize); + c.glDrawElementsBaseVertex(c.GL_TRIANGLES, self.vertexCount, c.GL_UNSIGNED_INT, null, self.bufferAllocation.start/8*4); } }; }; \ No newline at end of file diff --git a/src/graphics.zig b/src/graphics.zig index 4a38ebd2..31243097 100644 --- a/src/graphics.zig +++ b/src/graphics.zig @@ -423,6 +423,122 @@ pub const SSBO = struct { c.glBufferData(c.GL_SHADER_STORAGE_BUFFER, @intCast(c_long, data.len*@sizeOf(T)), data.ptr, c.GL_STATIC_DRAW); c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, 0); } + + pub fn createDynamicBuffer(self: SSBO, size: usize) void { + c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, self.bufferID); + c.glBufferData(c.GL_SHADER_STORAGE_BUFFER, @intCast(c_long, size), null, c.GL_DYNAMIC_DRAW); + c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, 0); + } +}; + +/// A big SSBO that is able to allocate/free smaller regions. +pub const LargeBuffer = struct { + pub const Allocation = struct { + start: u31, + len: u31, + }; + ssbo: SSBO, + freeBlocks: std.ArrayList(Allocation), + + pub fn init(self: *LargeBuffer, allocator: Allocator, size: u31, binding: c_uint) !void { + self.ssbo = SSBO.init(); + self.ssbo.createDynamicBuffer(size); + self.ssbo.bind(binding); + + self.freeBlocks = std.ArrayList(Allocation).init(allocator); + try self.freeBlocks.append(.{.start = 0, .len = size}); + } + + pub fn deinit(self: *LargeBuffer) void { + self.ssbo.deinit(); + self.freeBlocks.deinit(); + } + + fn alloc(self: *LargeBuffer, size: u31) !Allocation { + var smallestBlock: ?*Allocation = null; + for(self.freeBlocks.items) |*block, i| { + if(size == block.len) { + return self.freeBlocks.swapRemove(i); + } + if(size < block.len and if(smallestBlock) |_smallestBlock| block.len > _smallestBlock.len else true) { + smallestBlock = block; + } + } + if(smallestBlock) |block| { + const result = Allocation {.start = block.start, .len = size}; + block.start += size; + block.len -= size; + return result; + } else return error.OutOfMemory; // TODO: Increase the buffer size. + } + + pub fn free(self: *LargeBuffer, _allocation: Allocation) !void { + var allocation = _allocation; + if(allocation.len == 0) return; + for(self.freeBlocks.items) |*block, i| { + if(allocation.start + allocation.len == block.start) { + allocation.len += block.len; + _ = self.freeBlocks.swapRemove(i); + break; + } + } + for(self.freeBlocks.items) |*block| { + if(allocation.start == block.start + block.len) { + block.len += allocation.len; + return; + } + } + try self.freeBlocks.append(allocation); + } + + pub fn realloc(self: *LargeBuffer, allocation: *Allocation, newSize: u31) !void { + if(allocation.len == 0) allocation.* = try self.alloc(newSize); + if(newSize == allocation.len) return; + if(newSize < allocation.len) { + const diff = allocation.len - newSize; + // Check if there is a free block directly after: + for(self.freeBlocks.items) |*block| { + if(allocation.start + allocation.len == block.start and block.len + allocation.len >= newSize) { + block.start -= diff; + block.len += diff; + allocation.len -= diff; + return; + } + } + // Create a new free block: + allocation.len -= diff; + try self.freeBlocks.append(.{.start = allocation.start + allocation.len, .len = diff}); + } else { + // Check if the buffer can be extended without a problem: + for(self.freeBlocks.items) |*block, i| { + if(allocation.start + allocation.len == block.start and block.len + allocation.len >= newSize) { + const diff = newSize - allocation.len; + allocation.len += diff; + if(block.len != diff) { + block.start += diff; + block.len -= diff; + } else { + _ = self.freeBlocks.swapRemove(i); + } + return; + } + } + const oldAllocation = allocation.*; + allocation.* = try self.alloc(newSize); + + c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, self.ssbo.bufferID); + c.glCopyBufferSubData(c.GL_SHADER_STORAGE_BUFFER, c.GL_SHADER_STORAGE_BUFFER, oldAllocation.start, allocation.start, oldAllocation.len); + c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, 0); + + try self.free(oldAllocation); + } + } + + pub fn bufferSubData(self: *LargeBuffer, offset: u31, comptime T: type, data: []T) void { + c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, self.ssbo.bufferID); + c.glBufferSubData(c.GL_SHADER_STORAGE_BUFFER, offset, @sizeOf(T)*@intCast(c_long, data.len), data.ptr); + c.glBindBuffer(c.GL_SHADER_STORAGE_BUFFER, 0); + } }; pub const FrameBuffer = struct {