From a36459397ecf24dbce4133fae4c60d7351dc2c4b Mon Sep 17 00:00:00 2001 From: veclavtalica Date: Wed, 26 Feb 2025 13:27:09 +0300 Subject: [PATCH] draw: increase far Z, separate path for space quads, fix billboard batching --- apps/demos/scenery/scenes/ingame.c | 3 +- include/twn_draw.h | 10 +- src/rendering/twn_billboards.c | 13 +-- src/rendering/twn_draw.c | 48 +++------ src/rendering/twn_draw_c.h | 6 +- src/rendering/twn_gl_15_rendering.c | 5 +- src/rendering/twn_gl_any_rendering.c | 14 +-- src/rendering/twn_quads.c | 147 +++++++++++++++++++++++++++ src/twn_camera.c | 2 +- src/twn_engine_context_c.h | 1 + 10 files changed, 190 insertions(+), 59 deletions(-) diff --git a/apps/demos/scenery/scenes/ingame.c b/apps/demos/scenery/scenes/ingame.c index 827a67e..3c20c57 100644 --- a/apps/demos/scenery/scenes/ingame.c +++ b/apps/demos/scenery/scenes/ingame.c @@ -12,7 +12,7 @@ #define TERRAIN_FREQUENCY 0.15f -#define TERRAIN_DISTANCE 64 +#define TERRAIN_DISTANCE 128 #define HALF_TERRAIN_DISTANCE ((float)TERRAIN_DISTANCE / 2) #define PLAYER_HEIGHT 0.6f @@ -161,6 +161,7 @@ static void draw_terrain(SceneIngame *scn) { (Rect){ .w = 128, .h = 128 }, (Color){255, 255, 255, 255}); + /* TODO: only draw upclose */ draw_billboard("/assets/grasses/10.png", (Vec3){ (float)x, d0 + 0.15f, (float)y }, (Vec2){0.3f, 0.3f}, diff --git a/include/twn_draw.h b/include/twn_draw.h index 0e7d863..487d20e 100644 --- a/include/twn_draw.h +++ b/include/twn_draw.h @@ -66,12 +66,12 @@ TWN_API void draw_triangle(char const *texture, Color c2); /* optional, default: all 255 */ TWN_API void draw_quad(char const *texture, - Vec3 v0, /* upper-left */ - Vec3 v1, /* bottom-left */ - Vec3 v2, /* bottom-right */ - Vec3 v3, /* upper-right */ + Vec3 v0, /* upper-left */ + Vec3 v1, /* bottom-left */ + Vec3 v2, /* bottom-right */ + Vec3 v3, /* upper-right */ Rect texture_region, - Color color); /* optional, default: all 255 */ + Color color); /* optional, default: all 255 */ TWN_API void draw_billboard(char const *texture, Vec3 position, diff --git a/src/rendering/twn_billboards.c b/src/rendering/twn_billboards.c index 6ead8e7..cae6b66 100644 --- a/src/rendering/twn_billboards.c +++ b/src/rendering/twn_billboards.c @@ -95,14 +95,15 @@ void finally_draw_billboard_batch(struct MeshBatch const *batch, const Vec2 uv3c = { xr + wr, yr }; for (size_t batch_n = 0; batch_n <= (primitives_len - 1) / QUAD_ELEMENT_BUFFER_LENGTH; batch_n++) { + size_t const processing = MIN(primitives_len - batch_n * QUAD_ELEMENT_BUFFER_LENGTH, QUAD_ELEMENT_BUFFER_LENGTH); /* emit vertex data */ VertexBuffer const buffer = get_scratch_vertex_array(); VertexBufferBuilder builder = build_vertex_buffer( buffer, - sizeof (ElementIndexedBillboard) * MIN(primitives_len - batch_n * QUAD_ELEMENT_BUFFER_LENGTH, QUAD_ELEMENT_BUFFER_LENGTH)); + sizeof (ElementIndexedBillboard) * processing); - for (size_t i = 0; i < primitives_len; ++i) { + for (size_t i = 0; i < processing; ++i) { struct SpaceBillboard const billboard = ((SpaceBillboard *)(void *)batch->primitives)[batch_n * QUAD_ELEMENT_BUFFER_LENGTH + i]; /* a = (right + up) * size, b = (right - up) * size*/ @@ -179,20 +180,20 @@ void finally_draw_billboard_batch(struct MeshBatch const *batch, .buffer = buffer }; - command.textured = true; command.texture_key = texture_key; + command.textured = true; command.element_buffer = get_quad_element_buffer(); - command.element_count = 6 * (uint32_t)primitives_len; - command.range_end = 6 * (uint32_t)primitives_len; + command.element_count = 6 * (uint32_t)processing; + command.range_end = 6 * (uint32_t)processing; /* TODO: support alpha blended case, with distance sort */ TextureMode mode = textures_get_mode(&ctx.texture_cache, texture_key); if (mode == TEXTURE_MODE_GHOSTLY) mode = TEXTURE_MODE_SEETHROUGH; - command.texture_mode = mode; command.pipeline = PIPELINE_SPACE; + command.texture_mode = mode; command.depth_range_high = depth_range_high; command.depth_range_low = depth_range_low; diff --git a/src/rendering/twn_draw.c b/src/rendering/twn_draw.c index b9700e2..ac7b450 100644 --- a/src/rendering/twn_draw.c +++ b/src/rendering/twn_draw.c @@ -52,6 +52,9 @@ void render_clear(void) { for (size_t i = 0; i < hmlenu(ctx.billboard_batches); ++i) arrsetlen(ctx.billboard_batches[i].value.primitives, 0); + + for (size_t i = 0; i < hmlenu(ctx.quad_batches); ++i) + arrsetlen(ctx.quad_batches[i].value.primitives, 0); } @@ -196,31 +199,6 @@ void draw_nine_slice(const char *texture, Vec2 corners, Rect rect, float border_ } -TWN_API void draw_quad(char const *texture, - Vec3 v0, /* upper-left */ - Vec3 v1, /* bottom-left */ - Vec3 v2, /* bottom-right */ - Vec3 v3, /* upper-right */ - Rect texture_region, - Color color) -{ - Vec2 const uv0 = { texture_region.x, texture_region.y }; - Vec2 const uv1 = { texture_region.x, texture_region.y + texture_region.h }; - Vec2 const uv2 = { texture_region.x + texture_region.w, texture_region.y + texture_region.h }; - Vec2 const uv3 = { texture_region.x + texture_region.w, texture_region.y }; - - draw_triangle(texture, - v0, v1, v3, - uv0, uv1, uv3, - color, color, color); - - draw_triangle(texture, - v3, v1, v2, - uv3, uv1, uv2, - color, color, color); -} - - static void render_2d(void) { const size_t render_queue_len = arrlenu(ctx.render_queue_2d); @@ -397,18 +375,16 @@ static void render_2d(void) { static void render_space(void) { finally_draw_models(); - /* 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 || hmlenu(ctx.billboard_batches) != 0) { - 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) + 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.billboard_batches); ++i) { - finally_draw_billboard_batch(&ctx.billboard_batches[i].value, ctx.billboard_batches[i].key); - } - } + for (size_t i = 0; i < hmlenu(ctx.quad_batches); ++i) + finally_draw_space_quads_batch(&ctx.quad_batches[i].value, + ctx.quad_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); render_skybox(); /* after everything else, as to use depth buffer for early z rejection */ } diff --git a/src/rendering/twn_draw_c.h b/src/rendering/twn_draw_c.h index 9029341..4198114 100644 --- a/src/rendering/twn_draw_c.h +++ b/src/rendering/twn_draw_c.h @@ -23,7 +23,7 @@ extern float camera_2d_zoom; extern double depth_range_low, depth_range_high; -#define QUAD_ELEMENT_BUFFER_LENGTH (65536 / 6) +#define QUAD_ELEMENT_BUFFER_LENGTH ((65536 * 1) / 6) #define CIRCLE_VERTICES_MAX 2048 /* TODO: limit to only most necessary */ @@ -217,6 +217,7 @@ typedef struct ElementIndexedQuadWithoutColorWithoutTexture { Vec2 v3; } ElementIndexedQuadWithoutColorWithoutTexture; +/* TODO: rename to space quad */ /* TODO: no color variant */ typedef struct ElementIndexedBillboard { /* upper-left */ @@ -334,6 +335,9 @@ void push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch, Vec2 uv0, Vec2 uv1, Vec2 uv2, Vec2 uv3, Color color); +void finally_draw_space_quads_batch(const MeshBatch *batch, + const TextureKey texture_key); + void finally_draw_text(FontData const *font_data, size_t len, Color color, diff --git a/src/rendering/twn_gl_15_rendering.c b/src/rendering/twn_gl_15_rendering.c index 038779d..0b7adbc 100644 --- a/src/rendering/twn_gl_15_rendering.c +++ b/src/rendering/twn_gl_15_rendering.c @@ -584,8 +584,8 @@ void finally_draw_command(DeferredCommandDraw command) { finally_use_texture_mode(command.texture_mode); - glBindBuffer(GL_ARRAY_BUFFER, command.vertices.buffer); glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, command.element_buffer); + glBindBuffer(GL_ARRAY_BUFFER, command.vertices.buffer); glEnableClientState(GL_VERTEX_ARRAY); glVertexPointer(command.vertices.arity, @@ -637,7 +637,7 @@ void finally_draw_command(DeferredCommandDraw command) { command.range_start, command.range_end, command.element_count, - GL_UNSIGNED_SHORT, + GL_UNSIGNED_INT, NULL); } else { SDL_assert(command.primitive_count != 0); @@ -661,6 +661,7 @@ void finally_draw_command(DeferredCommandDraw command) { glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0); } + void render_line(const LinePrimitive *line) { finally_use_2d_pipeline(); glBegin(GL_LINES); diff --git a/src/rendering/twn_gl_any_rendering.c b/src/rendering/twn_gl_any_rendering.c index 8dcbb28..dc4e499 100644 --- a/src/rendering/twn_gl_any_rendering.c +++ b/src/rendering/twn_gl_any_rendering.c @@ -62,15 +62,15 @@ IndexBuffer get_quad_element_buffer(void) { /* TODO: use builder interface, not direct calls (glMapBuffer isn't portable) */ if (buffer == 0) { buffer = create_index_buffer(); - IndexBufferBuilder builder = build_index_buffer(buffer, sizeof (GLshort) * QUAD_ELEMENT_BUFFER_LENGTH * 6 ); + IndexBufferBuilder builder = build_index_buffer(buffer, sizeof (GLuint) * QUAD_ELEMENT_BUFFER_LENGTH * 6 ); for (size_t i = 0; i < QUAD_ELEMENT_BUFFER_LENGTH; ++i) { - ((GLshort *)builder.base)[i * 6 + 0] = (GLshort)(i * 4 + 0); - ((GLshort *)builder.base)[i * 6 + 1] = (GLshort)(i * 4 + 1); - ((GLshort *)builder.base)[i * 6 + 2] = (GLshort)(i * 4 + 2); - ((GLshort *)builder.base)[i * 6 + 3] = (GLshort)(i * 4 + 2); - ((GLshort *)builder.base)[i * 6 + 4] = (GLshort)(i * 4 + 3); - ((GLshort *)builder.base)[i * 6 + 5] = (GLshort)(i * 4 + 0); + ((GLuint *)builder.base)[i * 6 + 0] = (GLuint)(i * 4 + 0); + ((GLuint *)builder.base)[i * 6 + 1] = (GLuint)(i * 4 + 1); + ((GLuint *)builder.base)[i * 6 + 2] = (GLuint)(i * 4 + 2); + ((GLuint *)builder.base)[i * 6 + 3] = (GLuint)(i * 4 + 2); + ((GLuint *)builder.base)[i * 6 + 4] = (GLuint)(i * 4 + 3); + ((GLuint *)builder.base)[i * 6 + 5] = (GLuint)(i * 4 + 0); } finish_index_builder(&builder); diff --git a/src/rendering/twn_quads.c b/src/rendering/twn_quads.c index 16b8f54..81b7012 100644 --- a/src/rendering/twn_quads.c +++ b/src/rendering/twn_quads.c @@ -1,4 +1,8 @@ #include "twn_draw_c.h" +#include "twn_draw.h" +#include "twn_engine_context_c.h" +#include "twn_util.h" +#include "twn_util_c.h" #include @@ -171,3 +175,146 @@ void push_quad_payload_to_vertex_buffer_builder(struct QuadBatch batch, ((ElementIndexedQuadWithoutColorWithoutTexture *)builder->base)[index] = payload; } } + + +void draw_quad(char const *texture, + Vec3 v0, /* upper-left */ + Vec3 v1, /* bottom-left */ + Vec3 v2, /* bottom-right */ + Vec3 v3, /* upper-right */ + Rect texture_region, + Color color) +{ + Vec2 const uv0 = { texture_region.x, texture_region.y }; + Vec2 const uv1 = { texture_region.x, texture_region.y + texture_region.h }; + Vec2 const uv2 = { texture_region.x + texture_region.w, texture_region.y + texture_region.h }; + Vec2 const uv3 = { texture_region.x + texture_region.w, texture_region.y }; + + // 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.quad_batches, texture_key); + if (!batch_p) { + struct MeshBatch item = {0}; + hmput(ctx.quad_batches, texture_key, item); + batch_p = &ctx.quad_batches[hmlenu(ctx.quad_batches) - 1]; /* TODO: can last index be used? */ + } + + /* TODO: rename from billboards */ + struct ElementIndexedBillboard billboard = { + .v0 = v0, + .v1 = v1, + .v2 = v2, + .v3 = v3, + + /* will be adjusted later when atlas is baked */ + .uv0 = uv0, + .uv1 = uv1, + .uv2 = uv2, + .uv3 = uv3, + + /* flat shading is assumed, so we can skip setting the duplicates */ + .c0 = color, + // .c1 = color, + .c2 = color, + // .c3 = color, + }; + + struct ElementIndexedBillboard *billboards = (struct ElementIndexedBillboard *)(void *)batch_p->value.primitives; + + arrpush(billboards, billboard); + batch_p->value.primitives = (uint8_t *)billboards; +} + + +void finally_draw_space_quads_batch(const MeshBatch *batch, + const TextureKey texture_key) +{ + 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) { + ElementIndexedBillboard *payload = + &((ElementIndexedBillboard *)(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; + payload->uv3.x = xr + ((float)payload->uv3.x / srcrect.w) * wr; + payload->uv3.y = yr + ((float)payload->uv3.y / srcrect.h) * hr; + } + + for (size_t batch_n = 0; batch_n <= (primitives_len - 1) / QUAD_ELEMENT_BUFFER_LENGTH; batch_n++) { + size_t const processing = MIN(primitives_len - batch_n * QUAD_ELEMENT_BUFFER_LENGTH, QUAD_ELEMENT_BUFFER_LENGTH); + + VertexBuffer const buffer = get_scratch_vertex_array(); + + specify_vertex_buffer(buffer, &batch->primitives[batch_n * QUAD_ELEMENT_BUFFER_LENGTH], sizeof (ElementIndexedBillboard) * processing); + + DeferredCommandDraw command = {0}; + + command.vertices = (AttributeArrayPointer) { + .arity = 3, + .type = TWN_FLOAT, + .stride = offsetof(ElementIndexedBillboard, v1), + .offset = offsetof(ElementIndexedBillboard, v0), + .buffer = buffer + }; + + command.texcoords = (AttributeArrayPointer) { + .arity = 2, + .type = TWN_FLOAT, + .stride = offsetof(ElementIndexedBillboard, v1), + .offset = offsetof(ElementIndexedBillboard, uv0), + .buffer = buffer + }; + + command.colors = (AttributeArrayPointer) { + .arity = 4, + .type = TWN_UNSIGNED_BYTE, + .stride = offsetof(ElementIndexedBillboard, v1), + .offset = offsetof(ElementIndexedBillboard, c0), + .buffer = buffer + }; + + command.texture_key = texture_key; + command.textured = true; + + command.element_buffer = get_quad_element_buffer(); + command.element_count = 6 * (uint32_t)processing; + command.range_end = 6 * (uint32_t)processing; + + /* TODO: support alpha blended case, with distance sort */ + TextureMode mode = textures_get_mode(&ctx.texture_cache, texture_key); + if (mode == TEXTURE_MODE_GHOSTLY) + mode = TEXTURE_MODE_SEETHROUGH; + + command.pipeline = PIPELINE_SPACE; + command.texture_mode = mode; + + command.depth_range_high = depth_range_high; + command.depth_range_low = depth_range_low; + + DeferredCommand final_command = { + .type = DEFERRED_COMMAND_TYPE_DRAW, + .draw = command + }; + + arrpush(deferred_commands, final_command); + } +} diff --git a/src/twn_camera.c b/src/twn_camera.c index 99d9a5d..6bebd52 100644 --- a/src/twn_camera.c +++ b/src/twn_camera.c @@ -6,7 +6,7 @@ #define CAMERA_NEAR_Z 0.1f -#define CAMERA_FAR_Z 100.0f +#define CAMERA_FAR_Z 1000.0f Matrix4 camera_look_at(const Camera *const camera) { diff --git a/src/twn_engine_context_c.h b/src/twn_engine_context_c.h index 41358fe..4adf606 100644 --- a/src/twn_engine_context_c.h +++ b/src/twn_engine_context_c.h @@ -49,6 +49,7 @@ typedef struct EngineContext { Primitive2D *render_queue_2d; MeshBatchItem *uncolored_mesh_batches; MeshBatchItem *billboard_batches; + MeshBatchItem *quad_batches; TextCache text_cache; TextureCache texture_cache;