diff --git a/CMakeLists.txt b/CMakeLists.txt index ed73237..f6748e3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -106,10 +106,12 @@ set(TWN_NONOPT_SOURCE_FILES src/twn_textures.c src/twn_textures_c.h src/rendering/twn_draw.c src/rendering/twn_draw_c.h + src/rendering/twn_quads.c src/rendering/twn_sprites.c src/rendering/twn_rects.c src/rendering/twn_text.c src/rendering/twn_triangles.c + src/rendering/twn_billboards.c src/rendering/twn_circles.c src/rendering/twn_skybox.c src/rendering/twn_fog.c) diff --git a/apps/demos/scenery/scenes/ingame.c b/apps/demos/scenery/scenes/ingame.c index 54412b6..cd23bf4 100644 --- a/apps/demos/scenery/scenes/ingame.c +++ b/apps/demos/scenery/scenes/ingame.c @@ -158,6 +158,8 @@ static void draw_terrain(SceneIngame *scn) { (Vec2){ 128, 0 }, (Vec2){ 0, 0 }, (Vec2){ 0, 128 }); + + draw_billboard("/assets/grasses/10.png", (Vec3){ (float)x, d1 + 0.1f, (float)y }, (Vec2){0.3f, 0.3f}, (Color){255, 255, 255, 255}, true); } } } @@ -200,7 +202,7 @@ static void ingame_tick(State *state) { generate_terrain(scn); draw_terrain(scn); - draw_skybox("/assets/miramar/miramar_*.tga"); + // draw_skybox("/assets/miramar/miramar_*.tga"); draw_fog(0.9f, 1.0f, 0.05f, (Color){ 140, 147, 160, 255 }); } diff --git a/common-data/assets/grasses/10.png b/common-data/assets/grasses/10.png new file mode 100644 index 0000000..792293c --- /dev/null +++ b/common-data/assets/grasses/10.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3a7f397ff3073c7b49a624238526d2eb1124d128af5c2fbd8204b8edd19c6d23 +size 12102 diff --git a/include/twn_draw.h b/include/twn_draw.h index 319424c..3143a20 100644 --- a/include/twn_draw.h +++ b/include/twn_draw.h @@ -54,6 +54,7 @@ TWN_API void draw_triangle(char const *texture, // TODO: decide whether it's needed to begin with? // intended usage for it is baked lighting, i would think. +// TODO: instead add optional color parameters to 'draw_triangle' /* pushes a colored textured 3d triangle onto the render queue */ // void unfurl_colored_triangle(const char *path, // Vec3 v0, @@ -66,7 +67,7 @@ TWN_API void draw_triangle(char const *texture, // Color c1, // Color c2); -TWN_API void draw_billboard(const char *path, +TWN_API void draw_billboard(const char *texture, Vec3 position, Vec2 size, Color color, /* optional, default: all 255 */ diff --git a/include/twn_vec.h b/include/twn_vec.h index 83e626a..2158954 100644 --- a/include/twn_vec.h +++ b/include/twn_vec.h @@ -9,6 +9,26 @@ #include +static inline Vec2 vec2_add(Vec2 a, Vec2 b) { + return (Vec2) { a.x + b.x, a.y + b.y }; +} + +static inline Vec2 vec2_sub(Vec2 a, Vec2 b) { + return (Vec2) { a.x - b.x, a.y - b.y }; +} + +static inline Vec2 vec2_div(Vec2 a, Vec2 b) { + return (Vec2) { a.x / b.x, a.y / b.y }; +} + +static inline Vec2 vec2_mul(Vec2 a, Vec2 b) { + return (Vec2) { a.x * b.x, a.y * b.y }; +} + +static inline Vec2 vec2_scale(Vec2 a, float s) { + return (Vec2) { a.x * s, a.y * s }; +} + static inline Vec3 vec3_add(Vec3 a, Vec3 b) { return (Vec3) { a.x + b.x, a.y + b.y, a.z + b.z }; } @@ -17,12 +37,12 @@ static inline Vec3 vec3_sub(Vec3 a, Vec3 b) { return (Vec3) { a.x - b.x, a.y - b.y, a.z - b.z }; } -static inline Vec2 vec2_div(Vec2 a, Vec2 b) { - return (Vec2) { a.x / b.x, a.y / b.y }; +static inline Vec3 vec3_div(Vec3 a, Vec3 b) { + return (Vec3) { a.x / b.x, a.y / b.y, a.z / b.z }; } -static inline Vec2 vec2_scale(Vec2 a, float s) { - return (Vec2) { a.x * s, a.y * s }; +static inline Vec3 vec3_mul(Vec3 a, Vec3 b) { + return (Vec3) { a.x * b.x, a.y * b.y, a.z * b.z }; } static inline Vec3 vec3_scale(Vec3 a, float s) { @@ -83,15 +103,23 @@ static inline Vec3 vec3_rotate(Vec3 v, float angle, Vec3 axis) { )(p_any_vec2)) #define m_vec_add(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \ + Vec2: vec2_add, \ Vec3: vec3_add \ )(p_any_vec0, p_any_vec1)) #define m_vec_sub(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \ + Vec2: vec2_sub, \ Vec3: vec3_sub \ )(p_any_vec0, p_any_vec1)) #define m_vec_div(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \ - Vec2: vec2_div \ + Vec2: vec2_div, \ + Vec3: vec3_div \ + )(p_any_vec0, p_any_vec1)) + +#define m_vec_mul(p_any_vec0, p_any_vec1) (_Generic((p_any_vec0), \ + Vec2: vec2_mul, \ + Vec3: vec3_mul \ )(p_any_vec0, p_any_vec1)) #define m_vec_scale(p_any_vec, p_any_scalar) (_Generic((p_any_vec), \ diff --git a/src/rendering/twn_billboards.c b/src/rendering/twn_billboards.c new file mode 100644 index 0000000..00d5ce6 --- /dev/null +++ b/src/rendering/twn_billboards.c @@ -0,0 +1,168 @@ +#include "twn_draw.h" +#include "twn_draw_c.h" +#include "twn_engine_context_c.h" +#include "twn_textures_c.h" +#include "twn_types.h" +#include "twn_vec.h" + +#include + + +void draw_billboard(const char *texture, + Vec3 position, + Vec2 size, + Color color, + bool cylindrical) +{ + // TODO: order drawing by atlas id as well, so that texture rebinding is not as common + const TextureKey texture_key = textures_get_key(&ctx.texture_cache, texture); + + struct MeshBatchItem *batch_p = hmgetp_null(ctx.billboard_batches, texture_key); + if (!batch_p) { + struct MeshBatch item = {0}; + hmput(ctx.billboard_batches, texture_key, item); + batch_p = &ctx.billboard_batches[hmlenu(ctx.billboard_batches) - 1]; /* TODO: can last index be used? */ + } + + struct SpaceBillboard billboard = { + .color = color, + .cylindrical = cylindrical, + .position = position, + .size = size, + }; + + struct SpaceBillboard *billboards = (struct SpaceBillboard *)(void *)batch_p->value.primitives; + + arrpush(billboards, billboard); + batch_p->value.primitives = (uint8_t *)billboards; +} + + +/* reused for all billboards */ +static Vec3 right_plus_up; +static Vec3 right_minus_up; +static Vec3 right_plus_up_cylindrical; +static Vec3 right_minus_up_cylindrical; + +/* precalculate (right + up) and (right - up) that are used with all batches */ +static void calculate_intermediates(void) { + Vec3 const right = { camera_look_at_matrix.row[0].x, camera_look_at_matrix.row[1].x, camera_look_at_matrix.row[2].x }; + Vec3 const up = { camera_look_at_matrix.row[0].y, camera_look_at_matrix.row[1].y, camera_look_at_matrix.row[2].y }; + + right_plus_up = m_vec_add(right, up); + right_minus_up = m_vec_sub(right, up); + + Vec3 const up_cylindrical = { 0, 1, 0 }; + + right_plus_up_cylindrical = m_vec_add(right, up_cylindrical); + right_minus_up_cylindrical = m_vec_sub(right, up_cylindrical); +} + + +/* http://www.lighthouse3d.com/opengl/billboarding/index.php?billCheat2 */ +void finally_draw_billboard_batch(struct MeshBatch const *batch, + TextureKey texture_key) +{ + const size_t primitives_len = arrlenu(batch->primitives); + + /* nothing to do */ + if (primitives_len == 0) + return; + + /* TODO: only do it once per frame */ + calculate_intermediates(); + + const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key); + const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key); + + const float wr = srcrect.w / dims.w; + const float hr = srcrect.h / dims.h; + const float xr = srcrect.x / dims.w; + const float yr = srcrect.y / dims.h; + + const Vec2 uv0 = { xr + 1 * wr, yr + 0 * hr }; + const Vec2 uv1 = { xr + 1 * wr, yr + 1 * hr }; + const Vec2 uv2 = { xr + 0 * wr, yr + 1 * hr }; + const Vec2 uv3 = { xr + 0 * wr, yr + 0 * hr }; + + /* emit vertex data */ + VertexBuffer const buffer = get_scratch_vertex_array(); + VertexBufferBuilder builder = build_vertex_buffer(buffer, sizeof (ElementIndexedBillboard) * primitives_len); + + for (size_t i = 0; i < primitives_len; ++i) { + struct SpaceBillboard const billboard = ((SpaceBillboard *)(void *)batch->primitives)[i]; + + /* a = (right + up) * size, b = (right - up) * size*/ + Vec3 a, b; + if (billboard.cylindrical) { + a = vec3_mul(right_plus_up_cylindrical, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x})); + b = vec3_mul(right_minus_up_cylindrical, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x})); + } else { + a = vec3_mul(right_plus_up, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x})); + b = vec3_mul(right_minus_up, ((Vec3){billboard.size.x, billboard.size.y, billboard.size.x})); + } + + struct ElementIndexedBillboard const payload = { + /* TODO: use the flat shading to not set two of the colors */ + .c0 = billboard.color, + .c1 = billboard.color, + .c2 = billboard.color, + .c3 = billboard.color, + + .uv0 = uv0, + .uv1 = uv1, + .uv2 = uv2, + .uv3 = uv3, + + .v0 = vec3_sub(billboard.position, b), + .v1 = vec3_sub(billboard.position, a), + .v2 = vec3_add(billboard.position, b), + .v3 = vec3_add(billboard.position, a), + }; + + push_to_vertex_buffer_builder(&builder, &payload, sizeof (payload)); + } + + /* commit to drawing */ + DeferredCommandDraw command = {0}; + + command.vertices = (AttributeArrayPointer) { + .arity = 3, + .type = GL_FLOAT, + .stride = offsetof(ElementIndexedBillboard, v1), + .offset = offsetof(ElementIndexedBillboard, v0), + .buffer = buffer + }; + + command.texcoords = (AttributeArrayPointer) { + .arity = 2, + .type = GL_FLOAT, + .stride = offsetof(ElementIndexedBillboard, v1), + .offset = offsetof(ElementIndexedBillboard, uv0), + .buffer = buffer + }; + + command.colors = (AttributeArrayPointer) { + .arity = 4, + .type = GL_UNSIGNED_BYTE, + .stride = offsetof(ElementIndexedBillboard, v1), + .offset = offsetof(ElementIndexedBillboard, c0), + .buffer = buffer + }; + + command.textured = true; + command.texture_key = texture_key; + + command.element_buffer = get_quad_element_buffer(); + command.element_count = 6 * (GLsizei)primitives_len; + command.range_end = 6 * (GLsizei)primitives_len; + + use_texture_mode(textures_get_mode(&ctx.texture_cache, texture_key)); + + DeferredCommand final_command = { + .type = DEFERRED_COMMAND_TYPE_DRAW, + .draw = command + }; + + arrpush(deferred_commands, final_command); +} diff --git a/src/rendering/twn_circles.c b/src/rendering/twn_circles.c index 5b99d84..9d9d13f 100644 --- a/src/rendering/twn_circles.c +++ b/src/rendering/twn_circles.c @@ -1,6 +1,7 @@ #include "twn_engine_context_c.h" #include "twn_draw_c.h" #include "twn_draw.h" +#include "twn_util_c.h" #include #include @@ -66,3 +67,57 @@ void create_circle_geometry(Vec2 position, vertices[num_vertices - 1].y += position.y; } } + + +void render_circle(const CirclePrimitive *circle) { + static Vec2 vertices[CIRCLE_VERTICES_MAX]; + static int prev_num_vertices = 0; + static Vec2 prev_position = {0}; + + int const num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX); + + if (prev_num_vertices != num_vertices) { + create_circle_geometry(circle->position, + circle->radius, + num_vertices, + vertices); + prev_num_vertices = num_vertices; + prev_position = circle->position; + } else { + /* reuse the data, but offset it by difference with previously generated position */ + /* no evil cos sin ops this way, if radius is shared in sequential calls */ + Vec2 const d = { prev_position.x - circle->position.x, prev_position.y - circle->position.y }; + for (int i = 0; i < num_vertices; ++i) + vertices[i] = (Vec2){ vertices[i].x - d.x, vertices[i].y - d.y }; + prev_position = circle->position; + } + + VertexBuffer buffer = get_scratch_vertex_array(); + specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices); + + DeferredCommandDraw command = {0}; + + command.vertices = (AttributeArrayPointer) { + .arity = 2, + .type = GL_FLOAT, + .stride = sizeof (Vec2), + .offset = 0, + .buffer = buffer + }; + + command.constant_colored = true; + command.color = circle->color; + + command.element_buffer = get_circle_element_buffer(); + command.element_count = (num_vertices - 2) * 3; + command.range_end = (num_vertices - 2) * 3; + + use_texture_mode(circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY); + + DeferredCommand final_command = { + .type = DEFERRED_COMMAND_TYPE_DRAW, + .draw = command + }; + + arrpush(deferred_commands, final_command); +} diff --git a/src/rendering/twn_draw.c b/src/rendering/twn_draw.c index b1695b1..0a2dd73 100644 --- a/src/rendering/twn_draw.c +++ b/src/rendering/twn_draw.c @@ -29,8 +29,12 @@ void render_queue_clear(void) { /* and start overwriting the existing data */ arrsetlen(ctx.render_queue_2d, 0); + /* TODO: free memory if it isn't used for a while */ for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) arrsetlen(ctx.uncolored_mesh_batches[i].value.primitives, 0); + + for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i) + arrsetlen(ctx.billboard_batches[i].value.primitives, 0); } @@ -332,18 +336,23 @@ static void render_2d(void) { static void render_space(void) { /* nothing to do, abort */ /* as space pipeline isn't used we can have fewer changes and initialization costs */ - if (hmlenu(ctx.uncolored_mesh_batches) == 0) - return; + if (hmlenu(ctx.uncolored_mesh_batches) != 0 || hmlenu(ctx.billboard_batches) != 0) { + use_space_pipeline(); + apply_fog(); - use_space_pipeline(); - apply_fog(); + for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) { + finally_draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value, + ctx.uncolored_mesh_batches[i].key); + } - for (size_t i = 0; i < hmlenu(ctx.uncolored_mesh_batches); ++i) { - draw_uncolored_space_traingle_batch(&ctx.uncolored_mesh_batches[i].value, - ctx.uncolored_mesh_batches[i].key); + for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i) { + finally_draw_billboard_batch(&ctx.billboard_batches[i].value, ctx.billboard_batches[i].key); + } + + pop_fog(); } - pop_fog(); + render_skybox(); /* after everything else, as to use depth buffer for early z rejection */ } @@ -357,7 +366,6 @@ void render(void) { start_render_frame(); { render_space(); - render_skybox(); /* after space, as to use depth buffer for early z rejection */ render_2d(); } end_render_frame(); } @@ -370,6 +378,7 @@ void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction) { .target = direction, .up = up, }; + camera_projection_matrix = camera_perspective(&camera); camera_look_at_matrix = camera_look_at(&camera); } @@ -378,9 +387,11 @@ void draw_camera(Vec3 position, float fov, Vec3 up, Vec3 direction) { /* TODO: https://stackoverflow.com/questions/62493770/how-to-add-roll-in-camera-class */ DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position, float fov, float roll, float pitch, float yaw) { (void)roll; + float yawc, yaws, pitchc, pitchs; sincosf(yaw, &yaws, &yawc); sincosf(pitch, &pitchs, &pitchc); + Camera const camera = { .fov = fov, .pos = position, @@ -391,6 +402,7 @@ DrawCameraFromPrincipalAxesResult draw_camera_from_principal_axes(Vec3 position, })), .up = (Vec3){0, 1, 0}, }; + camera_projection_matrix = camera_perspective(&camera); camera_look_at_matrix = camera_look_at(&camera); @@ -520,364 +532,3 @@ void issue_deferred_draw_commands(void) { } } } - - -void render_circle(const CirclePrimitive *circle) { - static Vec2 vertices[CIRCLE_VERTICES_MAX]; - static int prev_num_vertices = 0; - static Vec2 prev_position = {0}; - - int const num_vertices = MIN((int)circle->radius, CIRCLE_VERTICES_MAX); - - if (prev_num_vertices != num_vertices) { - create_circle_geometry(circle->position, - circle->radius, - num_vertices, - vertices); - prev_num_vertices = num_vertices; - prev_position = circle->position; - } else { - /* reuse the data, but offset it by difference with previously generated position */ - /* no evil cos sin ops this way, if radius is shared in sequential calls */ - Vec2 const d = { prev_position.x - circle->position.x, prev_position.y - circle->position.y }; - for (int i = 0; i < num_vertices; ++i) - vertices[i] = (Vec2){ vertices[i].x - d.x, vertices[i].y - d.y }; - prev_position = circle->position; - } - - VertexBuffer buffer = get_scratch_vertex_array(); - specify_vertex_buffer(buffer, vertices, sizeof (Vec2) * num_vertices); - - DeferredCommandDraw command = {0}; - - command.vertices = (AttributeArrayPointer) { - .arity = 2, - .type = GL_FLOAT, - .stride = sizeof (Vec2), - .offset = 0, - .buffer = buffer - }; - - command.constant_colored = true; - command.color = circle->color; - - command.element_buffer = get_circle_element_buffer(); - command.element_count = (num_vertices - 2) * 3; - command.range_end = (num_vertices - 2) * 3; - - use_texture_mode(circle->color.a == 255 ? TEXTURE_MODE_OPAQUE : TEXTURE_MODE_GHOSTLY); - - DeferredCommand final_command = { - .type = DEFERRED_COMMAND_TYPE_DRAW, - .draw = command - }; - - arrpush(deferred_commands, final_command); -} - - -void finally_render_quads(const Primitive2D primitives[], - const struct QuadBatch batch, - const VertexBuffer buffer) -{ - DeferredCommandDraw command = {0}; - - GLsizei off = 0, voff = 0, uvoff = 0, coff = 0; - - if (!batch.constant_colored && batch.textured) { - off = offsetof(ElementIndexedQuad, v1); - voff = offsetof(ElementIndexedQuad, v0); - uvoff = offsetof(ElementIndexedQuad, uv0); - coff = offsetof(ElementIndexedQuad, c0); - } else if (batch.constant_colored && batch.textured) { - off = offsetof(ElementIndexedQuadWithoutColor, v1); - voff = offsetof(ElementIndexedQuadWithoutColor, v0); - uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0); - } else if (!batch.constant_colored && !batch.textured) { - off = offsetof(ElementIndexedQuadWithoutTexture, v1); - voff = offsetof(ElementIndexedQuadWithoutTexture, v0); - coff = offsetof(ElementIndexedQuad, c0); - } else if (batch.constant_colored && !batch.textured) { - off = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v1); - voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0); - } - - command.vertices = (AttributeArrayPointer) { - .arity = 2, - .type = GL_FLOAT, - .stride = off, - .offset = voff, - .buffer = buffer - }; - - if (batch.textured) - command.texcoords = (AttributeArrayPointer) { - .arity = 2, - .type = GL_FLOAT, - .stride = off, - .offset = uvoff, - .buffer = buffer - }; - - if (!batch.constant_colored) { - command.colors = (AttributeArrayPointer) { - .arity = 4, - .type = GL_UNSIGNED_BYTE, - .stride = off, - .offset = coff, - .buffer = buffer - }; - } else { - command.constant_colored = true; - command.color = primitives[0].sprite.color; - } - - if (batch.textured) { - command.textured = true; - command.texture_key = batch.texture_key; - command.texture_repeat = batch.repeat; - } - - command.element_buffer = get_quad_element_buffer(); - command.element_count = 6 * (GLsizei)batch.size; - command.range_end = 6 * (GLsizei)batch.size; - - use_texture_mode(batch.mode); - - DeferredCommand final_command = { - .type = DEFERRED_COMMAND_TYPE_DRAW, - .draw = command - }; - - arrpush(deferred_commands, final_command); -} - - -size_t get_quad_payload_size(struct QuadBatch batch) { - if (batch.constant_colored && batch.textured) - return sizeof (ElementIndexedQuadWithoutColor); - else if (!batch.constant_colored && batch.textured) - return sizeof (ElementIndexedQuad); - else if (batch.constant_colored && !batch.textured) - return sizeof (ElementIndexedQuadWithoutColorWithoutTexture); - else if (!batch.constant_colored && !batch.textured) - return sizeof (ElementIndexedQuadWithoutTexture); - - SDL_assert(false); - return 0; -} - - -bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch, - VertexBufferBuilder *builder, - Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3, - Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3, - Color color) -{ - if (!batch.constant_colored && batch.textured) { - ElementIndexedQuad const buffer_element = { - .v0 = v0, - .v1 = v1, - .v2 = v2, - .v3 = v3, - - .uv0 = uv0, - .uv1 = uv1, - .uv2 = uv2, - .uv3 = uv3, - - /* equal for all (flat shaded) */ - .c0 = color, - // .c1 = color, - .c2 = color, - // .c3 = color, - }; - - return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); - - } else if (batch.constant_colored && batch.textured) { - ElementIndexedQuadWithoutColor const buffer_element = { - .v0 = v0, - .v1 = v1, - .v2 = v2, - .v3 = v3, - - .uv0 = uv0, - .uv1 = uv1, - .uv2 = uv2, - .uv3 = uv3, - }; - - return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); - - } else if (!batch.constant_colored && !batch.textured) { - ElementIndexedQuadWithoutTexture const buffer_element = { - .v0 = v0, - .v1 = v1, - .v2 = v2, - .v3 = v3, - - /* equal for all (flat shaded) */ - .c0 = color, - // .c1 = color, - .c2 = color, - // .c3 = color, - }; - - return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); - - } else if (batch.constant_colored && !batch.textured) { - ElementIndexedQuadWithoutColorWithoutTexture const buffer_element = { - .v0 = v0, - .v1 = v1, - .v2 = v2, - .v3 = v3, - }; - - return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); - } - - SDL_assert(false); - return false; -} - - -void finally_draw_uncolored_space_traingle_batch(const MeshBatch *batch, - const TextureKey texture_key, - const VertexBuffer buffer) -{ - const size_t primitives_len = arrlenu(batch->primitives); - - /* nothing to do */ - if (primitives_len == 0) - return; - - const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key); - const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key); - - const float wr = srcrect.w / dims.w; - const float hr = srcrect.h / dims.h; - const float xr = srcrect.x / dims.w; - const float yr = srcrect.y / dims.h; - - /* update pixel-based uvs to correspond with texture atlases */ - for (size_t i = 0; i < primitives_len; ++i) { - UncoloredSpaceTriangle *payload = - &((UncoloredSpaceTriangle *)(void *)batch->primitives)[i]; - - payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr; - payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr; - payload->uv1.x = xr + ((float)payload->uv1.x / srcrect.w) * wr; - payload->uv1.y = yr + ((float)payload->uv1.y / srcrect.h) * hr; - payload->uv2.x = xr + ((float)payload->uv2.x / srcrect.w) * wr; - payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr; - } - - specify_vertex_buffer(buffer, batch->primitives, primitives_len * sizeof (UncoloredSpaceTriangle)); - - DeferredCommandDraw command = {0}; - - command.vertices = (AttributeArrayPointer) { - .arity = 3, - .type = GL_FLOAT, - .stride = offsetof(UncoloredSpaceTriangle, v1), - .offset = offsetof(UncoloredSpaceTriangle, v0), - .buffer = buffer - }; - - command.texcoords = (AttributeArrayPointer) { - .arity = 2, - .type = GL_FLOAT, - .stride = offsetof(UncoloredSpaceTriangle, v1), - .offset = offsetof(UncoloredSpaceTriangle, uv0), - .buffer = buffer - }; - - command.textured = true; - command.texture_key = texture_key; - - command.primitive_count = (GLsizei)(3 * primitives_len); - - DeferredCommand final_command = { - .type = DEFERRED_COMMAND_TYPE_DRAW, - .draw = command - }; - - arrpush(deferred_commands, final_command); -} - - - -bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data, - VertexBufferBuilder *builder, - stbtt_aligned_quad quad) -{ - (void)font_data; - - ElementIndexedQuadWithoutColor buffer_element = { - .v0 = (Vec2){ quad.x0, quad.y0 }, - .v1 = (Vec2){ quad.x1, quad.y0 }, - .v2 = (Vec2){ quad.x1, quad.y1 }, - .v3 = (Vec2){ quad.x0, quad.y1 }, - - .uv0 = (Vec2){ quad.s0, quad.t0 }, - .uv1 = (Vec2){ quad.s1, quad.t0 }, - .uv2 = (Vec2){ quad.s1, quad.t1 }, - .uv3 = (Vec2){ quad.s0, quad.t1 }, - }; - - return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); -} - - -void finally_draw_text(FontData const *font_data, - size_t len, - Color color, - VertexBuffer buffer) -{ - DeferredCommandDraw command = {0}; - - command.vertices = (AttributeArrayPointer) { - .arity = 2, - .type = GL_FLOAT, - .stride = offsetof(ElementIndexedQuadWithoutColor, v1), - .offset = offsetof(ElementIndexedQuadWithoutColor, v0), - .buffer = buffer - }; - - command.texcoords = (AttributeArrayPointer) { - .arity = 2, - .type = GL_FLOAT, - .stride = offsetof(ElementIndexedQuadWithoutColor, v1), - .offset = offsetof(ElementIndexedQuadWithoutColor, uv0), - .buffer = buffer - }; - - command.constant_colored = true; - command.color = color; - - command.gpu_texture = font_data->texture; - command.uses_gpu_key = true; - command.textured = true; - - command.element_buffer = get_quad_element_buffer(); - command.element_count = 6 * (GLsizei)len; - command.range_end = 6 * (GLsizei)len; - - use_texture_mode(TEXTURE_MODE_GHOSTLY); - - DeferredCommand final_command = { - .type = DEFERRED_COMMAND_TYPE_DRAW, - .draw = command - }; - - arrpush(deferred_commands, final_command); - - /* TODO: why doesn't it get restored if not placed here? */ - // glDepthMask(GL_TRUE); -} - - -size_t get_text_payload_size(void) { - return sizeof (ElementIndexedQuadWithoutColor); -} diff --git a/src/rendering/twn_draw_c.h b/src/rendering/twn_draw_c.h index a37dbae..62860f2 100644 --- a/src/rendering/twn_draw_c.h +++ b/src/rendering/twn_draw_c.h @@ -55,16 +55,16 @@ typedef struct RectPrimitive { } RectPrimitive; typedef struct CirclePrimitive { + Vec2 position; float radius; Color color; - Vec2 position; } CirclePrimitive; typedef struct TextPrimitive { - Color color; Vec2 position; char *text; const char *font; + Color color; int height_px; } TextPrimitive; @@ -76,7 +76,7 @@ typedef enum Primitive2DType { } Primitive2DType; typedef struct Primitive2D { - Primitive2DType type; + Primitive2DType type; /* TODO: separate to structure of arrays for more efficient memory usage? */ union { SpritePrimitive sprite; @@ -97,6 +97,14 @@ typedef struct UncoloredSpaceTriangle { Vec2 uv2; /* in pixels */ } UncoloredSpaceTriangle; +typedef struct SpaceBillboard { + Vec3 position; + Vec2 size; + Color color; + // TextureKey texture; /* is assumed from other places */ + bool cylindrical; +} SpaceBillboard; + /* batch of primitives with overlapping properties */ typedef struct MeshBatch { uint8_t *primitives; /* note: interpretation of it is arbitrary */ @@ -179,6 +187,26 @@ typedef struct ElementIndexedQuadWithoutColorWithoutTexture { Vec2 v3; } ElementIndexedQuadWithoutColorWithoutTexture; +/* TODO: no color variant */ +typedef struct ElementIndexedBillboard { + /* upper-left */ + Vec3 v0; + Vec2 uv0; + Color c0; + /* bottom-left */ + Vec3 v1; + Vec2 uv1; + Color c1; + /* bottom-right */ + Vec3 v2; + Vec2 uv2; + Color c2; + /* upper-right */ + Vec3 v3; + Vec2 uv3; + Color c3; +} ElementIndexedBillboard; + /* renders the background, then the primitives in all render queues */ void render(void); @@ -207,9 +235,6 @@ struct QuadBatch collect_rect_batch(const Primitive2D primitives[], size_t len); void render_sprite_batch(const Primitive2D primitives[], struct QuadBatch batch); void render_rect_batch(const Primitive2D primitives[], struct QuadBatch batch); -void draw_uncolored_space_traingle_batch(MeshBatch *batch, - TextureKey texture_key); - /* text */ void render_text(const TextPrimitive *text); @@ -280,8 +305,10 @@ bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch, Color color); void finally_draw_uncolored_space_traingle_batch(MeshBatch const *batch, - TextureKey texture_key, - VertexBuffer buffer); + TextureKey texture_key); + +void finally_draw_billboard_batch(MeshBatch const *batch, + TextureKey texture_key); size_t get_text_payload_size(void); diff --git a/src/rendering/twn_gl_15_rendering.c b/src/rendering/twn_gl_15_rendering.c index d5cc083..4bd9a52 100644 --- a/src/rendering/twn_gl_15_rendering.c +++ b/src/rendering/twn_gl_15_rendering.c @@ -452,6 +452,7 @@ void finally_draw_command(DeferredCommandDraw command) { command.colors.type, command.colors.stride, (void *)command.colors.offset); + } else if (command.constant_colored) glColor4ub(command.color.r, command.color.g, diff --git a/src/rendering/twn_quads.c b/src/rendering/twn_quads.c index 732eee0..8869ee1 100644 --- a/src/rendering/twn_quads.c +++ b/src/rendering/twn_quads.c @@ -1,30 +1,171 @@ #include "twn_draw_c.h" +#include + #include -struct QuadBatch collect_quad_batch(const Primitive2D primitives[], size_t len) { - if (primitives[0].type == PRIMITIVE_2D_SPRITE) - return collect_sprite_batch(primitives, len); - else if (primitives[0].type == PRIMITIVE_2D_RECT) - return collect_rect_batch(primitives, len); - else - SDL_assert(false); - - return (struct QuadBatch){0}; -} - - -/* assumes that orthogonal matrix setup is done already */ -void render_quad_batch(const Primitive2D primitives[], - const struct QuadBatch batch) +void finally_render_quads(const Primitive2D primitives[], + const struct QuadBatch batch, + const VertexBuffer buffer) { - if (primitives[0].type == PRIMITIVE_2D_SPRITE) - render_sprite_batch(primitives, batch); - else if (primitives[0].type == PRIMITIVE_2D_RECT) - render_rect_batch(primitives, batch); - else - SDL_assert(false); + DeferredCommandDraw command = {0}; - return (struct QuadBatch){0}; + GLsizei off = 0, voff = 0, uvoff = 0, coff = 0; + + if (!batch.constant_colored && batch.textured) { + off = offsetof(ElementIndexedQuad, v1); + voff = offsetof(ElementIndexedQuad, v0); + uvoff = offsetof(ElementIndexedQuad, uv0); + coff = offsetof(ElementIndexedQuad, c0); + } else if (batch.constant_colored && batch.textured) { + off = offsetof(ElementIndexedQuadWithoutColor, v1); + voff = offsetof(ElementIndexedQuadWithoutColor, v0); + uvoff = offsetof(ElementIndexedQuadWithoutColor, uv0); + } else if (!batch.constant_colored && !batch.textured) { + off = offsetof(ElementIndexedQuadWithoutTexture, v1); + voff = offsetof(ElementIndexedQuadWithoutTexture, v0); + coff = offsetof(ElementIndexedQuad, c0); + } else if (batch.constant_colored && !batch.textured) { + off = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v1); + voff = offsetof(ElementIndexedQuadWithoutColorWithoutTexture, v0); + } + + command.vertices = (AttributeArrayPointer) { + .arity = 2, + .type = GL_FLOAT, + .stride = off, + .offset = voff, + .buffer = buffer + }; + + if (batch.textured) + command.texcoords = (AttributeArrayPointer) { + .arity = 2, + .type = GL_FLOAT, + .stride = off, + .offset = uvoff, + .buffer = buffer + }; + + if (!batch.constant_colored) { + command.colors = (AttributeArrayPointer) { + .arity = 4, + .type = GL_UNSIGNED_BYTE, + .stride = off, + .offset = coff, + .buffer = buffer + }; + } else { + command.constant_colored = true; + command.color = primitives[0].sprite.color; + } + + if (batch.textured) { + command.textured = true; + command.texture_key = batch.texture_key; + command.texture_repeat = batch.repeat; + } + + command.element_buffer = get_quad_element_buffer(); + command.element_count = 6 * (GLsizei)batch.size; + command.range_end = 6 * (GLsizei)batch.size; + + use_texture_mode(batch.mode); + + DeferredCommand final_command = { + .type = DEFERRED_COMMAND_TYPE_DRAW, + .draw = command + }; + + arrpush(deferred_commands, final_command); +} + + +size_t get_quad_payload_size(struct QuadBatch batch) { + if (batch.constant_colored && batch.textured) + return sizeof (ElementIndexedQuadWithoutColor); + else if (!batch.constant_colored && batch.textured) + return sizeof (ElementIndexedQuad); + else if (batch.constant_colored && !batch.textured) + return sizeof (ElementIndexedQuadWithoutColorWithoutTexture); + else if (!batch.constant_colored && !batch.textured) + return sizeof (ElementIndexedQuadWithoutTexture); + + SDL_assert(false); + return 0; +} + + +bool push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch, + VertexBufferBuilder *builder, + Vec2 v0, Vec2 v1, Vec2 v2, Vec2 v3, + Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3, + Color color) +{ + if (!batch.constant_colored && batch.textured) { + ElementIndexedQuad const buffer_element = { + .v0 = v0, + .v1 = v1, + .v2 = v2, + .v3 = v3, + + .uv0 = uv0, + .uv1 = uv1, + .uv2 = uv2, + .uv3 = uv3, + + /* equal for all (flat shaded) */ + .c0 = color, + // .c1 = color, + .c2 = color, + // .c3 = color, + }; + + return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); + + } else if (batch.constant_colored && batch.textured) { + ElementIndexedQuadWithoutColor const buffer_element = { + .v0 = v0, + .v1 = v1, + .v2 = v2, + .v3 = v3, + + .uv0 = uv0, + .uv1 = uv1, + .uv2 = uv2, + .uv3 = uv3, + }; + + return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); + + } else if (!batch.constant_colored && !batch.textured) { + ElementIndexedQuadWithoutTexture const buffer_element = { + .v0 = v0, + .v1 = v1, + .v2 = v2, + .v3 = v3, + + /* equal for all (flat shaded) */ + .c0 = color, + // .c1 = color, + .c2 = color, + // .c3 = color, + }; + + return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); + + } else if (batch.constant_colored && !batch.textured) { + ElementIndexedQuadWithoutColorWithoutTexture const buffer_element = { + .v0 = v0, + .v1 = v1, + .v2 = v2, + .v3 = v3, + }; + + return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); + } + + SDL_assert(false); + return false; } diff --git a/src/rendering/twn_skybox.c b/src/rendering/twn_skybox.c index 590edc2..40a26e3 100644 --- a/src/rendering/twn_skybox.c +++ b/src/rendering/twn_skybox.c @@ -21,6 +21,8 @@ void render_skybox(void) { if (!paths_in_use) return; + use_space_pipeline(); + /* note: ownership of 'paths_in_use' goes there */ DeferredCommand command = { .type = DEFERRED_COMMAND_TYPE_DRAW_SKYBOX, diff --git a/src/rendering/twn_text.c b/src/rendering/twn_text.c index f37d062..f734c24 100644 --- a/src/rendering/twn_text.c +++ b/src/rendering/twn_text.c @@ -175,7 +175,7 @@ static void text_draw_with(FontData* font_data, char* text, Vec2 position, Color const size_t len = SDL_strlen(text); - VertexBufferBuilder payload = build_vertex_buffer(vertex_array, get_text_payload_size() * len); + VertexBufferBuilder payload = build_vertex_buffer(vertex_array, sizeof (ElementIndexedQuadWithoutColor) * len); for (size_t i = 0; i < len; ++i) { const char c = text[i]; @@ -318,3 +318,73 @@ float draw_text_width(const char *string, float height, const char *font) { return (float)length * font_data->scale_factor; } + + +bool push_text_payload_to_vertex_buffer_builder(FontData const *font_data, + VertexBufferBuilder *builder, + stbtt_aligned_quad quad) +{ + (void)font_data; + + ElementIndexedQuadWithoutColor buffer_element = { + .v0 = (Vec2){ quad.x0, quad.y0 }, + .v1 = (Vec2){ quad.x1, quad.y0 }, + .v2 = (Vec2){ quad.x1, quad.y1 }, + .v3 = (Vec2){ quad.x0, quad.y1 }, + + .uv0 = (Vec2){ quad.s0, quad.t0 }, + .uv1 = (Vec2){ quad.s1, quad.t0 }, + .uv2 = (Vec2){ quad.s1, quad.t1 }, + .uv3 = (Vec2){ quad.s0, quad.t1 }, + }; + + return push_to_vertex_buffer_builder(builder, &buffer_element, sizeof buffer_element); +} + + +void finally_draw_text(FontData const *font_data, + size_t len, + Color color, + VertexBuffer buffer) +{ + DeferredCommandDraw command = {0}; + + command.vertices = (AttributeArrayPointer) { + .arity = 2, + .type = GL_FLOAT, + .stride = offsetof(ElementIndexedQuadWithoutColor, v1), + .offset = offsetof(ElementIndexedQuadWithoutColor, v0), + .buffer = buffer + }; + + command.texcoords = (AttributeArrayPointer) { + .arity = 2, + .type = GL_FLOAT, + .stride = offsetof(ElementIndexedQuadWithoutColor, v1), + .offset = offsetof(ElementIndexedQuadWithoutColor, uv0), + .buffer = buffer + }; + + command.constant_colored = true; + command.color = color; + + command.gpu_texture = font_data->texture; + command.uses_gpu_key = true; + command.textured = true; + + command.element_buffer = get_quad_element_buffer(); + command.element_count = 6 * (GLsizei)len; + command.range_end = 6 * (GLsizei)len; + + use_texture_mode(TEXTURE_MODE_GHOSTLY); + + DeferredCommand final_command = { + .type = DEFERRED_COMMAND_TYPE_DRAW, + .draw = command + }; + + arrpush(deferred_commands, final_command); + + /* TODO: why doesn't it get restored if not placed here? */ + // glDepthMask(GL_TRUE); +} diff --git a/src/rendering/twn_triangles.c b/src/rendering/twn_triangles.c index e9b1901..06d329a 100644 --- a/src/rendering/twn_triangles.c +++ b/src/rendering/twn_triangles.c @@ -17,6 +17,7 @@ void draw_triangle(const char *path, Vec2 uv1, Vec2 uv2) { + // TODO: order drawing by atlas id as well, so that texture rebinding is not as common const TextureKey texture_key = textures_get_key(&ctx.texture_cache, path); struct MeshBatchItem *batch_p = hmgetp_null(ctx.uncolored_mesh_batches, texture_key); @@ -42,10 +43,67 @@ void draw_triangle(const char *path, } -void draw_uncolored_space_traingle_batch(struct MeshBatch *batch, - TextureKey texture_key) +void finally_draw_uncolored_space_traingle_batch(const MeshBatch *batch, + const TextureKey texture_key) { - VertexBuffer const vertex_array = get_scratch_vertex_array(); + const size_t primitives_len = arrlenu(batch->primitives); - finally_draw_uncolored_space_traingle_batch(batch, texture_key, vertex_array); + /* nothing to do */ + if (primitives_len == 0) + return; + + VertexBuffer const buffer = get_scratch_vertex_array(); + + const Rect srcrect = textures_get_srcrect(&ctx.texture_cache, texture_key); + const Rect dims = textures_get_dims(&ctx.texture_cache, texture_key); + + const float wr = srcrect.w / dims.w; + const float hr = srcrect.h / dims.h; + const float xr = srcrect.x / dims.w; + const float yr = srcrect.y / dims.h; + + /* update pixel-based uvs to correspond with texture atlases */ + for (size_t i = 0; i < primitives_len; ++i) { + UncoloredSpaceTriangle *payload = + &((UncoloredSpaceTriangle *)(void *)batch->primitives)[i]; + + payload->uv0.x = xr + ((float)payload->uv0.x / srcrect.w) * wr; + payload->uv0.y = yr + ((float)payload->uv0.y / srcrect.h) * hr; + payload->uv1.x = xr + ((float)payload->uv1.x / srcrect.w) * wr; + payload->uv1.y = yr + ((float)payload->uv1.y / srcrect.h) * hr; + payload->uv2.x = xr + ((float)payload->uv2.x / srcrect.w) * wr; + payload->uv2.y = yr + ((float)payload->uv2.y / srcrect.h) * hr; + } + + specify_vertex_buffer(buffer, batch->primitives, primitives_len * sizeof (UncoloredSpaceTriangle)); + + DeferredCommandDraw command = {0}; + + command.vertices = (AttributeArrayPointer) { + .arity = 3, + .type = GL_FLOAT, + .stride = offsetof(UncoloredSpaceTriangle, v1), + .offset = offsetof(UncoloredSpaceTriangle, v0), + .buffer = buffer + }; + + command.texcoords = (AttributeArrayPointer) { + .arity = 2, + .type = GL_FLOAT, + .stride = offsetof(UncoloredSpaceTriangle, v1), + .offset = offsetof(UncoloredSpaceTriangle, uv0), + .buffer = buffer + }; + + command.textured = true; + command.texture_key = texture_key; + + command.primitive_count = (GLsizei)(3 * primitives_len); + + DeferredCommand final_command = { + .type = DEFERRED_COMMAND_TYPE_DRAW, + .draw = command + }; + + arrpush(deferred_commands, final_command); } diff --git a/src/twn_amalgam.c b/src/twn_amalgam.c index cb4741e..1a2fb99 100644 --- a/src/twn_amalgam.c +++ b/src/twn_amalgam.c @@ -17,4 +17,6 @@ #include "rendering/twn_sprites.c" #include "rendering/twn_rects.c" #include "rendering/twn_text.c" +#include "rendering/twn_quads.c" #include "rendering/twn_triangles.c" +#include "rendering/twn_billboards.c" diff --git a/src/twn_camera.c b/src/twn_camera.c index 0aebf04..620d77b 100644 --- a/src/twn_camera.c +++ b/src/twn_camera.c @@ -28,9 +28,10 @@ Matrix4 camera_look_at(const Camera *const camera) { result.row[3].x = -m_vec_dot(r, camera->pos); result.row[3].y = -m_vec_dot(u, camera->pos); result.row[3].z = m_vec_dot(camera->target, camera->pos); - result.row[0].w = result.row[1].w = result.row[2].w = 0.0f; result.row[3].w = 1.0f; + result.row[0].w = result.row[1].w = result.row[2].w = 0.0f; + return result; } diff --git a/src/twn_engine_context_c.h b/src/twn_engine_context_c.h index 014adb8..0036bcd 100644 --- a/src/twn_engine_context_c.h +++ b/src/twn_engine_context_c.h @@ -47,6 +47,7 @@ typedef struct EngineContext { /* rendering */ Primitive2D *render_queue_2d; MeshBatchItem *uncolored_mesh_batches; + MeshBatchItem *billboard_batches; TextCache text_cache; TextureCache texture_cache;