From 4e92a4e503d796fbec9bc79f14a18c1617c88265 Mon Sep 17 00:00:00 2001 From: bjorn Date: Mon, 11 Jul 2022 20:52:35 -0700 Subject: [PATCH] Super ultra cool geometry cache; Pass stores a small 16-bucket cache of vertices/indices it recently generated. Draws that have relatively predictable geometry can provide a hash along with their draw. The Pass will reuse vertices based on the hash, when possible, and return a NULL vertex pointer to let the draw-er know they don't need to generate any vertices. This provides a dramatic speedup when drawing the same shape many times in a row. The overhead is negligible, with benefits kicking in with just a small handful of repeated draws (3-5 for cubes, less for more complex shapes). --- src/modules/graphics/graphics.c | 182 ++++++++++++++++++++++++-------- 1 file changed, 137 insertions(+), 45 deletions(-) diff --git a/src/modules/graphics/graphics.c b/src/modules/graphics/graphics.c index 7566462a..1dce0b11 100644 --- a/src/modules/graphics/graphics.c +++ b/src/modules/graphics/graphics.c @@ -26,6 +26,7 @@ const char** os_vk_get_instance_extensions(uint32_t* count); #define MAX_FRAME_MEMORY (1 << 30) #define MAX_SHADER_RESOURCES 32 #define MATERIALS_PER_BLOCK 1024 +#define FLOAT_BITS(f) ((union { float f; uint32_t u; }) { f }).u typedef struct { struct { float x, y, z; } position; @@ -170,6 +171,7 @@ typedef enum { } VertexFormat; typedef struct { + uint64_t hash; VertexMode mode; DefaultShader shader; Material* material; @@ -178,14 +180,11 @@ typedef struct { Buffer* buffer; VertexFormat format; uint32_t count; - const void* data; void** pointer; } vertex; struct { Buffer* buffer; uint32_t count; - uint32_t stride; - const void* data; void** pointer; } index; uint32_t start; @@ -244,6 +243,23 @@ typedef struct { bool dirty; } Pipeline; +enum { + SHAPE_PLANE, + SHAPE_BOX, + SHAPE_CIRCLE, + SHAPE_SPHERE, + SHAPE_CYLINDER, + SHAPE_CAPSULE, + SHAPE_TORUS, + SHAPE_MONKEY +}; + +typedef struct { + uint64_t hash; + gpu_buffer* vertices; + gpu_buffer* indices; +} Shape; + typedef struct { Sync* sync; Buffer* buffer; @@ -277,6 +293,7 @@ struct Pass { gpu_binding builtins[3]; gpu_buffer* vertexBuffer; gpu_buffer* indexBuffer; + Shape shapeCache[16]; arr_t(Access) access; }; @@ -2366,7 +2383,6 @@ Model* lovrModelCreate(ModelInfo* info) { draw->material = primitive->material == ~0u ? NULL: model->materials[primitive->material]; draw->vertex.buffer = model->vertexBuffer; - draw->index.stride = indexSize; if (primitive->indices) { draw->index.buffer = model->indexBuffer; @@ -2895,6 +2911,8 @@ Pass* lovrGraphicsGetPass(PassInfo* info) { pass->vertexBuffer = NULL; pass->indexBuffer = NULL; + memset(pass->shapeCache, 0, sizeof(pass->shapeCache)); + float viewport[6] = { 0.f, 0.f, (float) main->width, (float) main->height, 0.f, 1.f }; lovrPassSetViewport(pass, viewport, viewport + 4); @@ -3583,22 +3601,29 @@ static void flushMaterial(Pass* pass, Draw* draw, Shader* shader) { } static void flushBuffers(Pass* pass, Draw* draw) { + Shape* cache = NULL; + + if (draw->hash) { + cache = &pass->shapeCache[draw->hash & (COUNTOF(pass->shapeCache) - 1)]; + if (cache->hash == draw->hash) { + gpu_bind_vertex_buffers(pass->stream, &cache->vertices, NULL, 0, 1); + gpu_bind_index_buffer(pass->stream, cache->indices, 0, GPU_INDEX_U16); + *draw->vertex.pointer = NULL; + *draw->index.pointer = NULL; + return; + } + } + if (!draw->vertex.buffer && draw->vertex.count > 0) { lovrCheck(draw->vertex.count < UINT16_MAX, "This draw has too many vertices (max is 65534), try splitting it up into multiple draws or using a Buffer"); uint32_t stride = state.vertexFormats[draw->vertex.format].bufferStrides[0]; uint32_t size = draw->vertex.count * stride; gpu_buffer* scratchpad = tempAlloc(gpu_sizeof_buffer()); - void* pointer = gpu_map(scratchpad, size, stride, GPU_MAP_WRITE); - - if (draw->vertex.pointer) { - *draw->vertex.pointer = pointer; - } else { - memcpy(pointer, draw->vertex.data, size); - } + *draw->vertex.pointer = gpu_map(scratchpad, size, stride, GPU_MAP_WRITE); gpu_bind_vertex_buffers(pass->stream, &scratchpad, NULL, 0, 1); - pass->vertexBuffer = NULL; + pass->vertexBuffer = scratchpad; } else if (draw->vertex.buffer && draw->vertex.buffer->gpu != pass->vertexBuffer) { lovrCheck(draw->vertex.buffer->info.stride <= state.limits.vertexBufferStride, "Vertex buffer stride exceeds vertexBufferStride limit"); gpu_bind_vertex_buffers(pass->stream, &draw->vertex.buffer->gpu, NULL, 0, 1); @@ -3607,27 +3632,25 @@ static void flushBuffers(Pass* pass, Draw* draw) { } if (!draw->index.buffer && draw->index.count > 0) { - uint32_t stride = draw->index.stride ? draw->index.stride : sizeof(uint16_t); - uint32_t size = draw->index.count * stride; + uint32_t size = draw->index.count * sizeof(uint16_t); gpu_buffer* scratchpad = tempAlloc(gpu_sizeof_buffer()); - void* pointer = gpu_map(scratchpad, size, stride, GPU_MAP_WRITE); + *draw->index.pointer = gpu_map(scratchpad, size, sizeof(uint16_t), GPU_MAP_WRITE); - if (draw->index.pointer) { - *draw->index.pointer = pointer; - } else { - memcpy(pointer, draw->index.data, size); - } - - gpu_index_type type = stride == 4 ? GPU_INDEX_U32 : GPU_INDEX_U16; - gpu_bind_index_buffer(pass->stream, scratchpad, 0, type); - pass->indexBuffer = NULL; + gpu_bind_index_buffer(pass->stream, scratchpad, 0, GPU_INDEX_U16); + pass->indexBuffer = scratchpad; } else if (draw->index.buffer && draw->index.buffer->gpu != pass->indexBuffer) { gpu_index_type type = draw->index.buffer->info.stride == 4 ? GPU_INDEX_U32 : GPU_INDEX_U16; gpu_bind_index_buffer(pass->stream, draw->index.buffer->gpu, 0, type); pass->indexBuffer = draw->index.buffer->gpu; trackBuffer(pass, draw->index.buffer, GPU_PHASE_INPUT_INDEX, GPU_CACHE_INDEX); } + + if (cache) { + cache->hash = draw->hash; + cache->vertices = pass->vertexBuffer; + cache->indices = pass->indexBuffer; + } } static void lovrPassDraw(Pass* pass, Draw* draw) { @@ -3683,6 +3706,7 @@ void lovrPassLine(Pass* pass, uint32_t count, float** points) { } void lovrPassPlane(Pass* pass, float* transform, DrawStyle style, uint32_t cols, uint32_t rows) { + uint32_t key[] = { SHAPE_PLANE, style, cols, rows }; ShapeVertex* vertices; uint16_t* indices; @@ -3693,17 +3717,19 @@ void lovrPassPlane(Pass* pass, float* transform, DrawStyle style, uint32_t cols, indexCount = 2 * (rows + 1) + 2 * (cols + 1); lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_LINES, .transform = transform, .vertex.pointer = (void**) &vertices, .vertex.count = vertexCount, .index.pointer = (void**) &indices, - .index.count = indexCount + .index.count = indexCount, }); } else { indexCount = (cols * rows) * 6; lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_TRIANGLES, .transform = transform, .vertex.pointer = (void**) &vertices, @@ -3713,6 +3739,10 @@ void lovrPassPlane(Pass* pass, float* transform, DrawStyle style, uint32_t cols, }); } + if (!vertices) { + return; + } + for (uint32_t y = 0; y <= rows; y++) { float v = y * (1.f / rows); for (uint32_t x = 0; x <= cols; x++) { @@ -3757,8 +3787,12 @@ void lovrPassPlane(Pass* pass, float* transform, DrawStyle style, uint32_t cols, } void lovrPassBox(Pass* pass, float* transform, DrawStyle style) { + uint32_t key[] = { SHAPE_BOX, style }; + ShapeVertex* vertices; + uint16_t* indices; + if (style == STYLE_LINE) { - static ShapeVertex vertices[] = { + static ShapeVertex vertexData[] = { { { -.5f, .5f, -.5f }, { 0.f, 0.f, 0.f }, { 0.f, 0.f } }, // Front { { .5f, .5f, -.5f }, { 0.f, 0.f, 0.f }, { 0.f, 0.f } }, { { .5f, -.5f, -.5f }, { 0.f, 0.f, 0.f }, { 0.f, 0.f } }, @@ -3769,22 +3803,28 @@ void lovrPassBox(Pass* pass, float* transform, DrawStyle style) { { { -.5f, -.5f, .5f }, { 0.f, 0.f, 0.f }, { 0.f, 0.f } } }; - static uint16_t indices[] = { + static uint16_t indexData[] = { 0, 1, 1, 2, 2, 3, 3, 0, // Front 4, 5, 5, 6, 6, 7, 7, 4, // Back 0, 4, 1, 5, 2, 6, 3, 7 // Connections }; lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_LINES, .transform = transform, - .vertex.data = vertices, - .vertex.count = COUNTOF(vertices), - .index.data = indices, - .index.count = COUNTOF(indices) + .vertex.pointer = (void**) &vertices, + .vertex.count = COUNTOF(vertexData), + .index.pointer = (void**) &indices, + .index.count = COUNTOF(indexData) }); + + if (vertices) { + memcpy(vertices, vertexData, sizeof(vertexData)); + memcpy(indices, indexData, sizeof(indexData)); + } } else { - ShapeVertex vertices[] = { + static ShapeVertex vertexData[] = { { { -.5f, -.5f, -.5f }, { 0.f, 0.f, -1.f }, { 0.f, 0.f } }, // Front { { -.5f, .5f, -.5f }, { 0.f, 0.f, -1.f }, { 0.f, 1.f } }, { { .5f, -.5f, -.5f }, { 0.f, 0.f, -1.f }, { 1.f, 0.f } }, @@ -3811,7 +3851,7 @@ void lovrPassBox(Pass* pass, float* transform, DrawStyle style) { { { .5f, .5f, .5f }, { 0.f, 1.f, 0.f }, { 1.f, 0.f } } }; - uint16_t indices[] = { + static uint16_t indexData[] = { 0, 1, 2, 2, 1, 3, 4, 5, 6, 6, 5, 7, 8, 9, 10, 10, 9, 11, @@ -3821,13 +3861,19 @@ void lovrPassBox(Pass* pass, float* transform, DrawStyle style) { }; lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_TRIANGLES, .transform = transform, - .vertex.data = vertices, - .vertex.count = COUNTOF(vertices), - .index.data = indices, - .index.count = COUNTOF(indices) + .vertex.pointer = (void**) &vertices, + .vertex.count = COUNTOF(vertexData), + .index.pointer = (void**) &indices, + .index.count = COUNTOF(indexData) }); + + if (vertices) { + memcpy(vertices, vertexData, sizeof(vertexData)); + memcpy(indices, indexData, sizeof(indexData)); + } } } @@ -3837,6 +3883,7 @@ void lovrPassCircle(Pass* pass, float* transform, DrawStyle style, float angle1, angle2 = 2.f * (float) M_PI; } + uint32_t key[] = { SHAPE_CIRCLE, style, FLOAT_BITS(angle1), FLOAT_BITS(angle2), segments }; ShapeVertex* vertices; uint16_t* indices; @@ -3845,6 +3892,7 @@ void lovrPassCircle(Pass* pass, float* transform, DrawStyle style, float angle1, uint32_t indexCount = segments * 2; lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_LINES, .transform = transform, .vertex.pointer = (void**) &vertices, @@ -3852,11 +3900,16 @@ void lovrPassCircle(Pass* pass, float* transform, DrawStyle style, float angle1, .index.pointer = (void**) &indices, .index.count = indexCount }); + + if (!vertices) { + return; + } } else { uint32_t vertexCount = segments + 2; uint32_t indexCount = segments * 3; lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_TRIANGLES, .transform = transform, .vertex.pointer = (void**) &vertices, @@ -3865,6 +3918,10 @@ void lovrPassCircle(Pass* pass, float* transform, DrawStyle style, float angle1, .index.count = indexCount }); + if (!vertices) { + return; + } + // Center *vertices++ = (ShapeVertex) { { 0.f, 0.f, 0.f }, { 0.f, 0.f, 1.f }, { .5f, .5f } }; } @@ -3898,15 +3955,22 @@ void lovrPassSphere(Pass* pass, float* transform, uint32_t segmentsH, uint32_t s ShapeVertex* vertices; uint16_t* indices; + uint32_t key[] = { SHAPE_SPHERE, segmentsH, segmentsV }; + lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_TRIANGLES, .transform = transform, .vertex.pointer = (void**) &vertices, .vertex.count = vertexCount, .index.pointer = (void**) &indices, - .index.count = indexCount + .index.count = indexCount, }); + if (!vertices) { + return; + } + // Top *vertices++ = (ShapeVertex) { { 0.f, 1.f, 0.f }, { 0.f, 1.f, 0.f }, { .5f, 0.f } }; @@ -3965,6 +4029,8 @@ void lovrPassCylinder(Pass* pass, float* transform, bool capped, float angle1, f angle2 = 2.f * (float) M_PI; } + uint32_t key[] = { SHAPE_CYLINDER, capped, FLOAT_BITS(angle1), FLOAT_BITS(angle2), segments }; + uint32_t vertexCount = 2 * (segments + 1); uint32_t indexCount = 6 * segments; ShapeVertex* vertices; @@ -3977,6 +4043,7 @@ void lovrPassCylinder(Pass* pass, float* transform, bool capped, float angle1, f } lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_TRIANGLES, .transform = transform, .vertex.pointer = (void**) &vertices, @@ -3985,6 +4052,10 @@ void lovrPassCylinder(Pass* pass, float* transform, bool capped, float angle1, f .index.count = indexCount }); + if (!vertices) { + return; + } + float angleShift = (angle2 - angle1) / segments; // Tube @@ -4046,6 +4117,8 @@ void lovrPassCapsule(Pass* pass, float* transform, uint32_t segments) { float radius = sx; float length = sz * .5f; + uint32_t key[] = { SHAPE_CAPSULE, FLOAT_BITS(radius), FLOAT_BITS(length), segments }; + uint32_t rings = segments / 2; uint32_t vertexCount = 2 * (1 + rings * (segments + 1)); uint32_t indexCount = 2 * (3 * segments + 6 * segments * (rings - 1)) + 6 * segments; @@ -4053,14 +4126,19 @@ void lovrPassCapsule(Pass* pass, float* transform, uint32_t segments) { uint16_t* indices; lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_TRIANGLES, .transform = transform, .vertex.pointer = (void**) &vertices, .vertex.count = vertexCount, .index.pointer = (void**) &indices, - .index.count = indexCount + .index.count = indexCount, }); + if (!vertices) { + return; + } + float tip = length + radius; uint32_t h = vertexCount / 2; vertices[0] = (ShapeVertex) { { 0.f, 0.f, -tip }, { 0.f, 0.f, -1.f }, { .5f, 0.f } }; @@ -4133,12 +4211,14 @@ void lovrPassTorus(Pass* pass, float* transform, uint32_t segmentsT, uint32_t se float radius = sx * .5f; float thickness = sz * .5f; + uint32_t key[] = { SHAPE_TORUS, FLOAT_BITS(radius), FLOAT_BITS(thickness), segmentsT, segmentsP }; uint32_t vertexCount = segmentsT * segmentsP; uint32_t indexCount = segmentsT * segmentsP * 6; ShapeVertex* vertices; uint16_t* indices; lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_TRIANGLES, .transform = transform, .vertex.pointer = (void**) &vertices, @@ -4311,6 +4391,7 @@ void lovrPassText(Pass* pass, Font* font, ColoredString* strings, uint32_t count mat4_scale(transform, scale, scale, scale); mat4_translate(transform, 0.f, -ascent + valign / 2.f * (leading * lineCount), 0.f); + GlyphVertex* vertexPointer; uint16_t* indices; lovrPassDraw(pass, &(Draw) { .mode = VERTEX_TRIANGLES, @@ -4318,12 +4399,14 @@ void lovrPassText(Pass* pass, Font* font, ColoredString* strings, uint32_t count .material = font->material, .transform = transform, .vertex.format = VERTEX_GLYPH, + .vertex.pointer = (void**) &vertexPointer, .vertex.count = glyphCount * 4, - .vertex.data = vertices, - .index.count = glyphCount * 6, - .index.pointer = (void**) &indices + .index.pointer = (void**) &indices, + .index.count = glyphCount * 6 }); + memcpy(vertexPointer, vertices, glyphCount * 4 * sizeof(GlyphVertex)); + for (uint32_t i = 0; i < glyphCount * 4; i += 4) { uint16_t quad[] = { i + 0, i + 2, i + 1, i + 1, i + 2, i + 3 }; memcpy(indices, quad, sizeof(quad)); @@ -4364,18 +4447,25 @@ void lovrPassFill(Pass* pass, Texture* texture) { } void lovrPassMonkey(Pass* pass, float* transform) { + uint32_t key[] = { SHAPE_MONKEY }; uint32_t vertexCount = COUNTOF(monkey_vertices) / 6; - ShapeVertex* vertices; + uint16_t* indices; + lovrPassDraw(pass, &(Draw) { + .hash = hash64(key, sizeof(key)), .mode = VERTEX_TRIANGLES, - .vertex.count = vertexCount, .vertex.pointer = (void**) &vertices, + .vertex.count = vertexCount, + .index.pointer = (void**) &indices, .index.count = COUNTOF(monkey_indices), - .index.data = monkey_indices, .transform = transform }); + if (!vertices) { + return; + } + // Manual vertex format conversion to avoid another format (and sn8x3 isn't always supported) for (uint32_t i = 0; i < vertexCount; i++) { vertices[i] = (ShapeVertex) { @@ -4387,6 +4477,8 @@ void lovrPassMonkey(Pass* pass, float* transform) { .normal.z = monkey_vertices[6 * i + 5] / 255.f * 2.f - 1.f, }; } + + memcpy(indices, monkey_indices, sizeof(monkey_indices)); } static void renderNode(Pass* pass, Model* model, uint32_t index, bool recurse, uint32_t instances) {