diff --git a/CMakeLists.txt b/CMakeLists.txt index c532330..18bf350 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -40,6 +40,7 @@ add_executable(demo gpu.c linear_alg.c main.c + model.c ) configure_target(demo) target_include_directories(demo PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}") diff --git a/frag.glsl b/frag.glsl index dce8d2e..e9d6fa8 100644 --- a/frag.glsl +++ b/frag.glsl @@ -1,9 +1,9 @@ #version 450 -layout(location = 0) in vec3 frag_colour; +layout(location = 0) in vec3 frag_normal; layout(location = 0) out vec4 out_colour; void main() { - out_colour = vec4(frag_colour, 1.0); + out_colour = vec4(frag_normal, 1.0); } diff --git a/gpu.c b/gpu.c index 54eb81d..f468ed3 100644 --- a/gpu.c +++ b/gpu.c @@ -144,7 +144,7 @@ static void load_buffer( vkFreeCommandBuffers(ctx->dev, cmd_pool, 1, &cmd_buf); } -void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config) +void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config, model_t model) { // Initialise Vulkan instance. const VkApplicationInfo app_info = { @@ -428,7 +428,7 @@ void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config) .binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, - .offset = offsetof(vertex_t, colour), + .offset = offsetof(vertex_t, normal), }, }; const VkPipelineVertexInputStateCreateInfo vertex_input_config = { @@ -658,23 +658,24 @@ void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config) assert(result == VK_SUCCESS); // Set up staging, vertex and index buffers - ctx->index_count = config->index_data_size / sizeof(*config->index_data); + const size_t vertex_data_size = model.vertex_count * sizeof(vertex_t); + const size_t index_data_size = model.index_count * sizeof(vertex_t); + ctx->index_count = model.index_count; VkBuffer staging_buf; VkDeviceMemory staging_buf_mem; - const size_t staging_buf_size - = MAX(config->vertex_data_size, config->index_data_size); + const size_t staging_buf_size = MAX(vertex_data_size, index_data_size); create_buffer( ctx, staging_buf_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &staging_buf, &staging_buf_mem); create_buffer( - ctx, config->vertex_data_size, + ctx, vertex_data_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &ctx->vertex_buf, &ctx->vertex_buf_mem); create_buffer( - ctx, config->index_data_size, + ctx, index_data_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &ctx->index_buf, &ctx->index_buf_mem); @@ -692,11 +693,11 @@ void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config) // Load vertex and index data load_buffer( - ctx, ctx->vertex_buf, config->vertex_data, config->vertex_data_size, - staging_buf, staging_buf_mem, transient_cmd_pool); + ctx, ctx->vertex_buf, model.vertices, vertex_data_size, staging_buf, + staging_buf_mem, transient_cmd_pool); load_buffer( - ctx, ctx->index_buf, config->index_data, config->index_data_size, - staging_buf, staging_buf_mem, transient_cmd_pool); + ctx, ctx->index_buf, model.indices, index_data_size, staging_buf, + staging_buf_mem, transient_cmd_pool); // Clean up staging buffer and transient command pool vkDestroyCommandPool(ctx->dev, transient_cmd_pool, nullptr); diff --git a/gpu.h b/gpu.h index 16d39a2..916f9de 100644 --- a/gpu.h +++ b/gpu.h @@ -2,17 +2,13 @@ #define GPU_H #include "linear_alg.h" +#include "model.h" #define GLFW_INCLUDE_VULKAN #include #define SWAPCHAIN_IMG_BUFFER_SIZE 4 -typedef struct { - vec3_t position; - vec3_t colour; -} vertex_t; - typedef struct { mat4_t model; mat4_t view; @@ -53,13 +49,9 @@ typedef struct { typedef struct { const char *application_name; GLFWwindow *window; - const vertex_t *vertex_data; - size_t vertex_data_size; - const uint16_t *index_data; - size_t index_data_size; } gpu_config_t; -void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config); +void gpu_init(gpu_ctx_t *ctx, const gpu_config_t *config, model_t model); void gpu_cleanup(const gpu_ctx_t *ctx); void gpu_render(const gpu_ctx_t *ctx, const mvp_t *mvp); diff --git a/main.c b/main.c index 5c57657..197de48 100644 --- a/main.c +++ b/main.c @@ -1,4 +1,5 @@ #include "gpu.h" +#include "model.h" #include #include @@ -8,17 +9,6 @@ #define W 800 #define H 600 -static const vertex_t vertices[] = { - { { 0.000, 0.612, 0.000 }, { 1, 0, 0 } }, - { { -0.500, -0.204, -0.289 }, { 0, 1, 0 } }, - { { 0.500, -0.204, -0.289 }, { 0, 0, 1 } }, - { { 0.000, -0.204, 0.577 }, { 1, 1, 1 } }, -}; - -static const uint16_t indices[] = { - 0, 3, 1, 0, 1, 2, 0, 2, 3, 3, 2, 1, -}; - typedef enum { AXIS_X, AXIS_Y, @@ -54,8 +44,15 @@ key_callback(GLFWwindow *window, int key, int scancode, int action, int mods) } } -int main() +int main(int argc, char *argv[]) { + // Read model from passed path + if (argc != 2) { + fprintf(stderr, "Usage: %s PATH/TO/MODEL\n", argv[0]); + return 1; + } + model_t model = model_read(argv[1]); + // Set up window glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); @@ -70,18 +67,14 @@ int main() const gpu_config_t gpu_config = { .application_name = "Foo", .window = window, - .vertex_data = vertices, - .vertex_data_size = sizeof(vertices), - .index_data = indices, - .index_data_size = sizeof(indices), }; gpu_ctx_t gpu; - gpu_init(&gpu, &gpu_config); + gpu_init(&gpu, &gpu_config, model); - const vec3_t cam = { 2, 2, -2 }; - const vec3_t tgt = { 0, 0, 0 }; + const vec3_t cam = { 0, 4, -4 }; + const vec3_t tgt = { 0, 0.5, 0 }; mvp_t mvp = { - .model = MAT4_SCALE(2), + .model = MAT4_SCALE(0.667), .view = mat4_lookat(cam, tgt, (vec3_t) { 0, 1, 0 }), .proj = mat4_perspective((float)W / H, PI / 6, 0.1, 10.0), }; @@ -110,6 +103,7 @@ int main() // Clean up gpu_cleanup(&gpu); + model_cleanup(&model); glfwDestroyWindow(window); glfwTerminate(); diff --git a/model.c b/model.c new file mode 100644 index 0000000..5653628 --- /dev/null +++ b/model.c @@ -0,0 +1,241 @@ +#include "model.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#define BUFFER_SIZE 256 + +#define FIB_WRAP_COEFF 11400714819323198485LLU + +typedef struct { + uint8_t *rp; + size_t vertex_count; + size_t index_count; + size_t capacity; + size_t buffered; + const int fd; + uint8_t buffer[BUFFER_SIZE]; +} read_ctx_t; + +typedef struct { + unsigned probe_count; + index_t index; + bool occupied; +} table_node_t; + +typedef struct { + table_node_t *nodes; + size_t size; + unsigned shift; + unsigned max_probe_count; +} table_t; + +static uint32_t hash_vertex(vertex_t v) +{ + const uint32_t px = *((uint32_t *)&v.position.x); + const uint32_t py = *((uint32_t *)&v.position.y); + const uint32_t pz = *((uint32_t *)&v.position.z); + const uint32_t nx = *((uint32_t *)&v.normal.x); + const uint32_t ny = *((uint32_t *)&v.normal.y); + const uint32_t nz = *((uint32_t *)&v.normal.z); + return px ^ (py << 1) ^ (pz << 2) ^ (pz << 3) ^ (nx << 4) ^ (ny << 5) + ^ (nz << 6); +} + +static bool vertex_equals(vertex_t v, vertex_t u) +{ + return v.position.x == u.position.x && v.position.y == u.position.y + && v.position.z == u.position.z && v.normal.x == u.normal.x + && v.normal.y == u.normal.y && v.normal.z == u.normal.z; +} + +static unsigned wrap(table_t *table, uint32_t hash) +{ + return (unsigned)((hash * FIB_WRAP_COEFF) >> table->shift); +} + +static bool lookup( + table_t *table, model_t *model, unsigned base, vertex_t vertex, + index_t *index_out) +{ + for (unsigned i = 0; i < table->max_probe_count; ++i) { + const unsigned t = (base + i) & (UINT64_MAX >> table->shift); + const table_node_t *node = table->nodes + t; + if (!node->occupied) + return false; + + const vertex_t node_vertex = model->vertices[node->index]; + if (vertex_equals(vertex, node_vertex)) { + *index_out = node->index; + return true; + } + } + return false; +} + +static void insert(table_t *table, unsigned base, index_t index) +{ + for (unsigned i = 0; i < table->size; ++i) { + const unsigned t = (base + i) & (UINT64_MAX >> table->shift); + table_node_t *node = table->nodes + t; + + if (!node->occupied) { + node->occupied = true; + node->probe_count = i; + node->index = index; + if (i > table->max_probe_count) + table->max_probe_count = i; + return; + } + + if (node->probe_count < i) { + const unsigned node_probe_count = node->probe_count; + node->probe_count = i; + i = node_probe_count; + const index_t node_index = node->index; + node->index = index; + index = node_index; + } + } + + assert(false); +} + +static index_t handle_vertex(table_t *table, model_t *model, vertex_t vertex) +{ + index_t index; + const unsigned base = wrap(table, hash_vertex(vertex)); + const bool exists = lookup(table, model, base, vertex, &index); + if (exists) + return index; + index = model->vertex_count; + model->vertices[model->vertex_count++] = vertex; + insert(table, base, index); + return index; +} + +static uint8_t read_byte(read_ctx_t *ctx) +{ + if (ctx->rp >= ctx->buffer + ctx->buffered) { + const ssize_t res = read(ctx->fd, ctx->buffer, BUFFER_SIZE); + assert(res != -1); + ctx->buffered = (size_t)res; + ctx->rp = ctx->buffer; + } + return *ctx->rp++; +} + +static size_t read_triangle_count(read_ctx_t *ctx) +{ + size_t triangle_count = 0; + while (1) { + const uint8_t b = read_byte(ctx); + if (isdigit(b)) { + triangle_count = 10 * triangle_count + b - '0'; + } else { + assert(b == '\n'); + break; + } + } + return triangle_count; +} + +static float read_float(read_ctx_t *ctx) +{ + bool negative = false; + float value = 0, digit_div = 0; + while (1) { + const uint8_t b = read_byte(ctx); + if (b == '-') { + assert(value == 0); + negative = true; + } else if (b == '.') { + digit_div = 10; + } else if (isdigit(b)) { + if (digit_div == 0) { + value = 10 * value + (b - '0'); + } else { + value += (float)(b - '0') / digit_div; + digit_div *= 10; + } + } else { + assert(b == ' ' || b == '\n'); + break; + } + } + return (negative ? -1 : 1) * value; +} + +static vec3_t read_vec3(read_ctx_t *ctx) +{ + const float x = read_float(ctx); + const float y = read_float(ctx); + const float z = read_float(ctx); + return (vec3_t) { x, y, z }; +} + +static vertex_t read_vertex(read_ctx_t *ctx) +{ + const vec3_t position = read_vec3(ctx); + const vec3_t normal = read_vec3(ctx); + return (vertex_t) { position, normal }; +} + +model_t model_read(const char *path) +{ + read_ctx_t ctx = { .fd = open(path, O_RDONLY), .rp = ctx.buffer }; + assert(ctx.fd != -1); + + const size_t triangle_count = read_triangle_count(&ctx); + const size_t index_count = 3 * triangle_count; + + model_t model = { + .indices = malloc(index_count * sizeof(index_t)), + .index_count = index_count, + .vertices = malloc(index_count * sizeof(vertex_t)), + }; + assert(model.vertices != nullptr); + assert(model.indices != nullptr); + + table_t table = { .max_probe_count = 0 }; + table.size = stdc_bit_ceil((size_t)(1.5 * index_count)); + table.shift = 64 - stdc_trailing_zeros(table.size); + table.nodes = calloc(table.size, sizeof(table_node_t)); + assert(table.nodes != nullptr); + + for (size_t i = 0; i < index_count; ++i) { + const vertex_t vertex = read_vertex(&ctx); + index_t index = handle_vertex(&table, &model, vertex); + model.indices[i] = index; + + // Skip newline after each triangle + if (i % 3 == 2) { + const uint8_t b = read_byte(&ctx); + assert(b == '\n'); + } + } + + // Shrink vertex buffer down to needed size + model.vertices + = realloc(model.vertices, model.vertex_count * sizeof(vertex_t)); + assert(model.vertices != nullptr); + + free(table.nodes); + close(ctx.fd); + return model; +} + +void model_cleanup(model_t *m) +{ + free(m->vertices); + free(m->indices); + m->vertices = nullptr; + m->indices = nullptr; + m->vertex_count = m->index_count = 0; +} diff --git a/model.h b/model.h new file mode 100644 index 0000000..ab7dd0b --- /dev/null +++ b/model.h @@ -0,0 +1,26 @@ +#ifndef MODEL_H +#define MODEL_H + +#include "linear_alg.h" + +#include +#include + +typedef uint16_t index_t; + +typedef struct { + vec3_t position; + vec3_t normal; +} vertex_t; + +typedef struct { + vertex_t *vertices; + index_t *indices; + size_t vertex_count; + size_t index_count; +} model_t; + +model_t model_read(const char *path); +void model_cleanup(model_t *m); + +#endif diff --git a/vert.glsl b/vert.glsl index 5839af9..edd64e0 100644 --- a/vert.glsl +++ b/vert.glsl @@ -1,9 +1,9 @@ #version 450 -layout(location = 0) in vec3 pos; -layout(location = 1) in vec3 colour; +layout(location = 0) in vec3 position; +layout(location = 1) in vec3 normal; -layout(location = 0) out vec3 frag_colour; +layout(location = 0) out vec3 frag_normal; layout(push_constant) uniform MVP { mat4 model; @@ -12,6 +12,6 @@ layout(push_constant) uniform MVP { } mvp; void main() { - gl_Position = mvp.proj * mvp.view * mvp.model * vec4(pos, 1.0); - frag_colour = colour; + gl_Position = mvp.proj * mvp.view * mvp.model * vec4(position, 1.0); + frag_normal = normal; }