From 4ca45936f7cd8fb58dbd12f9500b6abe20ab362b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 19 Aug 2022 10:25:19 -0700 Subject: [PATCH] Only reallocate the GPU buffer if our CPU capacity changes Previously, every single render was re-allocating a fairly large (~1MB) buffer on the GPU. The recommended best practice is to allocate once and then use `glBufferSubData` to update the memory in-place on the GPU. We now track the last size we sent to the GPU, compare it to our copy on the CPU, and if it _grows_ then we reallocate the GPU buffer. If it shrinks we leave the GPU as-is for now. After this, we use the sub-data routines to update the data in place. --- src/Grid.zig | 27 +++++++++++++++++++++++++-- src/opengl/Buffer.zig | 10 ++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/Grid.zig b/src/Grid.zig index 3f57c8d15..62836bc4f 100644 --- a/src/Grid.zig +++ b/src/Grid.zig @@ -27,6 +27,11 @@ cell_size: CellSize, /// The current set of cells to render. cells: std.ArrayListUnmanaged(GPUCell), +/// The size of the cells list that was sent to the GPU. This is used +/// to detect when the cells array was reallocated/resized and handle that +/// accordingly. +gl_cells_size: usize = 0, + /// Shader program for cell rendering. program: gl.Program, vao: gl.VertexArray, @@ -497,7 +502,7 @@ pub fn flushAtlas(self: *Grid) !void { self.atlas_dirty = false; } -pub fn render(self: Grid) !void { +pub fn render(self: *Grid) !void { const t = trace(@src()); defer t.end(); @@ -518,7 +523,25 @@ pub fn render(self: Grid) !void { // Bind VBO and set data var binding = try self.vbo.bind(.ArrayBuffer); defer binding.unbind(); - try binding.setData(self.cells.items, .StaticDraw); + + // Our allocated buffer on the GPU is smaller than our capacity. + // We reallocate a new buffer with the full new capacity. + if (self.gl_cells_size < self.cells.capacity) { + log.info("reallocating GPU buffer old={} new={}", .{ + self.gl_cells_size, + self.cells.capacity, + }); + + try binding.setDataNullManual( + @sizeOf(GPUCell) * self.cells.capacity, + .StaticDraw, + ); + self.gl_cells_size = self.cells.capacity; + } + + // We always set the data using subdata if possible to avoid reallocation + // on the GPU. + try binding.setSubData(0, self.cells.items); // Bind our texture try gl.Texture.active(gl.c.GL_TEXTURE0); diff --git a/src/opengl/Buffer.zig b/src/opengl/Buffer.zig index 3c29b91d2..60d3380e9 100644 --- a/src/opengl/Buffer.zig +++ b/src/opengl/Buffer.zig @@ -69,6 +69,16 @@ pub const Binding = struct { try errors.getError(); } + /// Same as setDataNull but lets you manually specify the buffer size. + pub inline fn setDataNullManual( + b: Binding, + size: usize, + usage: Usage, + ) !void { + c.glBufferData(@enumToInt(b.target), @intCast(c_long, size), null, @enumToInt(usage)); + try errors.getError(); + } + fn dataInfo(data: anytype) struct { size: isize, ptr: *const anyopaque,