520 lines
17 KiB
C
520 lines
17 KiB
C
#include "draw.h"
|
|
#include "gpu.h"
|
|
#include "math.h"
|
|
#include "font.h"
|
|
#include "scratch.h"
|
|
#include "sprite.h"
|
|
#include "collider.h"
|
|
|
|
GLOBAL struct {
|
|
struct gpu_texture solid_white;
|
|
} G = ZI, DEBUG_ALIAS(G, G_draw);
|
|
|
|
/* ========================== *
|
|
* Startup
|
|
* ========================== */
|
|
|
|
struct draw_startup_receipt draw_startup(struct gpu_startup_receipt *gpu_sr,
|
|
struct font_startup_receipt *font_sr)
|
|
{
|
|
(UNUSED)gpu_sr;
|
|
(UNUSED)font_sr;
|
|
u32 pixel_white = 0xFFFFFFFF;
|
|
G.solid_white = gpu_texture_alloc(GPU_TEXTURE_FORMAT_R8G8B8A8_UNORM, 0, V2I32(1, 1), &pixel_white);
|
|
return (struct draw_startup_receipt) { 0 };
|
|
}
|
|
|
|
/* ========================== *
|
|
* Texture
|
|
* ========================== */
|
|
|
|
void draw_quad_texture_ex(struct gpu_cmd_buffer *cmdbuff, struct gpu_texture texture, struct sprite_tag sprite, struct clip_rect clip, u32 tint0_srgb, u32 tint1_srgb, struct quad quad)
|
|
{
|
|
struct gpu_cmd_parameters cmd_params = ZI;
|
|
cmd_params.kind = SHADER_TRIANGLE;
|
|
cmd_params.texture_params.texture = texture;
|
|
cmd_params.texture_params.sprite = sprite;
|
|
gpu_cmd_buffer_ensure_cmd(cmdbuff, &cmd_params);
|
|
|
|
struct triangle_shader_vertex *vertices = NULL;
|
|
vidx *indices = NULL;
|
|
u32 offset = gpu_cmd_buffer_push_vertices(cmdbuff, (u8 **)&vertices, &indices, 4, 6);
|
|
|
|
/* Top left */
|
|
vertices[0] = (struct triangle_shader_vertex) {
|
|
.pos = quad.p0,
|
|
.uv = { clip.p0.x, clip.p0.y },
|
|
.tint_srgb = tint0_srgb
|
|
};
|
|
/* Top right */
|
|
vertices[1] = (struct triangle_shader_vertex) {
|
|
.pos = quad.p1,
|
|
.uv = { clip.p1.x, clip.p0.y },
|
|
.tint_srgb = tint0_srgb
|
|
};
|
|
/* Bottom right */
|
|
vertices[2] = (struct triangle_shader_vertex) {
|
|
.pos = quad.p2,
|
|
.uv = { clip.p1.x, clip.p1.y },
|
|
.tint_srgb = tint1_srgb
|
|
};
|
|
/* Bottom left */
|
|
vertices[3] = (struct triangle_shader_vertex) {
|
|
.pos = quad.p3,
|
|
.uv = { clip.p0.x, clip.p1.y },
|
|
.tint_srgb = tint1_srgb
|
|
};
|
|
|
|
/* Top / right triangle */
|
|
indices[0] = offset + 0;
|
|
indices[1] = offset + 1;
|
|
indices[2] = offset + 2;
|
|
|
|
/* Bottom / left triangle */
|
|
indices[3] = offset + 0;
|
|
indices[4] = offset + 2;
|
|
indices[5] = offset + 3;
|
|
}
|
|
|
|
void draw_quad_texture(struct gpu_cmd_buffer *cmdbuff, struct draw_texture_params params, struct quad quad)
|
|
{
|
|
draw_quad_texture_ex(cmdbuff, params.texture, params.sprite, params.clip, params.tint, params.tint, quad);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Solid fill shapes
|
|
* ========================== */
|
|
|
|
void draw_poly(struct gpu_cmd_buffer *cmdbuff, struct v2_array array, u32 srgb)
|
|
{
|
|
if (array.count < 3) {
|
|
return;
|
|
}
|
|
|
|
struct gpu_cmd_parameters cmd_params = ZI;
|
|
cmd_params.kind = SHADER_TRIANGLE;
|
|
cmd_params.texture_params.texture = G.solid_white;
|
|
gpu_cmd_buffer_ensure_cmd(cmdbuff, &cmd_params);
|
|
|
|
u32 num_tris = array.count - 2;
|
|
u32 num_indices = num_tris * 3;
|
|
|
|
struct triangle_shader_vertex *vertices = NULL;
|
|
vidx *indices = NULL;
|
|
u32 idx_offset = gpu_cmd_buffer_push_vertices(cmdbuff, (u8 **)&vertices, &indices, array.count, num_indices);
|
|
|
|
/* Fill vertices */
|
|
for (u32 i = 0; i < array.count; ++i) {
|
|
vertices[i] = (struct triangle_shader_vertex) {
|
|
.pos = array.points[i],
|
|
.tint_srgb = srgb
|
|
};
|
|
}
|
|
|
|
/* Fill indices */
|
|
for (u32 i = 0; i < num_tris; ++i) {
|
|
u32 tri_offset = i * 3;
|
|
indices[tri_offset + 0] = idx_offset + 0;
|
|
indices[tri_offset + 1] = idx_offset + (i + 1);
|
|
indices[tri_offset + 2] = idx_offset + (i + 2);
|
|
}
|
|
}
|
|
|
|
void draw_circle(struct gpu_cmd_buffer *cmdbuff, struct v2 pos, f32 radius, u32 srgb, u32 detail)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
struct v2 *points = arena_push_array_no_zero(scratch.arena, struct v2, detail);
|
|
for(u32 i = 0; i < detail; ++i) {
|
|
struct v2 p = V2(
|
|
radius * math_cos(i * (PI * 2.f) / detail),
|
|
radius * math_sin(i * (PI * 2.f) / detail)
|
|
);
|
|
points[i] = v2_add(pos, p);
|
|
}
|
|
|
|
struct v2_array a = {
|
|
.points = points,
|
|
.count = detail
|
|
};
|
|
draw_poly(cmdbuff, a, srgb);
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
void draw_quad(struct gpu_cmd_buffer *cmdbuff, struct quad quad, u32 srgb)
|
|
{
|
|
draw_quad_texture_ex(cmdbuff, G.solid_white, sprite_tag_nil(), CLIP_ALL, srgb, srgb, quad);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Solid line shapes
|
|
* ========================== */
|
|
|
|
void draw_gradient_line(struct gpu_cmd_buffer *cmdbuff, struct v2 start, struct v2 end, f32 thickness, u32 start_srgb, u32 end_srgb)
|
|
{
|
|
struct quad quad = quad_from_line(start, end, thickness);
|
|
draw_quad_texture_ex(cmdbuff, G.solid_white, sprite_tag_nil(), CLIP_ALL, start_srgb, end_srgb, quad);
|
|
}
|
|
|
|
void draw_line(struct gpu_cmd_buffer *cmdbuff, struct v2 start, struct v2 end, f32 thickness, u32 srgb)
|
|
{
|
|
struct quad quad = quad_from_line(start, end, thickness);
|
|
draw_quad(cmdbuff, quad, srgb);
|
|
}
|
|
|
|
void draw_ray(struct gpu_cmd_buffer *cmdbuff, struct v2 pos, struct v2 rel, f32 thickness, u32 srgb)
|
|
{
|
|
struct quad quad = quad_from_ray(pos, rel, thickness);
|
|
draw_quad(cmdbuff, quad, srgb);
|
|
}
|
|
|
|
void draw_poly_line(struct gpu_cmd_buffer *cmdbuff, struct v2_array array, b32 loop, f32 thickness, u32 srgb)
|
|
{
|
|
if (array.count < 2) {
|
|
return;
|
|
}
|
|
|
|
for (u64 i = 1; i < array.count; ++i) {
|
|
struct v2 p1 = array.points[i - 1];
|
|
struct v2 p2 = array.points[i];
|
|
struct quad q = quad_from_line(p1, p2, thickness);
|
|
draw_quad(cmdbuff, q, srgb);
|
|
}
|
|
if (loop && array.count > 2) {
|
|
struct v2 p1 = array.points[array.count - 1];
|
|
struct v2 p2 = array.points[0];
|
|
struct quad q = quad_from_line(p1, p2, thickness);
|
|
draw_quad(cmdbuff, q, srgb);
|
|
}
|
|
}
|
|
|
|
void draw_circle_line(struct gpu_cmd_buffer *cmdbuff, struct v2 pos, f32 radius, f32 thickness, u32 srgb, u32 detail)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
struct v2 *points = arena_push_array_no_zero(scratch.arena, struct v2, detail);
|
|
for (u32 i = 0; i < detail; ++i) {
|
|
struct v2 p = V2(
|
|
radius * math_cos(i * (PI * 2.f) / detail),
|
|
radius * math_sin(i * (PI * 2.f) / detail)
|
|
);
|
|
points[i] = v2_add(pos, p);
|
|
}
|
|
|
|
struct v2_array a = {
|
|
.points = points,
|
|
.count = detail
|
|
};
|
|
draw_poly_line(cmdbuff, a, true, thickness, srgb);
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
void draw_quad_line(struct gpu_cmd_buffer *cmdbuff, struct quad quad, f32 thickness, u32 srgb)
|
|
{
|
|
struct v2 points[] = { quad.p0, quad.p1, quad.p2, quad.p3 };
|
|
struct v2_array a = { .points = points, .count = ARRAY_COUNT(points) };
|
|
draw_poly_line(cmdbuff, a, true, thickness, srgb);
|
|
}
|
|
|
|
void draw_arrow_line(struct gpu_cmd_buffer *cmdbuff, struct v2 start, struct v2 end, f32 thickness, f32 arrowhead_height, u32 srgb)
|
|
{
|
|
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);
|
|
|
|
struct 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);
|
|
|
|
struct v2 head_start = v2_add(end, head_start_dir);
|
|
|
|
struct v2 head_p1_dir = v2_perp_mul(head_start_dir, head_width_ratio);
|
|
struct v2 head_p2_dir = v2_neg(head_p1_dir);
|
|
|
|
struct v2 head_p1 = v2_add(head_start, head_p1_dir);
|
|
struct v2 head_p2 = v2_add(head_start, head_p2_dir);
|
|
|
|
struct v2 head_points[] = { end, head_p1, head_p2 };
|
|
struct v2_array head_points_v2_array = {
|
|
.points = head_points,
|
|
.count = ARRAY_COUNT(head_points)
|
|
};
|
|
draw_poly(cmdbuff, head_points_v2_array, srgb);
|
|
|
|
struct quad line_quad = quad_from_line(start, head_start, thickness);
|
|
draw_quad(cmdbuff, line_quad, srgb);
|
|
}
|
|
|
|
void draw_arrow_ray(struct gpu_cmd_buffer *cmdbuff, struct v2 pos, struct v2 rel, f32 thickness, f32 arrowhead_height, u32 srgb)
|
|
{
|
|
struct v2 end = v2_add(pos, rel);
|
|
draw_arrow_line(cmdbuff, pos, end, thickness, arrowhead_height, srgb);
|
|
}
|
|
|
|
void draw_collider_line(struct gpu_cmd_buffer *cmdbuff, struct xform draw_xf, struct collider_shape shape, struct xform shape_xf, f32 thickness, u32 srgb, u32 detail)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
struct v2 *points = arena_push_array_no_zero(scratch.arena, struct v2, detail);
|
|
for (u32 i = 0; i < detail; ++i) {
|
|
f32 angle = ((f32)i / (f32)detail) * (2 * PI);
|
|
struct v2 dir = V2(math_cos(angle), math_sin(angle));
|
|
struct v2 p = collider_get_support_point(&shape, shape_xf, dir).p;
|
|
p = xform_mul_v2(draw_xf, p);
|
|
points[i] = p;
|
|
}
|
|
|
|
#if 1
|
|
struct v2_array poly = { .points = points, .count = detail };
|
|
draw_poly_line(cmdbuff, poly, true, thickness, srgb);
|
|
#else
|
|
for (u64 i = 0; i < detail; ++i) {
|
|
struct v2 point = points[i];
|
|
(UNUSED)thickness;
|
|
draw_circle(cmdbuff, point, 3, srgb, 10);
|
|
}
|
|
#endif
|
|
|
|
scratch_end(scratch);
|
|
}
|
|
|
|
/* ========================== *
|
|
* Grid
|
|
* ========================== */
|
|
|
|
void draw_grid(struct gpu_cmd_buffer *cmdbuff, struct rect rect, u32 bg0_srgb, u32 bg1_srgb, u32 line_srgb, u32 x_srgb, u32 y_srgb, f32 thickness, f32 spacing, struct v2 offset)
|
|
{
|
|
struct gpu_cmd_parameters cmd_params = ZI;
|
|
cmd_params.kind = SHADER_GRID;
|
|
gpu_cmd_buffer_ensure_cmd(cmdbuff, &cmd_params);
|
|
|
|
struct grid_shader_vertex *vertices = NULL;
|
|
vidx *indices = NULL;
|
|
u32 index_offset = gpu_cmd_buffer_push_vertices(cmdbuff, (u8 **)&vertices, &indices, 4, 6);
|
|
|
|
struct quad quad = quad_from_rect(rect);
|
|
|
|
struct grid_shader_vertex attributes = ZI;
|
|
attributes.bg0_srgb = bg0_srgb;
|
|
attributes.bg1_srgb = bg1_srgb;
|
|
attributes.line_srgb = line_srgb;
|
|
attributes.x_srgb = x_srgb;
|
|
attributes.y_srgb = y_srgb;
|
|
attributes.line_thickness = thickness;
|
|
attributes.line_spacing = spacing;
|
|
attributes.offset = offset;
|
|
|
|
/* Top left */
|
|
vertices[0] = attributes;
|
|
vertices[0].pos = quad.p0;
|
|
/* Top right */
|
|
vertices[1] = attributes;
|
|
vertices[1].pos = quad.p1;
|
|
/* Bottom right */
|
|
vertices[2] = attributes;
|
|
vertices[2].pos = quad.p2;
|
|
/* Bottom left */
|
|
vertices[3] = attributes;
|
|
vertices[3].pos = quad.p3;
|
|
|
|
/* Top / right triangle */
|
|
indices[0] = index_offset + 0;
|
|
indices[1] = index_offset + 1;
|
|
indices[2] = index_offset + 2;
|
|
/* Bottom / left triangle */
|
|
indices[3] = index_offset + 0;
|
|
indices[4] = index_offset + 2;
|
|
indices[5] = index_offset + 3;
|
|
}
|
|
|
|
/* ========================== *
|
|
* Text
|
|
* ========================== */
|
|
|
|
/* Returns the rect that the drawn text spans */
|
|
struct rect draw_text_ex(struct gpu_cmd_buffer *cmdbuff, struct font *font, struct v2 pos, f32 scale, enum draw_text_alignment alignment, enum draw_text_offset_x offset_x, enum draw_text_offset_x offset_y, struct string str)
|
|
{
|
|
struct temp_arena scratch = scratch_begin_no_conflict();
|
|
|
|
f32 inv_font_image_width = 1.0 / (f32)font->image_width;
|
|
f32 inv_font_image_height = 1.0 / (f32)font->image_height;
|
|
f32 line_spacing = font->point_size * 1.5f * scale;
|
|
|
|
struct drawable_glyph {
|
|
f32 off_x;
|
|
f32 off_y;
|
|
f32 width;
|
|
f32 height;
|
|
f32 advance;
|
|
struct clip_rect 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 = NULL;
|
|
struct drawable_line *last_line = NULL;
|
|
f32 first_line_top_offset = 0;
|
|
f32 last_line_bottom_offset = 0;
|
|
|
|
if (str.len > 0) {
|
|
b32 string_done = false;
|
|
struct string_codepoint_iter iter = string_codepoint_iter_begin(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 = arena_dry_push(scratch.arena, struct drawable_glyph);
|
|
|
|
b32 submit_line = false;
|
|
b32 line_done = false;
|
|
while (!line_done) {
|
|
string_done = !string_codepoint_iter_next(&iter);
|
|
if (string_done) {
|
|
line_done = true;
|
|
} else {
|
|
submit_line = true;
|
|
u32 codepoint = iter.codepoint;
|
|
if (codepoint == '\n') {
|
|
line_done = true;
|
|
} else {
|
|
struct drawable_glyph *tg = arena_push(scratch.arena, struct drawable_glyph);
|
|
++num_line_glyphs;
|
|
struct font_glyph *glyph = font_get_glyph(font, codepoint);
|
|
tg->off_x = glyph->off_x * scale;
|
|
tg->off_y = glyph->off_y * scale;
|
|
tg->width = glyph->width * scale;
|
|
tg->height = glyph->height * scale;
|
|
tg->advance = glyph->advance * scale;
|
|
struct rect glyph_atlas_rect = glyph->atlas_rect;
|
|
tg->clip = (struct clip_rect) {
|
|
{
|
|
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 */
|
|
if (submit_line) {
|
|
/* TODO: Only create nodes for non-empty lines. Embed line number in the node. */
|
|
struct drawable_line *node = arena_push(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);
|
|
}
|
|
|
|
struct rect bounds = ZI;
|
|
bounds.x = pos.x;
|
|
bounds.y = pos.y;
|
|
bounds.width = widest_line;
|
|
bounds.height = num_lines * line_spacing + first_line_top_offset + last_line_bottom_offset;
|
|
|
|
/* Offset bounds X */
|
|
switch (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 (offset_y) {
|
|
case DRAW_TEXT_OFFSET_Y_TOP:
|
|
{
|
|
if (first_line) {
|
|
bounds.y -= first_line_top_offset;
|
|
}
|
|
} 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 - first_line_top_offset - last_line_bottom_offset;
|
|
}
|
|
} break;
|
|
}
|
|
|
|
|
|
u64 line_number = 0;
|
|
for (struct drawable_line *line = first_line; line; line = line->next) {
|
|
struct v2 draw_pos = bounds.pos;
|
|
draw_pos.y += line_number * line_spacing;
|
|
|
|
/* Alignment */
|
|
switch (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];
|
|
f32 x = draw_pos.x + tg->off_x;
|
|
f32 y = draw_pos.y + tg->off_y;
|
|
struct quad quad = quad_from_rect(RECT(x, y, tg->width, tg->height));
|
|
draw_quad_texture_ex(cmdbuff, font->texture, sprite_tag_nil(), tg->clip, COLOR_WHITE, COLOR_WHITE, quad);
|
|
draw_pos.x += tg->advance;
|
|
|
|
}
|
|
++line_number;
|
|
}
|
|
|
|
scratch_end(scratch);
|
|
return bounds;
|
|
}
|
|
|
|
/* Returns the rect that the drawn text spans */
|
|
struct rect draw_text(struct gpu_cmd_buffer *cmdbuff, struct font *font, struct v2 pos, struct string str)
|
|
{
|
|
return draw_text_ex(cmdbuff, font, pos, 1.0, DRAW_TEXT_ALIGNMENT_LEFT, DRAW_TEXT_OFFSET_X_LEFT, DRAW_TEXT_OFFSET_Y_TOP, str);
|
|
}
|