From 29af7cf14cd55ece0a8c8b1305f3b71ab57bebd6 Mon Sep 17 00:00:00 2001 From: 3gg <3gg@shellblade.net> Date: Fri, 26 May 2023 19:12:59 -0700 Subject: Add bounding boxes and an immediate mode renderer. Pending proper bounding boxes for animated meshes. --- gfx/CMakeLists.txt | 3 + gfx/include/gfx/gfx.h | 10 +- gfx/include/gfx/render_backend.h | 84 +++++++++--- gfx/include/gfx/renderer.h | 53 +++++++- gfx/include/gfx/scene/object.h | 7 + gfx/include/gfx/sizes.h | 8 +- gfx/include/gfx/util/shader.h | 3 + gfx/shaders/immediate_mode.frag | 10 ++ gfx/shaders/immediate_mode.vert | 11 ++ gfx/src/gfx.c | 17 ++- gfx/src/render/buffer.c | 32 ++++- gfx/src/render/buffer.h | 10 +- gfx/src/render/geometry.c | 284 ++++++++++++++++++++++++--------------- gfx/src/render/geometry.h | 8 +- gfx/src/render/render_backend.c | 35 +++++ gfx/src/renderer/imm_renderer.c | 181 +++++++++++++++++++++++++ gfx/src/renderer/renderer.c | 30 +++-- gfx/src/renderer/renderer_impl.h | 42 +++++- gfx/src/scene/object.c | 32 +++++ gfx/src/scene/scene_memory.c | 3 + gfx/src/util/scene.c | 132 +++++++++++------- gfx/src/util/shader.c | 7 + gltfview/src/game.c | 41 +++++- 23 files changed, 823 insertions(+), 220 deletions(-) create mode 100644 gfx/shaders/immediate_mode.frag create mode 100644 gfx/shaders/immediate_mode.vert create mode 100644 gfx/src/renderer/imm_renderer.c diff --git a/gfx/CMakeLists.txt b/gfx/CMakeLists.txt index d60a59f..8a9bc8f 100644 --- a/gfx/CMakeLists.txt +++ b/gfx/CMakeLists.txt @@ -16,6 +16,8 @@ add_shader_library(shaders shaders/cubemap_filtering.vert shaders/debug3d.frag shaders/debug3d.vert + shaders/immediate_mode.frag + shaders/immediate_mode.vert shaders/irradiance_map.frag shaders/prefiltered_environment_map.frag shaders/quad.vert @@ -39,6 +41,7 @@ add_library(gfx src/render/shader_program.c src/render/shader.c src/render/texture.c + src/renderer/imm_renderer.c src/renderer/renderer.c src/scene/animation.c src/scene/camera.c diff --git a/gfx/include/gfx/gfx.h b/gfx/include/gfx/gfx.h index b9b7183..303ddcd 100644 --- a/gfx/include/gfx/gfx.h +++ b/gfx/include/gfx/gfx.h @@ -1,9 +1,10 @@ #pragma once +typedef struct ImmRenderer ImmRenderer; typedef struct RenderBackend RenderBackend; -typedef struct Renderer Renderer; -typedef struct Scene Scene; -typedef struct SceneCamera SceneCamera; +typedef struct Renderer Renderer; +typedef struct Scene Scene; +typedef struct SceneCamera SceneCamera; typedef struct Gfx Gfx; @@ -19,6 +20,9 @@ RenderBackend* gfx_get_render_backend(Gfx*); /// Get the renderer. Renderer* gfx_get_renderer(Gfx*); +/// Get the immediate mode renderer. +ImmRenderer* gfx_get_imm_renderer(Gfx*); + /// Create a new scene. Scene* gfx_make_scene(Gfx*); diff --git a/gfx/include/gfx/render_backend.h b/gfx/include/gfx/render_backend.h index ef6e4e9..b07cf16 100644 --- a/gfx/include/gfx/render_backend.h +++ b/gfx/include/gfx/render_backend.h @@ -6,6 +6,7 @@ #include "sizes.h" +#include #include #include #include @@ -47,10 +48,24 @@ typedef enum BufferType { Buffer2d, Buffer3d, Buffer4d, + BufferFloat, BufferU8, BufferU16 } BufferType; +/// Buffer data descriptor. +typedef struct BufferDataDesc { + union { + const void* data; + const vec2* vec2s; + const vec3* vec3s; + const float* floats; + const uint8_t* u8s; + const uint16_t* u16s; + }; + size_t count; +} BufferDataDesc; + /// Buffer descriptor. /// /// 'count' is the number of elements in the array. For untyped buffers, this is @@ -64,27 +79,20 @@ typedef enum BufferType { /// Typed buffers don't work well with interleaved vertex attributes. Not sure /// this is really worth it. typedef struct BufferDesc { - BufferUsage usage; - BufferType type; - union { - const void* data; - const vec2* vec2s; - const vec3* vec3s; - const uint8_t* u8s; - const uint16_t* u16s; - }; - size_t count; + BufferUsage usage; + BufferType type; + BufferDataDesc data; } BufferDesc; /// A buffer view for vertex data (attributes or indices). /// Either 'data' or 'buffer' must be set. #define MAKE_BUFFER_VIEW(NAME, TYPE) \ typedef struct NAME { \ - const TYPE* data; \ - const Buffer* buffer; \ - size_t offset_bytes; \ - size_t size_bytes; \ - size_t stride_bytes; \ + const TYPE* data; \ + Buffer* buffer; \ + size_t offset_bytes; \ + size_t size_bytes; \ + size_t stride_bytes; \ } NAME; /// A buffer view for untyped data. @@ -116,6 +124,14 @@ MAKE_BUFFER_VIEW(BufferViewIdx16, uint16_t) /// Describes a piece of geometry. /// +/// Buffer views may point to either already-existing GPU buffers or to data in +/// host memory. +/// +/// If the buffer views do not already point to GPU buffers, GPU buffers are +/// created for the geometry. The 'buffer_usage' field specifies the usage for +/// the created buffers. Use BufferStatic for static geometry and BufferDynamic +/// for dynamic geometry. +/// /// Currently we support only up to 16-bit vertex indices. Might have to change /// this to support a larger variety of 3D models. typedef struct GeometryDesc { @@ -138,6 +154,8 @@ typedef struct GeometryDesc { VertexCount num_verts; size_t num_indices; PrimitiveType type; + BufferUsage buffer_usage; + aabb3 aabb; } GeometryDesc; /// Shader compiler define. @@ -298,6 +316,18 @@ void gfx_set_viewport(RenderBackend*, int width, int height); /// Get the render backend's viewport dimensions. void gfx_get_viewport(RenderBackend*, int* width, int* height); +/// Set blending state. +void gfx_set_blending(RenderBackend*, bool enable); + +/// Set depth mask. +void gfx_set_depth_mask(RenderBackend*, bool enable); + +/// Set cull mode. +void gfx_set_culling(RenderBackend*, bool enable); + +/// Set polygon offset. +void gfx_set_polygon_offset(RenderBackend*, float scale, float bias); + // ----------------------------------------------------------------------------- // Buffers. // ----------------------------------------------------------------------------- @@ -308,6 +338,9 @@ Buffer* gfx_make_buffer(RenderBackend*, const BufferDesc*); /// Destroy the buffer. void gfx_destroy_buffer(RenderBackend*, Buffer**); +/// Update the buffer's data. +void gfx_update_buffer(RenderBackend*, Buffer*, const BufferDataDesc*); + // ----------------------------------------------------------------------------- // Geometry. // ----------------------------------------------------------------------------- @@ -321,20 +354,27 @@ void gfx_destroy_geometry(RenderBackend*, Geometry**); /// Upload new vertex data for the geometry. /// /// This is similar to gfx_make_geometry(), but the geometry need not be -/// entirely specified. Only the vertex attributes set in the descriptor are -/// updated. +/// entirely specified. +/// +/// Only the vertex attributes, vertex count, and index count set in the +/// descriptor are updated. Index data, primitive type, and other properties of +/// the geometry are not updated. /// -/// This function only updates vertex attributes, not indices or primitive type. +/// New data must be given as arrays in host memory. That is, the buffer views +/// in the descriptor must point to CPU arrays, not GPU buffers. /// -/// Note that the descriptor cannot specify more vertex attributes than the -/// geometry was created with. If the size or any other attribute not handled -/// by this update function needs to be changed, then a new geometry must be -/// created. +/// Note that the descriptor cannot specify a larger vertex or index count than +/// what the geometry was created with. If the geometry size or any other +/// attribute not handled by this update function needs to be changed, then a +/// new geometry must be created. void gfx_update_geometry(Geometry*, const GeometryDesc*); /// Render the geometry. void gfx_render_geometry(const Geometry*); +/// Return the geometry's bounding box. +aabb3 gfx_get_geometry_aabb(const Geometry*); + // ----------------------------------------------------------------------------- // Textures. // ----------------------------------------------------------------------------- diff --git a/gfx/include/gfx/renderer.h b/gfx/include/gfx/renderer.h index d82fab6..1517af6 100644 --- a/gfx/include/gfx/renderer.h +++ b/gfx/include/gfx/renderer.h @@ -1,11 +1,54 @@ #pragma once +#include +#include +#include +#include +#include + typedef struct RenderBackend RenderBackend; -typedef struct Scene Scene; -typedef struct SceneCamera SceneCamera; +typedef struct Scene Scene; +typedef struct SceneCamera SceneCamera; + +typedef struct ImmRenderer ImmRenderer; +typedef struct Renderer Renderer; -typedef struct Renderer Renderer; +// ----------------------------------------------------------------------------- +// Main Renderer. +// ----------------------------------------------------------------------------- /// Render the scene. -void gfx_render_scene(Renderer*, RenderBackend*, const Scene*, - const SceneCamera*); +void gfx_render_scene(Renderer*, const Scene*, const SceneCamera*); + +// ----------------------------------------------------------------------------- +// Immediate Mode Renderer. +// ----------------------------------------------------------------------------- + +/// Prepare the graphics systems for immediate-mode rendering. +/// +/// Call this before issuing any immediate-mode rendering draws. +void gfx_imm_start(ImmRenderer*); + +/// End immediate mode rendering. +/// +/// Call this after issuing immediate-mode rendering draws and before swapping +/// buffers. +void gfx_imm_end(ImmRenderer*); + +/// Draw a set of triangles. +void gfx_imm_draw_triangles(ImmRenderer*, const vec3[], size_t num_triangles); + +/// Draw a triangle. +void gfx_imm_draw_triangle(ImmRenderer*, const vec3[3]); + +/// Draw a bounding box. +void gfx_imm_draw_aabb(ImmRenderer*, aabb3); + +/// Set the camera. +void gfx_imm_set_camera(ImmRenderer*, const Camera*); + +/// Set the model matrix. +void gfx_imm_set_model_matrix(ImmRenderer*, const mat4*); + +/// Set the render colour. +void gfx_imm_set_colour(ImmRenderer*, vec4 colour); diff --git a/gfx/include/gfx/scene/object.h b/gfx/include/gfx/scene/object.h index 59372c5..ccc9999 100644 --- a/gfx/include/gfx/scene/object.h +++ b/gfx/include/gfx/scene/object.h @@ -2,6 +2,8 @@ #include +#include + typedef struct Mesh Mesh; typedef struct SceneNode SceneNode; typedef struct Skeleton Skeleton; @@ -28,3 +30,8 @@ void gfx_remove_object_mesh(SceneObject*, Mesh*); /// Set the object's skeleton. void gfx_set_object_skeleton(SceneObject*, const Skeleton*); + +/// Computes the object's bounding box. +/// +/// The object's bounding box is the bounding box of its mesh geometries. +aabb3 gfx_calc_object_aabb(const SceneObject*); diff --git a/gfx/include/gfx/sizes.h b/gfx/include/gfx/sizes.h index 17e7c7d..9f13e30 100644 --- a/gfx/include/gfx/sizes.h +++ b/gfx/include/gfx/sizes.h @@ -74,6 +74,12 @@ /// Maximum number of compiler defines in a Shader. #define GFX_MAX_SHADER_COMPILER_DEFINES 16 -// Gfx +// Renderer. + +/// Maximum number of triangles that the immediate-mode renderer can draw in a +/// frame. +#define IMM_MAX_NUM_TRIANGLES 1024 + +// Gfx. #define GFX_MAX_NUM_SCENES 4 diff --git a/gfx/include/gfx/util/shader.h b/gfx/include/gfx/util/shader.h index cadca55..9bde8cf 100644 --- a/gfx/include/gfx/util/shader.h +++ b/gfx/include/gfx/util/shader.h @@ -21,6 +21,9 @@ ShaderProgram* gfx_make_cook_torrance_shader_perm( /// Create a 3D debugging shader. ShaderProgram* gfx_make_debug3d_shader(RenderBackend*); +/// Create a shader for drawing in immediate mode. +ShaderProgram* gfx_make_immediate_mode_shader(RenderBackend*); + /// Create a shader for computing irradiance maps from cube maps. ShaderProgram* gfx_make_irradiance_map_shader(RenderBackend*); diff --git a/gfx/shaders/immediate_mode.frag b/gfx/shaders/immediate_mode.frag new file mode 100644 index 0000000..ac23b5c --- /dev/null +++ b/gfx/shaders/immediate_mode.frag @@ -0,0 +1,10 @@ +precision highp float; + +uniform vec4 Colour; + +out vec4 FragColour; + +void main() +{ + FragColour = vec4(pow(Colour.rgb, vec3(1.0/2.2)), Colour.a); +} diff --git a/gfx/shaders/immediate_mode.vert b/gfx/shaders/immediate_mode.vert new file mode 100644 index 0000000..65070bb --- /dev/null +++ b/gfx/shaders/immediate_mode.vert @@ -0,0 +1,11 @@ +precision highp float; + +uniform mat4 Model; +uniform mat4 ViewProjection; + +layout (location = 0) in vec3 vPosition; + +void main() +{ + gl_Position = ViewProjection * Model * vec4(vPosition, 1.0); +} diff --git a/gfx/src/gfx.c b/gfx/src/gfx.c index 4640a52..27312b2 100644 --- a/gfx/src/gfx.c +++ b/gfx/src/gfx.c @@ -13,9 +13,10 @@ #include typedef struct Gfx { + scene_idx scene; // First child scene. RenderBackend render_backend; Renderer renderer; - scene_idx scene; // First child scene. + ImmRenderer imm_renderer; } Gfx; Gfx* gfx_init() { @@ -33,6 +34,12 @@ Gfx* gfx_init() { gfx_destroy(&gfx); return 0; } + if (!imm_renderer_make(&gfx->imm_renderer, &gfx->render_backend)) { + // TODO: Add error logs to the initialization failure cases here and inside + // the renderers. + gfx_destroy(&gfx); + return 0; + } scene_mem_init(); return gfx; } @@ -43,7 +50,8 @@ void gfx_destroy(Gfx** gfx) { return; } scene_mem_destroy(); - renderer_destroy(&(*gfx)->renderer, &(*gfx)->render_backend); + renderer_destroy(&(*gfx)->renderer); + imm_renderer_destroy(&(*gfx)->imm_renderer); gfx_del_render_backend(&(*gfx)->render_backend); free(*gfx); *gfx = 0; @@ -59,6 +67,11 @@ Renderer* gfx_get_renderer(Gfx* gfx) { return &gfx->renderer; } +ImmRenderer* gfx_get_imm_renderer(Gfx* gfx) { + assert(gfx); + return &gfx->imm_renderer; +} + Scene* gfx_make_scene(Gfx* gfx) { Scene* scene = mem_alloc_scene(); if (!scene) { diff --git a/gfx/src/render/buffer.c b/gfx/src/render/buffer.c index 392777c..3c0c794 100644 --- a/gfx/src/render/buffer.c +++ b/gfx/src/render/buffer.c @@ -6,8 +6,9 @@ #include #include -static size_t get_buffer_size_bytes(const BufferDesc* desc) { - switch (desc->type) { +static size_t get_buffer_size_bytes( + BufferType type, const BufferDataDesc* desc) { + switch (type) { case BufferUntyped: return desc->count; case Buffer2d: @@ -16,11 +17,15 @@ static size_t get_buffer_size_bytes(const BufferDesc* desc) { return desc->count * sizeof(vec3); case Buffer4d: return desc->count * sizeof(vec4); + case BufferFloat: + return desc->count * sizeof(float); case BufferU8: return desc->count * sizeof(uint8_t); case BufferU16: return desc->count * sizeof(uint16_t); } + assert(false); + return 0; } static GLenum get_buffer_usage(BufferUsage usage) { @@ -36,11 +41,13 @@ static GLenum get_buffer_usage(BufferUsage usage) { bool gfx_init_buffer(Buffer* buffer, const BufferDesc* desc) { assert(buffer); - buffer->size_bytes = get_buffer_size_bytes(desc); + buffer->type = desc->type; + buffer->usage = desc->usage; + buffer->size_bytes = get_buffer_size_bytes(desc->type, &desc->data); const GLenum usage = get_buffer_usage(desc->usage); glGenBuffers(1, &buffer->vbo); glBindBuffer(GL_ARRAY_BUFFER, buffer->vbo); - glBufferData(GL_ARRAY_BUFFER, buffer->size_bytes, desc->data, usage); + glBufferData(GL_ARRAY_BUFFER, buffer->size_bytes, desc->data.data, usage); glBindBuffer(GL_ARRAY_BUFFER, 0); ASSERT_GL; return true; @@ -53,3 +60,20 @@ void gfx_del_buffer(Buffer* buffer) { buffer->vbo = 0; } } + +void gfx_update_buffer( + RenderBackend* render_backend, Buffer* buffer, const BufferDataDesc* desc) { + assert(render_backend); + assert(buffer); + assert(desc); + // OpenGL allows updating static buffers, but it is not optimal for + // performance, so we enforce data in static buffers remain static. + assert(buffer->usage == BufferDynamic); + + const size_t update_size_bytes = get_buffer_size_bytes(buffer->type, desc); + assert(update_size_bytes <= buffer->size_bytes); + + glBindBuffer(GL_ARRAY_BUFFER, buffer->vbo); + glBufferSubData(GL_ARRAY_BUFFER, 0, update_size_bytes, desc->data); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} diff --git a/gfx/src/render/buffer.h b/gfx/src/render/buffer.h index 575fbb9..0c81e7b 100644 --- a/gfx/src/render/buffer.h +++ b/gfx/src/render/buffer.h @@ -1,5 +1,7 @@ #pragma once +#include + #include "gl_util.h" #include @@ -7,11 +9,11 @@ #include #include -typedef struct BufferDesc BufferDesc; - typedef struct Buffer { - GLuint vbo; - size_t size_bytes; + GLuint vbo; + BufferType type; + BufferUsage usage; + size_t size_bytes; } Buffer; /// Create a buffer from raw data. diff --git a/gfx/src/render/geometry.c b/gfx/src/render/geometry.c index 076a956..e9d3ae5 100644 --- a/gfx/src/render/geometry.c +++ b/gfx/src/render/geometry.c @@ -6,7 +6,11 @@ #include #include -#define view_is_populated(BUFFER_VIEW) (BUFFER_VIEW.data || BUFFER_VIEW.buffer) +/// Determines whether a view is populated. +/// +/// Note that views are allowed to have no data, in which case a buffer of the +/// specified size is created. +#define view_is_populated(BUFFER_VIEW) (BUFFER_VIEW.size_bytes > 0) static GLenum primitive_type_to_gl(PrimitiveType type) { switch (type) { @@ -22,23 +26,18 @@ static GLenum primitive_type_to_gl(PrimitiveType type) { } } -/// Create a buffer for the view. -/// -/// If the view already has a buffer, return the buffer. Otherwise create a -/// buffer from the view's data and assign it to the view. -static const Buffer* get_or_make_view_buffer( - RenderBackend* render_backend, BufferView* view) { - if (view->buffer) { - return view->buffer; - } else { - return view->buffer = gfx_make_buffer( - render_backend, &(BufferDesc){ - .usage = BufferStatic, - .type = BufferUntyped, - .data = view->data, - .count = view->size_bytes}); - } -} +/// Create a buffer for the buffer view if the view does not already point to +/// a buffer. +#define INIT_VIEW_BUFFER(render_backend, view, buffer_type, buffer_usage) \ + if (!view.buffer) { \ + view.buffer = (__typeof__(view.buffer))gfx_make_buffer( \ + render_backend, &(BufferDesc){ \ + .usage = buffer_usage, \ + .type = buffer_type, \ + .data.data = view.data, \ + .data.count = view.size_bytes}); \ + } \ + assert(view.size_bytes <= view.buffer->size_bytes); /// Create a buffer for the view, then configure it in the VAO. static bool configure_buffer( @@ -48,15 +47,13 @@ static bool configure_buffer( assert(render_backend); assert(desc); assert(view); + assert(view->buffer); assert( desc->num_verts <= view->size_bytes / (num_components * component_size_bytes)); - const Buffer* buffer = get_or_make_view_buffer(render_backend, view); - if (!buffer) { - return false; - } - assert(view->size_bytes <= buffer->size_bytes); - glBindBuffer(GL_ARRAY_BUFFER, buffer->vbo); + assert(view->size_bytes <= view->buffer->size_bytes); + + glBindBuffer(GL_ARRAY_BUFFER, view->buffer->vbo); glEnableVertexAttribArray(channel); if ((component_type == GL_FLOAT) || normalized) { glVertexAttribPointer( @@ -72,6 +69,8 @@ static bool configure_buffer( channel, num_components, component_type, view->stride_bytes, (const void*)view->offset_bytes); } + glBindBuffer(GL_ARRAY_BUFFER, 0); + return true; } @@ -80,74 +79,138 @@ static bool configure_vertex_attributes( assert(render_backend); assert(desc); - bool result = true; - if (view_is_populated(desc->positions3d)) { - result = - result && configure_buffer( - render_backend, desc, (BufferView*)&desc->positions3d, 3, - sizeof(float), GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL); + INIT_VIEW_BUFFER( + render_backend, desc->positions3d, Buffer3d, desc->buffer_usage); + if (!desc->positions3d.buffer || + !configure_buffer( + render_backend, desc, (BufferView*)&desc->positions3d, 3, + sizeof(float), GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL)) { + return false; + } } else if (view_is_populated(desc->positions2d)) { - result = - result && configure_buffer( - render_backend, desc, (BufferView*)&desc->positions2d, 2, - sizeof(float), GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL); + INIT_VIEW_BUFFER( + render_backend, desc->positions2d, Buffer2d, desc->buffer_usage); + if (!desc->positions2d.buffer || + !configure_buffer( + render_backend, desc, (BufferView*)&desc->positions2d, 2, + sizeof(float), GL_FLOAT, GL_FALSE, GFX_POSITION_CHANNEL)) { + return false; + } } - if (view_is_populated(desc->normals)) { - result = - result && configure_buffer( - render_backend, desc, (BufferView*)&desc->normals, 3, - sizeof(float), GL_FLOAT, GL_FALSE, GFX_NORMAL_CHANNEL); + INIT_VIEW_BUFFER( + render_backend, desc->normals, Buffer3d, desc->buffer_usage); + if (!desc->normals.buffer || + !configure_buffer( + render_backend, desc, (BufferView*)&desc->normals, 3, sizeof(float), + GL_FLOAT, GL_FALSE, GFX_NORMAL_CHANNEL)) { + return false; + } } - if (view_is_populated(desc->tangents)) { - result = - result && configure_buffer( - render_backend, desc, (BufferView*)&desc->tangents, 4, - sizeof(float), GL_FLOAT, GL_FALSE, GFX_TANGENT_CHANNEL); + INIT_VIEW_BUFFER( + render_backend, desc->tangents, Buffer4d, desc->buffer_usage); + if (!desc->tangents.buffer || + !configure_buffer( + render_backend, desc, (BufferView*)&desc->tangents, 4, + sizeof(float), GL_FLOAT, GL_FALSE, GFX_TANGENT_CHANNEL)) { + return false; + } } - if (view_is_populated(desc->texcoords)) { - result = - result && configure_buffer( - render_backend, desc, (BufferView*)&desc->texcoords, 2, - sizeof(float), GL_FLOAT, GL_FALSE, GFX_TEXCOORDS_CHANNEL); + INIT_VIEW_BUFFER( + render_backend, desc->texcoords, Buffer2d, desc->buffer_usage); + if (!desc->texcoords.buffer || + !configure_buffer( + render_backend, desc, (BufferView*)&desc->texcoords, 2, + sizeof(float), GL_FLOAT, GL_FALSE, GFX_TEXCOORDS_CHANNEL)) { + return false; + } } - if (view_is_populated(desc->joints.u8)) { - result = result && configure_buffer( - render_backend, desc, (BufferView*)&desc->joints.u8, - 4, sizeof(uint8_t), GL_UNSIGNED_BYTE, GL_FALSE, - GFX_JOINTS_CHANNEL); + INIT_VIEW_BUFFER( + render_backend, desc->joints.u8, BufferU8, desc->buffer_usage); + if (!desc->joints.u8.buffer || + !configure_buffer( + render_backend, desc, (BufferView*)&desc->joints.u8, 4, + sizeof(uint8_t), GL_UNSIGNED_BYTE, GL_FALSE, GFX_JOINTS_CHANNEL)) { + return false; + } } else if (view_is_populated(desc->joints.u16)) { - result = result && configure_buffer( - render_backend, desc, (BufferView*)&desc->joints.u16, - 4, sizeof(uint16_t), GL_UNSIGNED_SHORT, GL_FALSE, - GFX_JOINTS_CHANNEL); + INIT_VIEW_BUFFER( + render_backend, desc->joints.u16, BufferU16, desc->buffer_usage); + if (!desc->joints.u16.buffer || + !configure_buffer( + render_backend, desc, (BufferView*)&desc->joints.u16, 4, + sizeof(uint16_t), GL_UNSIGNED_SHORT, GL_FALSE, + GFX_JOINTS_CHANNEL)) { + return false; + } } // If weights are given as unsigned integers, then they are normalized when // read by the shader. if (view_is_populated(desc->weights.u8)) { - result = result && configure_buffer( - render_backend, desc, (BufferView*)&desc->weights.u8, - 4, sizeof(uint8_t), GL_UNSIGNED_BYTE, GL_TRUE, - GFX_WEIGHTS_CHANNEL); + INIT_VIEW_BUFFER( + render_backend, desc->weights.u8, BufferU8, desc->buffer_usage); + if (!desc->weights.u8.buffer || + !configure_buffer( + render_backend, desc, (BufferView*)&desc->weights.u8, 4, + sizeof(uint8_t), GL_UNSIGNED_BYTE, GL_TRUE, GFX_WEIGHTS_CHANNEL)) { + return false; + } } else if (view_is_populated(desc->weights.u16)) { - result = result && configure_buffer( - render_backend, desc, - (BufferView*)&desc->weights.u16, 4, sizeof(uint16_t), - GL_UNSIGNED_SHORT, GL_TRUE, GFX_WEIGHTS_CHANNEL); + INIT_VIEW_BUFFER( + render_backend, desc->weights.u16, BufferU16, desc->buffer_usage); + if (!desc->weights.u16.buffer || + !configure_buffer( + render_backend, desc, (BufferView*)&desc->weights.u16, 4, + sizeof(uint16_t), GL_UNSIGNED_SHORT, GL_TRUE, + GFX_WEIGHTS_CHANNEL)) { + return false; + } } else if (view_is_populated(desc->weights.floats)) { - result = result && - configure_buffer( - render_backend, desc, (BufferView*)&desc->weights.floats, 4, - sizeof(float), GL_FLOAT, GL_FALSE, GFX_WEIGHTS_CHANNEL); + INIT_VIEW_BUFFER( + render_backend, desc->weights.floats, BufferFloat, desc->buffer_usage); + if (!desc->weights.floats.buffer || + !configure_buffer( + render_backend, desc, (BufferView*)&desc->weights.floats, 4, + sizeof(float), GL_FLOAT, GL_FALSE, GFX_WEIGHTS_CHANNEL)) { + return false; + } } - glBindBuffer(GL_ARRAY_BUFFER, 0); - return result; + return true; +} + +static bool configure_indices( + RenderBackend* render_backend, GeometryDesc* desc) { + assert(render_backend); + assert(desc); + + if (view_is_populated(desc->indices8)) { + assert(desc->num_indices > 0); + assert( + desc->num_indices <= desc->indices8.size_bytes / sizeof(VertexIndex8)); + INIT_VIEW_BUFFER( + render_backend, desc->indices8, BufferU8, desc->buffer_usage); + if (!desc->indices8.buffer) { + return false; + } + } else if (view_is_populated(desc->indices16)) { + assert(desc->num_indices > 0); + assert( + desc->num_indices <= + desc->indices16.size_bytes / sizeof(VertexIndex16)); + INIT_VIEW_BUFFER( + render_backend, desc->indices16, BufferU16, desc->buffer_usage); + if (!desc->indices16.buffer) { + return false; + } + } + + return true; } bool gfx_init_geometry( @@ -163,47 +226,29 @@ bool gfx_init_geometry( geometry->mode = primitive_type_to_gl(input_desc->type); geometry->desc = *input_desc; + geometry->num_verts = input_desc->num_verts; geometry->render_backend = render_backend; - // We manipulate the descriptor copy below. Create a shorter name for it. + // The geometry's copy of the descriptor is manipulated below. Create a + // shorter name for it. GeometryDesc* desc = &geometry->desc; glGenVertexArrays(1, &geometry->vao); glBindVertexArray(geometry->vao); - if (!configure_vertex_attributes(render_backend, desc)) { - gfx_del_geometry(geometry); - return false; + goto cleanup; } - - if (view_is_populated(desc->indices8)) { - assert(desc->num_indices > 0); - assert( - desc->num_indices <= desc->indices8.size_bytes / sizeof(VertexIndex8)); - const Buffer* buffer = - get_or_make_view_buffer(render_backend, (BufferView*)&desc->indices8); - if (!buffer) { - gfx_del_geometry(geometry); - return false; - } - assert(desc->indices8.size_bytes <= buffer->size_bytes); - } else if (view_is_populated(desc->indices16)) { - assert(desc->num_indices > 0); - assert( - desc->num_indices <= - desc->indices16.size_bytes / sizeof(VertexIndex16)); - const Buffer* buffer = - get_or_make_view_buffer(render_backend, (BufferView*)&desc->indices16); - if (!buffer) { - gfx_del_geometry(geometry); - return false; - } - assert(desc->indices16.size_bytes <= buffer->size_bytes); + if (!configure_indices(render_backend, desc)) { + goto cleanup; } - glBindVertexArray(0); ASSERT_GL; - return geometry; + + return true; + +cleanup: + gfx_del_geometry(geometry); + return 0; } void gfx_del_geometry(Geometry* geometry) { @@ -217,6 +262,7 @@ void gfx_del_geometry(Geometry* geometry) { void gfx_update_geometry(Geometry* geometry, const GeometryDesc* desc) { assert(geometry); assert(desc); + // New geometry size cannot exceed original size. assert(desc->positions3d.size_bytes <= geometry->desc.positions3d.size_bytes); assert(desc->positions2d.size_bytes <= geometry->desc.positions2d.size_bytes); assert(desc->normals.size_bytes <= geometry->desc.normals.size_bytes); @@ -230,22 +276,32 @@ void gfx_update_geometry(Geometry* geometry, const GeometryDesc* desc) { desc->weights.floats.size_bytes <= geometry->desc.weights.floats.size_bytes); - geometry->desc = *desc; + if (desc->positions3d.data) { + // The geometry must already have an underlying GPU buffer. + assert(geometry->desc.positions3d.buffer); + gfx_update_buffer( + geometry->render_backend, geometry->desc.positions3d.buffer, + &(BufferDataDesc){ + .vec3s = desc->positions3d.data, + .count = desc->positions3d.size_bytes / sizeof(vec3)}); + } + // TODO: more + else { + assert(false && "TODO: gfx_update_geometry() - handle other buffer types"); + } - glBindVertexArray(geometry->vao); - bool result = - configure_vertex_attributes(geometry->render_backend, &geometry->desc); - // Shouldn't fail since we're just uploading buffer data, not creating new - // buffers. - assert(result); - glBindVertexArray(0); + if (desc->num_verts != 0) { + geometry->num_verts = desc->num_verts; + } } void gfx_render_geometry(const Geometry* geometry) { assert(geometry); assert(geometry->vao); + const GeometryDesc* desc = &geometry->desc; glBindVertexArray(geometry->vao); + if (desc->indices8.buffer) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, desc->indices8.buffer->vbo); glDrawElements( @@ -257,7 +313,13 @@ void gfx_render_geometry(const Geometry* geometry) { geometry->mode, desc->num_indices, GL_UNSIGNED_SHORT, (const void*)desc->indices16.offset_bytes); } else { - glDrawArrays(geometry->mode, 0, desc->num_verts); + glDrawArrays(geometry->mode, 0, geometry->num_verts); } + glBindVertexArray(0); } + +aabb3 gfx_get_geometry_aabb(const Geometry* geometry) { + assert(geometry); + return geometry->desc.aabb; +} diff --git a/gfx/src/render/geometry.h b/gfx/src/render/geometry.h index 8fb36da..484934d 100644 --- a/gfx/src/render/geometry.h +++ b/gfx/src/render/geometry.h @@ -13,9 +13,11 @@ /// the renderer assumes ownership of all rendering resources, which simplifies /// their management. typedef struct Geometry { - GLuint vao; - GLenum mode; - GeometryDesc desc; + GLuint vao; + GLenum mode; + GeometryDesc desc; + size_t num_verts; // May differ from the initial value in the descriptor if + // the geometry is updated. RenderBackend* render_backend; } Geometry; diff --git a/gfx/src/render/render_backend.c b/gfx/src/render/render_backend.c index 8feefab..defc164 100644 --- a/gfx/src/render/render_backend.c +++ b/gfx/src/render/render_backend.c @@ -96,6 +96,41 @@ void gfx_get_viewport(RenderBackend* render_backend, int* width, int* height) { *height = render_backend->viewport.height; } +void gfx_set_blending(RenderBackend* render_backend, bool enable) { + assert(render_backend); + if (enable) { + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } else { + glDisable(GL_BLEND); + } +} + +void gfx_set_depth_mask(RenderBackend* render_backend, bool enable) { + assert(render_backend); + glDepthMask(enable ? GL_TRUE : GL_FALSE); +} + +void gfx_set_culling(RenderBackend* render_backend, bool enable) { + assert(render_backend); + if (enable) { + glEnable(GL_CULL_FACE); + } else { + glDisable(GL_CULL_FACE); + } +} + +void gfx_set_polygon_offset( + RenderBackend* render_backend, float scale, float bias) { + assert(render_backend); + if ((scale != 0.0f) || (bias != 0.0f)) { + glEnable(GL_POLYGON_OFFSET_FILL); + } else { + glDisable(GL_POLYGON_OFFSET_FILL); + } + glPolygonOffset(scale, bias); +} + // ----------------------------------------------------------------------------- // Buffers. // ----------------------------------------------------------------------------- diff --git a/gfx/src/renderer/imm_renderer.c b/gfx/src/renderer/imm_renderer.c new file mode 100644 index 0000000..e9f98f8 --- /dev/null +++ b/gfx/src/renderer/imm_renderer.c @@ -0,0 +1,181 @@ +#include "renderer_impl.h" + +#include +#include + +#include + +#include +#include // memcpy + +#include // TODO: remove + +bool imm_renderer_make(ImmRenderer* renderer, RenderBackend* render_backend) { + assert(renderer); + assert(render_backend); + + const size_t num_triangle_verts = IMM_MAX_NUM_TRIANGLES * 3; + + renderer->render_backend = render_backend; + + renderer->triangles = gfx_make_geometry( + render_backend, + &(GeometryDesc){ + .type = Triangles, + .buffer_usage = BufferDynamic, + .num_verts = num_triangle_verts, + .positions3d = + (BufferView3d){.size_bytes = num_triangle_verts * sizeof(vec3)}}); + if (!renderer->triangles) { + goto cleanup; + } + + renderer->shader = gfx_make_immediate_mode_shader(render_backend); + if (!renderer->shader) { + goto cleanup; + } + + gfx_imm_set_colour(renderer, vec4_make(0.0, 0.0, 0.0, 1.0)); + + return true; + +cleanup: + imm_renderer_destroy(renderer); + return false; +} + +void imm_renderer_destroy(ImmRenderer* renderer) { + assert(renderer); + assert(renderer->render_backend); + + if (renderer->triangles) { + gfx_destroy_geometry(renderer->render_backend, &renderer->triangles); + // TODO: Could also destroy the geometry's buffers here. + } + + if (renderer->shader) { + gfx_destroy_shader_program(renderer->render_backend, &renderer->shader); + } +} + +void imm_renderer_flush(ImmRenderer* renderer) { + assert(renderer); + + if (renderer->num_triangle_verts > 0) { + gfx_update_geometry( + renderer->triangles, + &(GeometryDesc){ + .num_verts = renderer->num_triangle_verts, + .positions3d = (BufferView3d){ + .data = renderer->triangle_verts, + .size_bytes = renderer->num_triangle_verts * sizeof(vec3)} + }); + + gfx_apply_uniforms(renderer->shader); + gfx_render_geometry(renderer->triangles); + + renderer->num_triangle_verts = 0; + } +} + +void gfx_imm_start(ImmRenderer* renderer) { + assert(renderer); + + ShaderProgram* shader = renderer->shader; + gfx_activate_shader_program(shader); + // TODO: Having to apply manually is annoying. Maybe just apply implicitly + // when the program is activated? + // Note that then we'll need to be able to just gfx_apply_uniforms() in + // flush(). + // gfx_set_culling(renderer->render_backend, false); + gfx_apply_uniforms(shader); + gfx_set_blending(renderer->render_backend, true); + gfx_set_depth_mask(renderer->render_backend, false); + gfx_set_polygon_offset(renderer->render_backend, 0.5f, 0.5f); +} + +void gfx_imm_end(ImmRenderer* renderer) { + assert(renderer); + + imm_renderer_flush(renderer); + + gfx_set_polygon_offset(renderer->render_backend, 0.0f, 0.0f); + gfx_set_depth_mask(renderer->render_backend, true); + gfx_set_blending(renderer->render_backend, false); + // gfx_set_culling(renderer->render_backend, true); + gfx_deactivate_shader_program(renderer->shader); +} + +void gfx_imm_draw_triangles( + ImmRenderer* renderer, const vec3 verts[], size_t num_triangles) { + assert(renderer); + assert(verts); + const size_t new_verts = num_triangles * 3; + assert( + renderer->num_triangle_verts + new_verts < (IMM_MAX_NUM_TRIANGLES * 3)); + + memcpy( + renderer->triangle_verts + renderer->num_triangle_verts, verts, + new_verts * sizeof(vec3)); + + renderer->num_triangle_verts += new_verts; +} + +void gfx_imm_draw_triangle(ImmRenderer* renderer, const vec3 verts[3]) { + gfx_imm_draw_triangles(renderer, verts, 1); +} + +void gfx_imm_draw_aabb(ImmRenderer* renderer, aabb3 box) { + assert(renderer); + + const vec3 verts[8] = { + box.min, // 2 ----- 6 + vec3_make(box.min.x, box.min.y, box.max.z), // / /| + vec3_make(box.min.x, box.max.y, box.min.z), // 3 ----- 7 | + vec3_make(box.min.x, box.max.y, box.max.z), // | | | + vec3_make(box.max.x, box.min.y, box.min.z), // | 0 ----- 4 + vec3_make(box.max.x, box.min.y, box.max.z), // |/ |/ + vec3_make(box.max.x, box.max.y, box.min.z), // 1 ----- 5 + box.max}; + +#define tri(i0, i1, i2) verts[i0], verts[i1], verts[i2] + + // TODO: Use vertex indices in Geometry. + const vec3 tris[36] = {// Front. + tri(1, 5, 7), tri(1, 7, 3), + // Right. + tri(5, 4, 6), tri(5, 6, 7), + // Back. + tri(4, 0, 2), tri(4, 2, 6), + // Left. + tri(0, 1, 3), tri(0, 3, 2), + // Top. + tri(3, 7, 6), tri(3, 6, 2), + // Bottom. + tri(0, 4, 5), tri(0, 5, 1)}; + + gfx_imm_draw_triangles(renderer, tris, 12); +} + +void gfx_imm_set_camera(ImmRenderer* renderer, const Camera* camera) { + assert(renderer); + assert(renderer->shader); + imm_renderer_flush(renderer); + const mat4 modelview = spatial3_inverse_transform(&camera->spatial); + const mat4 mvp = mat4_mul(camera->projection, modelview); + gfx_set_mat4_uniform(renderer->shader, "ViewProjection", &mvp); +} + +void gfx_imm_set_model_matrix(ImmRenderer* renderer, const mat4* model) { + assert(renderer); + assert(model); + imm_renderer_flush(renderer); + gfx_set_mat4_uniform(renderer->shader, "Model", model); +} + +void gfx_imm_set_colour(ImmRenderer* renderer, vec4 colour) { + assert(renderer); + assert(renderer->shader); + imm_renderer_flush(renderer); + gfx_set_vec4_uniform(renderer->shader, "Colour", colour); +} diff --git a/gfx/src/renderer/renderer.c b/gfx/src/renderer/renderer.c index b0bef33..5d88ae6 100644 --- a/gfx/src/renderer/renderer.c +++ b/gfx/src/renderer/renderer.c @@ -18,6 +18,7 @@ #include +// TODO: Move to a header like "constants.h". static const int IRRADIANCE_MAP_WIDTH = 1024; static const int IRRADIANCE_MAP_HEIGHT = 1024; static const int PREFILTERED_ENVIRONMENT_MAP_WIDTH = 128; @@ -29,29 +30,31 @@ bool renderer_make(Renderer* renderer, RenderBackend* render_backend) { assert(renderer); assert(render_backend); + renderer->render_backend = render_backend; + // TODO: Load the IBL stuff lazily. if (!(renderer->ibl = gfx_make_ibl(render_backend))) { - renderer_destroy(renderer, render_backend); + renderer_destroy(renderer); return false; } if (!(renderer->brdf_integration_map = gfx_make_brdf_integration_map( renderer->ibl, render_backend, BRDF_INTEGRATION_MAP_WIDTH, BRDF_INTEGRATION_MAP_HEIGHT))) { - renderer_destroy(renderer, render_backend); + renderer_destroy(renderer); return false; } return true; } -void renderer_destroy(Renderer* renderer, RenderBackend* render_backend) { +void renderer_destroy(Renderer* renderer) { if (!renderer) { return; } - assert(render_backend); + assert(renderer->render_backend); if (renderer->ibl) { - gfx_destroy_ibl(render_backend, &renderer->ibl); + gfx_destroy_ibl(renderer->render_backend, &renderer->ibl); } } @@ -168,6 +171,8 @@ static void draw_recursively( const SceneObject* object = mem_get_object(node->object); assert(object); + // TODO: Here we would frustum-cull the object. + // TODO: Avoid computing matrices like Modelview or MVP if the shader does // not use them. const mat4 model_matrix = mat4_mul(node_transform, object->transform); @@ -189,6 +194,11 @@ static void draw_recursively( } assert(mesh->geometry); assert(mesh->material); + + // TODO: Here we would frustum-cull the mesh. The AABB would have to be + // transformed by the model matrix. Rotation would make the AABB + // relatively large, but still, the culling would be conservative. + // Apply common shader uniforms not captured by materials. ShaderProgram* shader = mesh->shader; gfx_set_mat4_uniform(shader, "ModelMatrix", &model_matrix); @@ -240,14 +250,12 @@ static void draw_recursively( } void gfx_render_scene( - Renderer* renderer, RenderBackend* render_backend, const Scene* scene, - const SceneCamera* camera) { + Renderer* renderer, const Scene* scene, const SceneCamera* camera) { assert(renderer); - assert(render_backend); + assert(scene); + assert(camera); - if (!scene) { - return; - } + RenderBackend* render_backend = renderer->render_backend; const mat4 projection = camera ? camera->camera.projection : mat4_id(); const mat4 camera_rotation = diff --git a/gfx/src/renderer/renderer_impl.h b/gfx/src/renderer/renderer_impl.h index ce8ebe7..833025a 100644 --- a/gfx/src/renderer/renderer_impl.h +++ b/gfx/src/renderer/renderer_impl.h @@ -1,18 +1,52 @@ #pragma once #include - +#include #include +#include +#include + #include +#include + +// Currently, the immediate mode renderer can only draw up to a maximum number +// of primitives per frame. It does not adjust this number dynamically. Keeps +// things simple while the extra complexity is not needed. + +typedef struct Buffer Buffer; +typedef struct Geometry Geometry; +typedef struct ShaderProgram ShaderProgram; typedef struct Renderer { - IBL* ibl; - Texture* brdf_integration_map; + RenderBackend* render_backend; + IBL* ibl; + Texture* brdf_integration_map; } Renderer; +typedef struct ImmRenderer { + RenderBackend* render_backend; + ShaderProgram* shader; + Geometry* triangles; + size_t num_triangle_verts; // Number of triangle verts this frame. + // TODO: wireframe rendering. + struct { + bool wireframe : 1; + } flags; + vec3 triangle_verts[IMM_MAX_NUM_TRIANGLES * 3]; +} ImmRenderer; + /// Create a new renderer. bool renderer_make(Renderer*, RenderBackend*); /// Destroy the renderer. -void renderer_destroy(Renderer*, RenderBackend*); +void renderer_destroy(Renderer*); + +/// Create a new immediate mode renderer. +bool imm_renderer_make(ImmRenderer*, RenderBackend*); + +/// Destroy the immediate mode renderer. +void imm_renderer_destroy(ImmRenderer*); + +/// Flush draw commands. +void imm_renderer_flush(ImmRenderer*); diff --git a/gfx/src/scene/object.c b/gfx/src/scene/object.c index 47d2f25..64bb5a6 100644 --- a/gfx/src/scene/object.c +++ b/gfx/src/scene/object.c @@ -1,5 +1,8 @@ #include "object_impl.h" +#include + +#include "mesh_impl.h" #include "node_impl.h" #include "scene_memory.h" @@ -75,3 +78,32 @@ void gfx_set_object_skeleton(SceneObject* object, const Skeleton* skeleton) { assert(skeleton); object->skeleton = mem_get_skeleton_index(skeleton); } + +// TODO: Could compute just once if we changed the Object API to require an +// object descriptor up front instead of allowing the client to add meshes +// and skeletons dynamically. +aabb3 gfx_calc_object_aabb(const SceneObject* object) { + assert(object); + + bool first = true; + aabb3 box; + + mesh_link_idx ml = object->mesh_link; + while (ml.val) { + const MeshLink* mesh_link = mem_get_mesh_link(ml); + const mesh_idx mi = mesh_link->mesh; + if (mi.val) { + const Mesh* mesh = mem_get_mesh(mi); + const aabb3 mesh_box = gfx_get_geometry_aabb(mesh->geometry); + if (first) { + box = mesh_box; + first = false; + } else { + box = aabb3_sum(box, mesh_box); + } + } + ml = mesh_link->next; + } + + return box; +} diff --git a/gfx/src/scene/scene_memory.c b/gfx/src/scene/scene_memory.c index 62e9401..217c719 100644 --- a/gfx/src/scene/scene_memory.c +++ b/gfx/src/scene/scene_memory.c @@ -182,6 +182,9 @@ void mem_free_skeleton(Skeleton** skeleton) { } // Query by index. +// +// TODO: Check for 0 index and return nullptr? Otherwise this can accidentally +// return a pointer to the dummy objects. Anima* mem_get_anima(anima_idx index) { return mempool_get_block(&mem.animas, index.val); } diff --git a/gfx/src/util/scene.c b/gfx/src/util/scene.c index e8dd6b1..dc55d5a 100644 --- a/gfx/src/util/scene.c +++ b/gfx/src/util/scene.c @@ -373,23 +373,31 @@ static float read_float(const void* data, const cgltf_accessor* accessor) { #define ACCESSOR_FOREACH_VEC(dimensions, accessor, body) \ { \ assert((1 <= dimensions) && (dimensions <= 4)); \ - assert(!(dimensions == 1) || (accessor->type == cgltf_type_scalar)); \ - assert(!(dimensions == 2) || (accessor->type == cgltf_type_vec2)); \ - assert(!(dimensions == 3) || (accessor->type == cgltf_type_vec3)); \ - assert(!(dimensions == 4) || (accessor->type == cgltf_type_vec4)); \ + assert( \ + ((dimensions == 1) && (accessor->type == cgltf_type_scalar)) || \ + ((dimensions == 2) && (accessor->type == cgltf_type_vec2)) || \ + ((dimensions == 3) && (accessor->type == cgltf_type_vec3)) || \ + ((dimensions == 4) && (accessor->type == cgltf_type_vec4))); \ const cgltf_buffer_view* view = accessor->buffer_view; \ const cgltf_buffer* buffer = view->buffer; \ const cgltf_size offset = accessor->offset + view->offset; \ + const uint8_t* bytes = (const uint8_t*)buffer->data + offset; \ + /* Component size in bytes. */ \ const cgltf_size comp_size = get_component_size(accessor->component_type); \ - const uint8_t* bytes = (const uint8_t*)buffer->data + offset; \ + /* Element size in bytes. */ \ + const cgltf_size elem_size = dimensions * comp_size; \ + /* Stride in bytes. If the view stride is 0, then the elements are tightly \ + * packed. */ \ + const cgltf_size stride = view->stride != 0 ? view->stride : elem_size; \ + /* There isn't an accessor stride in the spec, but cgltf still specifies \ + * one. */ \ + assert(accessor->stride == elem_size); \ + /* Accessor data must fit inside the buffer. */ \ assert( \ - (offset + accessor->count * dimensions * comp_size) < buffer->size); \ - /* From the spec: */ \ - /* "Buffer views with other types of data MUST NOT not define */ \ - /* byteStride (unless such layout is explicitly enabled by an */ \ - /* extension)."*/ \ - assert(view->stride == 0); \ - assert(accessor->stride == dimensions * comp_size); \ + (offset + (accessor->count * elem_size) + \ + ((accessor->count - 1) * view->stride)) <= buffer->size); \ + /* Accessor data must fit inside the view. */ \ + assert(accessor->count * accessor->stride <= view->size); \ cgltf_float x = 0, y = 0, z = 0, w = 0; \ /* Silence unused variable warnings. */ \ (void)y; \ @@ -401,58 +409,63 @@ static float read_float(const void* data, const cgltf_accessor* accessor) { types, we take the performance hit and perform checks and conversions \ inside the loop for simplicity. */ \ if (accessor->component_type == cgltf_component_type_r_32f) { \ - const cgltf_float* floats = (const cgltf_float*)bytes; \ switch (dimensions) { \ case 1: \ assert(accessor->type == cgltf_type_scalar); \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - x = *floats++; \ + for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + x = *floats; \ body; \ } \ break; \ case 2: \ assert(accessor->type == cgltf_type_vec2); \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - x = *floats++; \ - y = *floats++; \ + for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + x = *floats++; \ + y = *floats; \ body; \ } \ break; \ case 3: \ assert(accessor->type == cgltf_type_vec3); \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - x = *floats++; \ - y = *floats++; \ - z = *floats++; \ + for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + x = *floats++; \ + y = *floats++; \ + z = *floats; \ body; \ } \ break; \ case 4: \ assert(accessor->type == cgltf_type_vec4); \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - x = *floats++; \ - y = *floats++; \ - z = *floats++; \ - w = *floats++; \ + for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ + const cgltf_float* floats = (const cgltf_float*)bytes; \ + x = *floats++; \ + y = *floats++; \ + z = *floats++; \ + w = *floats; \ body; \ } \ break; \ } \ } else { \ - for (cgltf_size i = 0; i < accessor->count; ++i) { \ - x = read_float(bytes, accessor); \ - bytes += accessor->stride; \ + for (cgltf_size i = 0; i < accessor->count; ++i, bytes += stride) { \ + const uint8_t* component = bytes; \ + \ + x = read_float(component, accessor); \ + component += comp_size; \ if (dimensions > 1) { \ - y = read_float(bytes, accessor); \ - bytes += accessor->stride; \ + y = read_float(component, accessor); \ + component += comp_size; \ } \ if (dimensions > 2) { \ - z = read_float(bytes, accessor); \ - bytes += accessor->stride; \ + z = read_float(component, accessor); \ + component += comp_size; \ } \ if (dimensions > 3) { \ - w = read_float(bytes, accessor); \ - bytes += accessor->stride; \ + w = read_float(component, accessor); \ + component += comp_size; \ } \ body; \ } \ @@ -538,10 +551,10 @@ static bool load_buffers( assert(buffer->data); buffers[i] = gfx_make_buffer( render_backend, &(BufferDesc){ - .usage = BufferStatic, - .type = BufferUntyped, - .data = buffer->data, - .count = buffer->size}); + .usage = BufferStatic, + .type = BufferUntyped, + .data.data = buffer->data, + .data.count = buffer->size}); if (!buffers[i]) { return false; } @@ -564,10 +577,10 @@ static bool load_tangent_buffers( assert(buffer->data); tangent_buffers[i] = gfx_make_buffer( render_backend, &(BufferDesc){ - .usage = BufferStatic, - .type = BufferUntyped, - .data = buffer->data, - .count = buffer->size_bytes}); + .usage = BufferStatic, + .type = BufferUntyped, + .data.data = buffer->data, + .data.count = buffer->size_bytes}); if (!tangent_buffers[i]) { return false; } @@ -843,6 +856,26 @@ static Material* make_default_material() { return gfx_make_material(&desc); } +/// Compute the bounding box of the vertices pointed to by the accessor. +/// 'dim' is the dimension of the vertices (2D or 3D). +aabb3 compute_aabb(const cgltf_accessor* accessor, int dim) { + aabb3 box = {0}; + if (accessor->has_min && accessor->has_max) { + box = aabb3_make( + vec3_from_array(accessor->min), vec3_from_array(accessor->max)); + } else { + ACCESSOR_FOREACH_VEC(dim, accessor, { + const vec3 p = vec3_make(x, y, z); + if (i == 0) { + box = aabb3_make(p, p); + } else { + box = aabb3_add(box, p); + } + }); + } + return box; +} + /// Load all meshes from the glTF scene. static bool load_meshes( const cgltf_data* data, Gfx* gfx, Buffer** buffers, @@ -910,7 +943,8 @@ static bool load_meshes( } GeometryDesc geometry_desc = { - .type = from_gltf_primitive_type(prim->type)}; + .type = from_gltf_primitive_type(prim->type), + .buffer_usage = BufferStatic}; // Vertex indices. if (prim->indices) { @@ -969,11 +1003,13 @@ static bool load_meshes( switch (accessor->type) { case cgltf_type_vec2: assert(geometry_desc.positions3d.buffer == 0); - buffer_view_2d = &geometry_desc.positions2d; + buffer_view_2d = &geometry_desc.positions2d; + geometry_desc.aabb = compute_aabb(accessor, 2); break; case cgltf_type_vec3: assert(geometry_desc.positions2d.buffer == 0); - buffer_view_3d = &geometry_desc.positions3d; + buffer_view_3d = &geometry_desc.positions3d; + geometry_desc.aabb = compute_aabb(accessor, 3); break; default: LOGE( @@ -1478,6 +1514,8 @@ static SceneNode* load_scene( scene_nodes[i] = gfx_make_node(); } + // TODO: If the scene does not have animations, then a top-level LogicalNode + // would make more sense than an AnimaNode. anima_node = gfx_make_node(); load_skins(data, buffers, scene_nodes, anima_desc->skeletons); load_animations(data, scene_nodes, anima_desc->animations); diff --git a/gfx/src/util/shader.c b/gfx/src/util/shader.c index fdfb1d3..ed81f79 100644 --- a/gfx/src/util/shader.c +++ b/gfx/src/util/shader.c @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include #include @@ -94,6 +96,11 @@ ShaderProgram* gfx_make_cook_torrance_shader_perm( num_defines); } +ShaderProgram* gfx_make_immediate_mode_shader(RenderBackend* render_backend) { + return make_shader_program( + render_backend, immediate_mode_vert, immediate_mode_frag, 0, 0); +} + ShaderProgram* gfx_make_irradiance_map_shader(RenderBackend* render_backend) { return make_shader_program( render_backend, cubemap_filtering_vert, irradiance_map_frag, 0, 0); diff --git a/gltfview/src/game.c b/gltfview/src/game.c index f822b08..662272c 100644 --- a/gltfview/src/game.c +++ b/gltfview/src/game.c @@ -2,6 +2,7 @@ #include #include +#include #include #include #include @@ -32,8 +33,10 @@ static const char* FLIGHT_HELMET = "/assets/glTF-Sample-Models/2.0/FlightHelmet/glTF/FlightHelmet.gltf"; static const char* DAMAGED_HELMET = "/assets/glTF-Sample-Models/2.0/DamagedHelmet/glTF/DamagedHelmet.gltf"; +static const char* GIRL = + "/home/jeanne/Nextcloud/assets/models/girl/girl-with-ground.gltf"; -#define DEFAULT_SCENE_FILE DAMAGED_HELMET +#define DEFAULT_SCENE_FILE GIRL static const char* CLOUDS1_TEXTURE = "/assets/skybox/clouds1/clouds1_west.bmp"; @@ -189,6 +192,26 @@ static bool load_texture_debugger_scene(Game* game) { return true; } +/// Render the bounding boxes of all scene objects. +static void render_bounding_boxes(ImmRenderer* imm, const SceneNode* node) { + if (gfx_get_node_type(node) == ObjectNode) { + // TODO: Look at the scene log. The JointNodes are detached from the + // ObjectNodes. This is why the boxes are not being transformed as expected + // here. Anima needs to animate boxes? Use OOBB in addition to AABB? + const mat4 model = gfx_get_node_global_transform(node); + const SceneObject* obj = gfx_get_node_object(node); + const aabb3 box = gfx_calc_object_aabb(obj); + gfx_imm_set_model_matrix(imm, &model); + gfx_imm_draw_aabb(imm, box); + } + + // Render children's boxes. + for (NodeIter it = gfx_get_node_child(node); it; + it = gfx_get_next_child(it)) { + render_bounding_boxes(imm, gfx_get_iter_node(it)); + } +} + bool game_new(Game* game, int argc, const char** argv) { // TODO: getopt() to implement proper argument parsing. const char* scene_filepath = argc > 1 ? argv[1] : DEFAULT_SCENE_FILE; @@ -239,6 +262,8 @@ void game_end(Game* game) { gfx_destroy(&game->gfx); } void game_update(Game* game, double t, double dt) { // LOGD("Tick"); + // TODO: Animation should be handled by Gfx instead. Descend through the scene + // looking for animas and animate them. gfx_animate(t). Anima* anima = gfx_get_node_anima(game->root_node); gfx_update_animation(anima, t); @@ -263,8 +288,18 @@ void game_update(Game* game, double t, double dt) { } void game_render(const Game* game) { - gfx_render_scene( - game->renderer, game->render_backend, game->scene, game->camera); + gfx_render_scene(game->renderer, game->scene, game->camera); + + ImmRenderer* imm = gfx_get_imm_renderer(game->gfx); + assert(imm); + gfx_imm_start(imm); + gfx_imm_set_camera(imm, gfx_get_camera_camera(game->camera)); + gfx_imm_set_colour(imm, vec4_make(0.2, 0.2, 1.0, 0.3)); + // DEBUG + // const aabb3 box = aabb3_make(vec3_make(0, 0, 0), vec3_make(1, 1, 1)); + // gfx_imm_draw_aabb(imm, box); + render_bounding_boxes(imm, gfx_get_scene_root(game->scene)); + gfx_imm_end(imm); } void game_set_viewport(Game* game, int width, int height) { -- cgit v1.2.3