GLOBAL struct { G_Resource *solid_white_texture; } G = ZI, DEBUG_ALIAS(G, G_draw); /* ========================== * * Startup * ========================== */ D_StartupReceipt draw_startup(F_StartupReceipt *font_sr) { __prof; (UNUSED)font_sr; u32 pixel_white = 0xFFFFFFFF; G.solid_white_texture = gp_texture_alloc(GP_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, V2i32FromXY(1, 1), &pixel_white); return (D_StartupReceipt) { 0 }; } /* ========================== * * Material * ========================== */ void draw_material(G_RenderSig *sig, D_MaterialParams params) { G_RenderCmdDesc cmd = ZI; cmd.kind = GP_RENDER_CMD_KIND_DRAW_MATERIAL; cmd.material.xf = params.xf; cmd.material.texture = params.texture; cmd.material.clip = params.clip; cmd.material.tint = params.tint; cmd.material.is_light = params.is_light; cmd.material.light_emittance = params.light_emittance; gp_push_render_cmd(sig, &cmd); } /* ========================== * * Fill shapes * ========================== */ void draw_poly_ex(G_RenderSig *sig, V2Array vertices, G_Indices indices, u32 color) { G_RenderCmdDesc cmd = ZI; cmd.kind = GP_RENDER_CMD_KIND_DRAW_UI_SHAPE; cmd.ui_shape.vertices = vertices; cmd.ui_shape.indices = indices; cmd.ui_shape.color = color; gp_push_render_cmd(sig, &cmd); } /* Draws a filled polygon using triangles in a fan pattern */ void draw_poly(G_RenderSig *sig, V2Array vertices, u32 color) { if (vertices.count >= 3) { TempArena scratch = BeginScratchNoConflict(); u32 num_tris = vertices.count - 2; u32 num_indices = num_tris * 3; /* Generate indices in a fan pattern */ G_Indices indices = { .count = num_indices, .indices = PushArrayNoZero(scratch.arena, u32, num_indices) }; for (u32 i = 0; i < num_tris; ++i) { u32 tri_offset = i * 3; indices.indices[tri_offset + 0] = 0; indices.indices[tri_offset + 1] = (i + 1); indices.indices[tri_offset + 2] = (i + 2); } draw_poly_ex(sig, vertices, indices, color); EndScratch(scratch); } } void draw_circle(G_RenderSig *sig, V2 pos, f32 radius, u32 color, u32 detail) { TempArena scratch = BeginScratchNoConflict(); V2 *points = PushArrayNoZero(scratch.arena, V2, detail); for (u32 i = 0; i < detail; ++i) { f32 angle = ((f32)i / (f32)detail) * TAU; V2 p = V2FromXY( radius * math_cos(angle), radius * math_sin(angle) ); points[i] = v2_add(pos, p); } V2Array vertices = { .points = points, .count = detail }; draw_poly(sig, vertices, color); EndScratch(scratch); } void draw_quad(G_RenderSig *sig, Quad quad, u32 color) { LOCAL_PERSIST u32 indices_array[6] = { 0, 1, 2, 0, 2, 3 }; V2Array vertices = { .count = 4, .points = quad.e }; G_Indices indices = { .count = 6, .indices = indices_array }; draw_poly_ex(sig, vertices, indices, color); } /* ========================== * * Line shapes * ========================== */ void draw_gradient_line(G_RenderSig *sig, V2 start, V2 end, f32 thickness, u32 start_color, u32 end_color) { #if 0 Quad quad = quad_from_line(start, end, thickness); draw_material(sig, DRAW_MATERIAL_PARAMS(.texture = G.solid_white_texture, .tint0 = start_color, .tint1 = end_color, .quad = quad)); #else /* Placeholder */ (UNUSED)end_color; Quad quad = quad_from_line(start, end, thickness); draw_quad(sig, quad, start_color); #endif } void draw_line(G_RenderSig *sig, V2 start, V2 end, f32 thickness, u32 color) { Quad quad = quad_from_line(start, end, thickness); draw_quad(sig, quad, color); } void draw_ray(G_RenderSig *sig, V2 pos, V2 rel, f32 thickness, u32 color) { Quad quad = quad_from_ray(pos, rel, thickness); draw_quad(sig, quad, color); } void draw_poly_line(G_RenderSig *sig, V2Array points, b32 loop, f32 thickness, u32 color) { if (points.count >= 2) { for (u64 i = 1; i < points.count; ++i) { V2 p1 = points.points[i - 1]; V2 p2 = points.points[i]; Quad q = quad_from_line(p1, p2, thickness); draw_quad(sig, q, color); } if (loop && points.count > 2) { V2 p1 = points.points[points.count - 1]; V2 p2 = points.points[0]; Quad q = quad_from_line(p1, p2, thickness); draw_quad(sig, q, color); } } } void draw_circle_line(G_RenderSig *sig, V2 pos, f32 radius, f32 thickness, u32 color, u32 detail) { TempArena scratch = BeginScratchNoConflict(); V2 *points = PushArrayNoZero(scratch.arena, V2, detail); for (u32 i = 0; i < detail; ++i) { f32 angle = ((f32)i / (f32)detail) * TAU; V2 p = V2FromXY( radius * math_cos(angle), radius * math_sin(angle) ); points[i] = v2_add(pos, p); } V2Array a = { .points = points, .count = detail }; draw_poly_line(sig, a, 1, thickness, color); EndScratch(scratch); } void draw_quad_line(G_RenderSig *sig, Quad quad, f32 thickness, u32 color) { V2 points[] = { quad.p0, quad.p1, quad.p2, quad.p3 }; V2Array a = { .points = points, .count = countof(points) }; draw_poly_line(sig, a, 1, thickness, color); } void draw_arrow_line(G_RenderSig *sig, V2 start, V2 end, f32 thickness, f32 arrowhead_height, u32 color) { const f32 head_width_ratio = 0.5f; /* Width of arrowhead relative to its length */ const f32 max_height_to_line_ratio = 0.9f; /* Maximum length of arrowhead relative to total line length */ arrowhead_height = min_f32(arrowhead_height, v2_len(v2_sub(end, start)) * max_height_to_line_ratio); V2 head_start_dir = v2_sub(start, end); head_start_dir = v2_norm(head_start_dir); head_start_dir = v2_mul(head_start_dir, arrowhead_height); V2 head_start = v2_add(end, head_start_dir); V2 head_p1_dir = v2_perp_mul(head_start_dir, head_width_ratio); V2 head_p2_dir = v2_neg(head_p1_dir); V2 head_p1 = v2_add(head_start, head_p1_dir); V2 head_p2 = v2_add(head_start, head_p2_dir); V2 head_points[] = { end, head_p1, head_p2 }; V2Array head_points_v2_array = { .points = head_points, .count = countof(head_points) }; draw_poly(sig, head_points_v2_array, color); Quad line_quad = quad_from_line(start, head_start, thickness); draw_quad(sig, line_quad, color); } void draw_arrow_ray(G_RenderSig *sig, V2 pos, V2 rel, f32 thickness, f32 arrowhead_height, u32 color) { V2 end = v2_add(pos, rel); draw_arrow_line(sig, pos, end, thickness, arrowhead_height, color); } void draw_collider_line(G_RenderSig *sig, CLD_Shape shape, Xform shape_xf, f32 thickness, u32 color, u32 detail) { TempArena scratch = BeginScratchNoConflict(); V2Array poly = ZI; if (shape.radius == 0) { poly.count = shape.count; poly.points = PushArrayNoZero(scratch.arena, V2, shape.count); for (u32 i = 0; i < shape.count; ++i) { V2 p = xform_mul_v2(shape_xf, shape.points[i]); poly.points[i] = p; } } else { poly.count = detail; poly.points = PushArrayNoZero(scratch.arena, V2, detail); for (u32 i = 0; i < detail; ++i) { f32 angle = ((f32)i / (f32)detail) * TAU; V2 dir = V2FromXY(math_cos(angle), math_sin(angle)); V2 p = collider_get_support_point(&shape, shape_xf, dir).p; poly.points[i] = p; } } draw_poly_line(sig, poly, 1, thickness, color); EndScratch(scratch); } /* ========================== * * Grid * ========================== */ void draw_grid(G_RenderSig *sig, Xform xf, u32 bg0_color, u32 bg1_color, u32 line_color, u32 x_color, u32 y_color, f32 thickness, f32 spacing, V2 offset) { i32 grid_id = 0; { G_RenderCmdDesc cmd = ZI; cmd.kind = GP_RENDER_CMD_KIND_PUSH_GRID; cmd.grid.bg0_color = bg0_color; cmd.grid.bg1_color = bg1_color; cmd.grid.line_color = line_color; cmd.grid.x_color = x_color; cmd.grid.y_color = y_color; cmd.grid.line_thickness = thickness; cmd.grid.line_spacing = spacing; cmd.grid.offset = offset; grid_id = gp_push_render_cmd(sig, &cmd); } G_RenderCmdDesc cmd = ZI; cmd.kind = GP_RENDER_CMD_KIND_DRAW_MATERIAL; cmd.material.xf = xf; cmd.material.tint = COLOR_WHITE; cmd.material.grid_cmd_id = grid_id; gp_push_render_cmd(sig, &cmd); } /* ========================== * * UI * ========================== */ void draw_ui_rect(G_RenderSig *sig, D_UiRectParams params) { G_RenderCmdDesc cmd = ZI; cmd.kind = GP_RENDER_CMD_KIND_DRAW_UI_RECT; cmd.ui_rect.xf = params.xf; cmd.ui_rect.texture = params.texture; cmd.ui_rect.clip = params.clip; cmd.ui_rect.tint = params.tint; gp_push_render_cmd(sig, &cmd); } /* ========================== * * Text * ========================== */ /* Returns the rect of the text area */ Rect draw_text(G_RenderSig *sig, D_TextParams params) { TempArena scratch = BeginScratchNoConflict(); f32 inv_font_image_width = 1.0 / (f32)params.font->image_width; f32 inv_font_image_height = 1.0 / (f32)params.font->image_height; f32 line_spacing = params.font->point_size * 1.5f * params.scale; /* ========================== * * Build line glyphs * ========================== */ struct drawable_glyph { f32 off_x; f32 off_y; f32 width; f32 height; f32 advance; ClipRect clip; }; struct drawable_line { f32 line_width; u32 num_glyphs; struct drawable_glyph *glyphs; struct drawable_line *next; }; u64 num_lines = 0; f32 widest_line = 0; struct drawable_line *first_line = 0; struct drawable_line *last_line = 0; f32 first_line_top_offset = 0; f32 last_line_bottom_offset = 0; if (params.str.len > 0) { b32 string_done = 0; struct string_codepoint_iter iter = string_codepoint_iter_begin(params.str); while (!string_done) { f32 line_width = 0; f32 top_offset = 0; f32 bottom_offset = 0; u64 num_line_glyphs = 0; struct drawable_glyph *line_glyphs = PushDry(scratch.arena, struct drawable_glyph); b32 line_done = 0; while (!line_done) { string_done = !string_codepoint_iter_next(&iter); if (string_done) { line_done = 1; } else { u32 codepoint = iter.codepoint; if (codepoint == '\n') { line_done = 1; } else { struct drawable_glyph *tg = PushStruct(scratch.arena, struct drawable_glyph); ++num_line_glyphs; F_Glyph *glyph = font_get_glyph(params.font, codepoint); tg->off_x = glyph->off_x * params.scale; tg->off_y = glyph->off_y * params.scale; tg->width = glyph->width * params.scale; tg->height = glyph->height * params.scale; tg->advance = glyph->advance * params.scale; Rect glyph_atlas_rect = glyph->atlas_rect; tg->clip = (ClipRect) { { glyph_atlas_rect.x * inv_font_image_width, glyph_atlas_rect.y * inv_font_image_height }, { (glyph_atlas_rect.x + glyph_atlas_rect.width) * inv_font_image_width, (glyph_atlas_rect.y + glyph_atlas_rect.height) * inv_font_image_height } }; line_width += tg->advance; top_offset = min_f32(top_offset, tg->off_y); bottom_offset = max_f32(bottom_offset, tg->off_y + tg->height); } } } /* Line ended */ /* TODO: Only create nodes for non-empty lines. Embed line number in the node. */ struct drawable_line *node = PushStruct(scratch.arena, struct drawable_line); node->line_width = line_width; node->num_glyphs = num_line_glyphs; node->glyphs = line_glyphs; if (last_line) { last_line->next = node; } else { first_line = node; first_line_top_offset = top_offset; } last_line = node; last_line_bottom_offset = bottom_offset; widest_line = max_f32(widest_line, line_width); ++num_lines; } string_codepoint_iter_end(&iter); } /* ========================== * * Determine text bounds * ========================== */ Rect bounds = ZI; bounds.x = params.pos.x; bounds.y = params.pos.y; bounds.width = widest_line; bounds.height = num_lines * line_spacing + first_line_top_offset + last_line_bottom_offset; /* Offset bounds X */ switch (params.offset_x) { case DRAW_TEXT_OFFSET_X_LEFT: break; case DRAW_TEXT_OFFSET_X_CENTER: { bounds.x -= bounds.width / 2.f; } break; case DRAW_TEXT_OFFSET_X_RIGHT: { bounds.x -= bounds.width; } break; } /* Offset bounds Y */ switch (params.offset_y) { case DRAW_TEXT_OFFSET_Y_TOP: break; case DRAW_TEXT_OFFSET_Y_CENTER: { bounds.y -= bounds.height / 2.f; } break; case DRAW_TEXT_OFFSET_Y_BOTTOM: { if (last_line) { bounds.y -= bounds.height + last_line_bottom_offset; } } break; } /* ========================== * * Draw lines * ========================== */ u64 line_number = 0; for (struct drawable_line *line = first_line; line; line = line->next) { V2 draw_pos = bounds.pos; draw_pos.y += line_number * line_spacing - first_line_top_offset; /* Alignment */ switch (params.alignment) { case DRAW_TEXT_ALIGNMENT_LEFT: break; case DRAW_TEXT_ALIGNMENT_CENTER: { draw_pos.x += (bounds.width - line->line_width) / 2.f; } break; case DRAW_TEXT_ALIGNMENT_RIGHT: { draw_pos.x += bounds.width - line->line_width; } break; } /* Draw glyphs */ for (u64 i = 0; i < line->num_glyphs; ++i) { struct drawable_glyph *tg = &line->glyphs[i]; V2 pos = V2FromXY(draw_pos.x + tg->off_x, draw_pos.y + tg->off_y); V2 size = V2FromXY(tg->width, tg->height); Xform xf = xform_from_rect(RECT_FROM_V2(pos, size)); draw_ui_rect(sig, DRAW_UI_RECT_PARAMS(.xf = xf, .texture = params.font->texture, .tint = params.color, .clip = tg->clip)); draw_pos.x += tg->advance; } ++line_number; } EndScratch(scratch); return bounds; }