~rycwo/forge

b80b6671260e6759ffcce0203e817a30c2e5c46f — Ryan Chan 4 months ago 8c3b0a1
Re-introduce depth buffer for GUI element sorting

In order to support out-of-order draw calls for different types of
GUI elements we have to re-introduce the depth buffer to be able to
correctly occlude element types (e.g. lines, text, ...) that are drawn
later.

This change also temporarily removes support for border drawing for
rects and circles. The intention is to reintroduce borders as line
(and curve) primitives instead.
M include/forge/gui_type.h => include/forge/gui_type.h +9 -5
@@ 124,13 124,17 @@ struct fg_gui_rect {
	float rect[4];
	// unorm4x8: R, G, B, A.
	uint32_t color;
	// unorm4x8: R, G, B, A.
	uint32_t border_color;
	/*
	 * half2x16.
	 * First value is border width, and second value is corner radius.
	 * float.
	 * Could be quantized to half if needed.
	 */
	float corner_radius;
	/*
	 * 32-bit signed integer.
	 * Draw order for the given rect. This is a global value across all drawn
	 * elements that is mapped to a corresponding depth value.
	 */
	uint32_t border;
	int32_t order;
	/*
	 * 32-bit unsigned integer.
	 * 24 bits for the clip index, and the remaining 8 bits for the shape flag.

M shader/gui_rect.frag.glsl => shader/gui_rect.frag.glsl +5 -29
@@ 11,18 11,14 @@

layout(location = 0) in vertex_attr {
	flat vec4 color;
	flat vec4 border_color;
	flat vec2 size;
	flat vec2 center;
	flat float border_width;
	flat float corner_radius;
	flat int circle;
} frag_in;

layout(location = 0) out vec4 color;

const float aa_width = 0.9;

// Distance functions adapted from:
// https://iquilezles.org/www/articles/distfunctions2d/distfunctions2d.htm



@@ 42,30 38,10 @@ main(void) {
	// Express fragment position relative to rect center
	vec2 pos = gl_FragCoord.xy - frag_in.center;

	// Shape excluding border
	vec2 s0 = frag_in.size * 0.5;
	float d0 = rounded_box(pos, s0, frag_in.corner_radius);
	d0 = bool(frag_in.circle) ? circle(pos, s0.x) : d0;

	// Shape including border
	vec2 s1 = s0 + frag_in.border_width;
	float d1 = rounded_box(pos, s1, frag_in.corner_radius);
	d1 = bool(frag_in.circle) ? circle(pos, s1.x) : d1;

	float dd0 = fwidth(d0) * aa_width;
	color = frag_in.color * (1.0 - smoothstep(-dd0, dd0, d0));

	// Subtract the shape to w/o border from the full shape to get only the
	// border. This allows the border and fill to have separate translucencies.
	// FIXME: Anti-aliasing on the outer edge of the fill and the inner edge of
	// the border creates a small translucent gap through which elements beneath
	// peek through.
	d1 = mix(1.0, max(-d0, d1), float(frag_in.border_width > 0.0));

	float dd1 = fwidth(d1) * aa_width;
	color = mix(color, frag_in.border_color, 1.0 - smoothstep(-dd1, dd1, d1));
	vec2 halfwidth = frag_in.size * 0.5;
	float d = bool(frag_in.circle)
		? circle(pos, halfwidth.x)
		: rounded_box(pos, halfwidth, frag_in.corner_radius);

	// Un-premultiply alpha to avoid artifacts when blending anti-aliased
	// translucent pixels. TODO: Does this break translucent fills?
	color.xyz *= 1.0 / color.w;
	color = frag_in.color * float(d < 0.0);
}

M shader/gui_rect.vert.glsl => shader/gui_rect.vert.glsl +12 -26
@@ 13,8 13,8 @@
struct primitive {
	vec4 rect;
	uint color;
	uint border_color;
	uint border;
	float corner_radius;
	int order;
	uint flags;
};



@@ 33,19 33,14 @@ layout(std430, binding = 1) restrict readonly buffer prim_buffer {

layout(location = 0) out vertex_attr {
	flat vec4 color;
	flat vec4 border_color;
	flat vec2 size;
	flat vec2 center;
	flat float border_width;
	flat float corner_radius;
	flat int circle;
} vert_out;

const int clip_mask = 0xFFFFFF;
const vec2 quad[6] = vec2[6](
	vec2(1.0, 0.0), vec2(0.0, 0.0), vec2(1.0, 1.0),
	vec2(1.0, 1.0), vec2(0.0, 0.0), vec2(0.0, 1.0)
);
const int quad_vertices[6] = int[6](0, 1, 2, 2, 1, 3);

void
main(void) {


@@ 60,44 55,35 @@ main(void) {
	// v0 └─────┘ v1

	primitive prim = prims[gl_VertexID / 6];
	int vertex = quad_vertices[gl_VertexID % 6];

	vec2 v2 = prim.rect.xy;
	vec2 v1 = prim.rect.zw + v2;

	vec4 color = unpackUnorm4x8(prim.color);
	vec4 border_color = unpackUnorm4x8(prim.border_color);

	vec2 border = unpackHalf2x16(prim.border);
	float border_width = border[0];
	float corner_radius = border[1];

	uint clip_index = prim.flags & clip_mask;
	uint shape = (prim.flags & (~clip_mask)) >> 24;

	// Compute size without border and extensions
	vec2 size = v1 - v2;

	vec2 center = (projection * vec4(v2 + size * 0.5, 0.0, 1.0)).xy;

	// Rect is expanded by an additional pixel to accomodate for anti-aliasing
	v2 -= vec2(border_width + 1.0);
	v1 += vec2(border_width + 1.0);

	// Bear in mind the Y-axis is flipped in the window-space to NDC
	// transformation when we multiply the position by the projection matrix.
	vec2 position = mix(v1, v2, quad[gl_VertexID % 6]);
	vec2 position;
	position.x = bool(vertex & 1) ? v1.x : v2.x;
	position.y =     (vertex < 2) ? v1.y : v2.y;

	// Apply clip rect
	vec4 clip = clips[clip_index];
	position = clamp(position, clip.xy, clip.xy + clip.zw);

	gl_Position = projection * vec4(position, 0.0, 1.0);
	float depth = (1.0 - (float(prim.order) / float(0xFFFFFF)) * 2.0) - 1.0;

	gl_Position = projection * vec4(position, depth, 1.0);

	vert_out.color = color;
	vert_out.border_color = border_color;
	vert_out.border_width = border_width;
	vert_out.corner_radius = corner_radius;
	vert_out.color = unpackUnorm4x8(prim.color);
	vert_out.size = size;
	vert_out.center = (center * 0.5 + 0.5) * viewport;
	vert_out.corner_radius = prim.corner_radius;
	vert_out.circle = int(shape);
}

M src/gui_draw.c => src/gui_draw.c +24 -20
@@ 18,7 18,20 @@
#include "forge/gui_util.h"
#include "forge/pack_float.h"

#define PRIM_CLIP_MASK (0xFFFFFF)
#define CLIP_MASK (0xFFFFFF)

static int32_t
next_prim_id(struct fg_gui_context* context) {
	if (context->layer == FG_GUI_LAYER_OVERLAY) {
		// Order of overlay items is offset so that depth-wise they are always
		// above base layer items at draw time. Currently hardcoded to an offset
		// of the 16-bit integer max, assuming base layer items will not exceed
		// this number.
		int32_t const count = (context->overlay_count)++;
		return (int32_t)INT16_MAX + count;
	}
	return (context->base_count)++;
}

static bool
make_gui_rect(


@@ 27,9 40,8 @@ make_gui_rect(
		fg_vec4 const rect,
		struct fg_gui_rect* prim) {
	memset(prim, 0, sizeof(struct fg_gui_rect));
	bool const border = style->flags & FG_GUI_STYLE_BORDER;
	// Filter out completely transparent rects
	if (!border && fabs(style->color[3]) <= 1e-6)
	// Filter out transparent rects
	if (fabs(style->color[3]) <= 1e-6) // FIXME
		return false;

	fg_vec4 rect_;


@@ 42,27 54,19 @@ make_gui_rect(
		color[i] = (float)style->color[i];
	prim->color = fg_pack_unorm_4x8(color);

	float border_properties[2] = {0.0, 0.0};
	if (border) {
		// Border alpha is ignored because it is a pain to deal with
		float border_color[4] = {
			(float)style->border_color[0],
			(float)style->border_color[1],
			(float)style->border_color[2],
			1.0f
		};
		prim->border_color = fg_pack_unorm_4x8(border_color);
		border_properties[0] = (float)style->border_width;
	}
	if (style->flags & FG_GUI_STYLE_ROUNDED_CORNERS)
		border_properties[1] = (float)style->corner_radius;
	prim->border = fg_pack_half_2x16(border_properties);
	prim->flags = ((uint32_t)context->clip) & PRIM_CLIP_MASK;
		prim->corner_radius = (float)style->corner_radius;
	prim->order = next_prim_id(context);
	prim->flags = ((uint32_t)context->clip) & CLIP_MASK;
	return true;
}

static inline void
push_rect(struct fg_gui_context* context, struct fg_gui_rect prim) {
	// FIXME: Does rect order matter any more considering depth is used to sort
	// elements in the draw call? Element draw order will affect translucency
	// but if we have to interlave different types of elements that will be
	// ruined anyway.
	struct fg_gui_rect_buffer* buffer = &context->rects;
	if (context->layer == FG_GUI_LAYER_OVERLAY) {
		int const size = (buffer->size_back)++;


@@ 104,4 108,4 @@ fg_draw_gui_circle(
	push_rect(context, prim);
}

#undef PRIM_CLIP_MASK
#undef CLIP_MASK

M src/gui_opengl_46.c => src/gui_opengl_46.c +5 -6
@@ 146,11 146,6 @@ load_rect_program(struct fg_allocator const* alloc) {
}

static GLuint
load_text_program(struct fg_allocator const* alloc) {
	return 0; // TODO
}

static GLuint
load_line_program(struct fg_allocator const* alloc) {
	return 0; // TODO
}


@@ 286,7 281,8 @@ fg_draw_gui_opengl_46(struct fg_gui_context* context) {
			0, sizeof(struct shader_context),
			(void const*)&shd_context);

	glDepthMask(GL_FALSE);  // Disable depth writes
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LESS);

	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);


@@ 294,6 290,9 @@ fg_draw_gui_opengl_46(struct fg_gui_context* context) {

	draw_rects(context);

	// Depth mask *must* be re-enabled so that the next call to
	// glClear(GL_DEPTH_BUFFER_BIT) is able to write to the depth buffer.
	glDepthMask(GL_TRUE);
	glBindVertexArray(0);
	glUseProgram(0);
}