~rycwo/forge

5f26ce94ed885c67590d71e8ae28a29613c4ef2c — Ryan Chan 6 months ago 4dcd28c
Apply active gui transform before pushing prims
M include/forge/gui.h => include/forge/gui.h +1 -0
@@ 8,6 8,7 @@
#include "forge/gui_input.h"
#include "forge/gui_layout.h"
#include "forge/gui_type.h"
#include "forge/gui_util.h"
#include "forge/gui_window.h"

#endif  // GUI_H

M include/forge/gui_layout.h => include/forge/gui_layout.h +3 -3
@@ 53,13 53,13 @@ pop_next_gui_position(

/*
 * Set the transform for subsequent elements.
 * Note that transforms cannot be nested.
 * Note that new transforms replace the previous transform.
 */
void
begin_gui_transform(struct gui_context* context, mat4 const transform);
set_gui_transform(struct gui_context* context, mat4 const transform);

void
end_gui_transform(struct gui_context* context);
set_gui_transform_identity(struct gui_context* context);

/*
 * Set the clip rect for subsequent elements.

M include/forge/gui_type.h => include/forge/gui_type.h +19 -18
@@ 2,6 2,7 @@
#define GUI_TYPE_H

#include <stdalign.h>
#include <stdbool.h>
#include <stdint.h>

#include <GL/glew.h>


@@ 47,7 48,6 @@ struct gui_rect {
	float border_width;
	float corner_radius;
	int32_t circle;
	int32_t transform;
	int32_t clip;
	int32_t order;
};


@@ 65,40 65,41 @@ struct gui_context {
	struct allocator alloc;
	struct gui_input input;

	/// Stack of container rects expressed in relative coordinates.
	// Stack of container rects expressed in relative coordinates.
	struct vec4_buffer layout;
	/// Absolute pos of the top container of the layout stack.
	// Absolute pos of the top container of the layout stack.
	vec2 layout_top;
	/// Absolute pos of the next element.
	// Absolute pos of the next element.
	vec2 next_pos;

	/// Id of the active element.
	// Active transform to apply to elements before they are added to the
	// primitive buffer.
	mat4 transform;
	bool transform_active;

	// Id of the active element.
	uint64_t active;
	/// Id of the currently hovered element. See next_hover.
	// Id of the currently hovered element. See next_hover.
	uint64_t hover;
	/// Id of the actual item hovered on the current frame. We introduce a
	/// frame delay in order to correctly resolve interaction of overlapping
	/// elements. See gui_item_hovered() for implementation details.
	// Id of the actual item hovered on the current frame. We introduce a
	// frame delay in order to correctly resolve interaction of overlapping
	// elements. See gui_item_hovered() for implementation details.
	uint64_t next_hover;
	/// Layer of the next_hover item.
	// Layer of the next_hover item.
	int next_hover_layer;

	/// Currently active layer. See gui_layer.
	// Currently active layer. See gui_layer.
	int layer;
	/// Index of the active transform buffer element.
	int transform;
	/// Index of the active clip buffer element.
	// Index of the active clip buffer element.
	int clip;

	/// Number of elements pushed to base layer.
	// Number of elements pushed to base layer.
	int32_t base_count;
	/// Number of elements pushed to overlay layer.
	// Number of elements pushed to overlay layer.
	int32_t overlay_count;

	// Draw buffers
	struct mat4_buffer transforms;
	struct vec4_buffer clips;

	struct gui_rect_buffer rects;
};


A include/forge/gui_util.h => include/forge/gui_util.h +17 -0
@@ 0,0 1,17 @@
#ifndef GUI_UTIL_H
#define GUI_UTIL_H

#include "forge/type.h"
#include "forge/gui_type.h"

/*
 * Transform the rect by the active transform.
 * Note this does not take rotations into account.
 */
void
gui_transform_rect(
		struct gui_context const* context,
		vec4 out,
		vec4 const rect);

#endif  // GUI_UTIL_H

M meson.build => meson.build +2 -0
@@ 74,6 74,7 @@ lib = library(
        'src/gui_input.c',
        'src/gui_layout.c',
        'src/gui_opengl_46.c',
        'src/gui_util.c',
        'src/gui_window.c',
        'src/hash_table.c',
        'src/memory_pool.c',


@@ 109,6 110,7 @@ install_headers(
        'include/forge/gui_input.h',
        'include/forge/gui_layout.h',
        'include/forge/gui_type.h',
        'include/forge/gui_util.h',
        'include/forge/gui_window.h',
        'include/forge/hash_table.h',
        'include/forge/input.h',

M shader/gui_rect.vert.glsl => shader/gui_rect.vert.glsl +4 -11
@@ 7,7 7,6 @@ struct primitive {
	float border_width;
	float corner_radius;
	int circle;
	int transform;
	int clip;
	int order;
};


@@ 17,15 16,11 @@ layout(std140, binding = 0) uniform context {
	vec2 viewport;
};

layout(std430, binding = 0) restrict readonly buffer transform_buffer {
	mat4 transforms[];
};

layout(std430, binding = 1) restrict readonly buffer clip_buffer {
layout(std430, binding = 0) restrict readonly buffer clip_buffer {
	vec4 clips[];
};

layout(std430, binding = 2) restrict readonly buffer prim_buffer {
layout(std430, binding = 1) restrict readonly buffer prim_buffer {
	primitive prims[];
};



@@ 57,10 52,8 @@ main(void) {
	primitive prim = prims[gl_VertexID / 6];
	int vertex = quad_vertices[gl_VertexID % 6];

	mat4 transform = transforms[prim.transform];

	vec4 v2 = transform * vec4(prim.rect.xy, 0.0, 1.0);
	vec4 v1 = transform * vec4(prim.rect.xy + prim.rect.zw, 0.0, 1.0);
	vec4 v2 = vec4(prim.rect.xy, 0.0, 1.0);
	vec4 v1 = vec4(prim.rect.xy + prim.rect.zw, 0.0, 1.0);

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

M src/gui_context.c => src/gui_context.c +6 -10
@@ 26,12 26,10 @@ init_gui(
	context->layout.capacity = desc.layout_stack_size;
	context->layout.size = 1;

	// FIXME(ryc): Receive transform and clip buffer sizes as arguments
	context->transforms.buf = (mat4*)alloc->alloc(sizeof(mat4) * 128);
	mat4_identity(context->transforms.buf[0]);
	context->transforms.capacity = 128;
	context->transforms.size = 1;
	mat4_identity(context->transform);
	context->transform_active = false;

	// FIXME(ryc): Receive clip buffer size as argument
	// Default clip is initialized in begin_gui_frame()
	context->clips.buf = (vec4*)alloc->alloc(sizeof(vec4) * 128);
	context->clips.capacity = 128;


@@ 49,7 47,6 @@ init_gui(

void
terminate_gui(struct gui_context* context) {
	context->alloc.free(context->transforms.buf);
	context->alloc.free(context->clips.buf);
	context->alloc.free(context->rects.prims);
	memset(context, 0, sizeof(struct gui_context));


@@ 65,7 62,6 @@ begin_gui_frame(struct gui_context* context) {
	context->base_count = 0;
	context->overlay_count = 0;

	context->transforms.size = 1;
	vec2 viewport_size;
	gui_viewport_size(context, viewport_size);
	vec4_set(


@@ 85,9 81,9 @@ commit_gui_frame(struct gui_context* context) {
			"Base layer must be active before commit_gui_frame(). "
			"Forgot to call set_gui_layer(GUI_LAYER_BASE)?\n");
	expect(
			context->transform == 0,
			"Transform must be reset before commit_gui_frame(). "
			"Forgot to call end_gui_transform()?\n");
			!context->transform_active,
			"Transform must be reset to identity before commit_gui_frame(). "
			"Forgot to call set_gui_transform_identity()?\n");
	expect(
			context->clip == 0,
			"Clip must be reset before commit_gui_frame(). "

M src/gui_draw.c => src/gui_draw.c +4 -5
@@ 6,6 6,7 @@
#include <string.h>

#include "forge/almost_equal.h"
#include "forge/gui_util.h"
#include "forge/vector.h"

static int32_t


@@ 34,15 35,13 @@ make_gui_rect(
	// Filter out completely transparent rects
	if (!border && fabs(style->color[3]) <= 1e-6)
		return false;
	vec4_copy(prim->rect, rect);
	gui_transform_rect(context, prim->rect, rect);
	vec4_copy(prim->color, style->color);
	if (border) {
		// Border alpha is ignored for now. In the future borders may
		// become a separate primitive/element.
		// Border alpha is ignored cause it's a pain to deal with
		vec4_copy_vec3(prim->border_color, style->border_color, 1.0);
		prim->border_width = style->border_width;
	}
	prim->transform = context->transform;
	prim->clip = context->clip;
	prim->order = next_prim_id(context);
	return true;


@@ 76,7 75,7 @@ draw_gui_circle(
		struct gui_context* context,
		struct gui_style const* style,
		vec2 const center,
		float const radius) {
		float radius) {
	float const r2 = radius + radius;
	vec4 const rect = {center[0] - radius, center[1] - radius, r2, r2};
	struct gui_rect prim;

M src/gui_graph.c => src/gui_graph.c +4 -6
@@ 115,11 115,9 @@ begin_gui_node(
	if (pressed) {
		// Apply scale to the mouse position delta in the space of
		// the nodes (according to the active transform).
		mat4 transform;
		mat4_copy(transform, context->transforms.buf[context->transform]);
		vec2 const scale = {
			1.0 / mat4_get(transform, 0, 0),
			1.0 / mat4_get(transform, 1, 1),
			1.0 / mat4_get(context->transform, 0, 0),
			1.0 / mat4_get(context->transform, 1, 1),
		};
		vec2 delta;
		vec2_mul(delta, context->input.cursor_pos_delta, scale);


@@ 248,11 246,11 @@ begin_gui_canvas(
	mat4_set(T, 0, 3, tx);
	mat4_set(T, 1, 3, ty);

	begin_gui_transform(context, T);
	set_gui_transform(context, T);
}

void
end_gui_canvas(struct gui_context* context) {
	end_gui_transform(context);
	set_gui_transform_identity(context);
	end_gui_clip(context);
}

M src/gui_input.c => src/gui_input.c +4 -25
@@ 2,6 2,7 @@

#include <assert.h>

#include "forge/gui_util.h"
#include "forge/input.h"
#include "forge/matrix.h"
#include "forge/stretchy_buffer.h"


@@ 13,16 14,6 @@ check_bounds(vec2 const pos, vec4 const rect) {
		&& pos[1] > rect[1] && pos[1] < (rect[1] + rect[3]);
}

static inline void
transform_rect(vec4 out, mat4 const transform, vec4 const rect) {
	vec4 v1 = {rect[0], rect[1], 0.0, 1.0};
	vec4 v2 = {rect[0] + rect[2], rect[1] + rect[3], 0.0, 1.0};
	mat4_mul_vec4(out, transform, v1);
	mat4_mul_vec4(v1, transform, v2);
	out[2] = v1[0] - out[0];
	out[3] = v1[1] - out[1];
}

void
set_gui_cursor_position(struct gui_input* input, vec2 const pos) {
	vec2_sub(input->cursor_pos_delta, pos, input->cursor_pos);


@@ 52,22 43,10 @@ set_gui_viewport_size(struct gui_context* context, vec2 const size) {

bool
gui_item_hovered(struct gui_context* context, uint64_t id, vec4 const rect) {
	// Transform the rect by the active transform. We still pass the transform
	// in to the shader to draw since we don't want to have to transform all
	// elements on the client side - only the interactive ones.
	//
	// This should save CPU time, but unfortunately means that there *may* be
	// subtle bugs as a result of some operations taking the transform into
	// account and some not.
	//
	// Note this does not take rotations into account.
	vec4 rect_tr;
	transform_rect(
			rect_tr,
			context->transforms.buf[context->transform],
			rect);
	vec4 transformed_rect;
	gui_transform_rect(context, transformed_rect, rect);
	if (context->layer >= context->next_hover_layer
			&& check_bounds(context->input.cursor_pos, rect_tr)) {
			&& check_bounds(context->input.cursor_pos, transformed_rect)) {
		context->next_hover = id;
		context->next_hover_layer = context->layer;
	}

M src/gui_layout.c => src/gui_layout.c +7 -10
@@ 114,19 114,16 @@ pop_next_gui_position(
}

void
begin_gui_transform(struct gui_context* context, mat4 const transform) {
	// Transforms cannot be nested. The existing transform must end first.
	assert(!context->transform);
	assert(context->transforms.size < context->transforms.capacity);
	int const i = context->transforms.size++;
	mat4_copy(context->transforms.buf[i], transform);
	context->transform = i;
set_gui_transform(struct gui_context* context, mat4 const transform) {
	// Transforms cannot be "nested", new transforms replace the previous one
	mat4_copy(context->transform, transform);
	context->transform_active = true;
}

void
end_gui_transform(struct gui_context* context) {
	assert(context->transform);
	context->transform = 0;
set_gui_transform_identity(struct gui_context* context) {
	mat4_identity(context->transform);
	context->transform_active = false;
}

void

M src/gui_opengl_46.c => src/gui_opengl_46.c +3 -16
@@ 21,7 21,6 @@ struct opengl_prim {

struct opengl_resources {
	GLuint vert_array;  // Dummy. A VAO is necessary to call glDrawArrays().
	GLuint transforms;
	GLuint clips;
	struct opengl_prim rects;
};


@@ 140,7 139,6 @@ draw_rects(int offset, int count) {
		return;

	GLuint const buffers[] = {
		resources_g.transforms,
		resources_g.clips,
		resources_g.rects.prims,
	};


@@ 162,12 160,6 @@ init_gui_opengl_46(struct gui_context* context) {

	glCreateVertexArrays(1, &resources_g.vert_array);

	glCreateBuffers(1, &resources_g.transforms);
	glNamedBufferData(
			resources_g.transforms,
			sizeof(mat4) * context->transforms.capacity,
			NULL, GL_STREAM_DRAW);

	glCreateBuffers(1, &resources_g.clips);
	glNamedBufferData(
			resources_g.clips,


@@ 190,7 182,6 @@ init_gui_opengl_46(struct gui_context* context) {
void
term_gui_opengl_46(void) {
	glDeleteProgram(resources_g.rects.program);
	glDeleteBuffers(1, &resources_g.transforms);
	glDeleteBuffers(1, &resources_g.clips);
	glDeleteBuffers(1, &resources_g.rects.uniform);
	glDeleteBuffers(1, &resources_g.rects.prims);


@@ 199,10 190,6 @@ term_gui_opengl_46(void) {
void
commit_gui_opengl_46(struct gui_context* context) {
	glNamedBufferSubData(
			resources_g.transforms,
			0, sizeof(mat4) * context->transforms.size,
			(void const*)context->transforms.buf);
	glNamedBufferSubData(
			resources_g.clips,
			0, sizeof(vec4) * context->clips.size,
			(void const*)context->clips.buf);


@@ 220,9 207,9 @@ void
draw_gui_opengl_46(struct gui_context* context) {
	// Note that using vertex pulling is more in-line with new graphics
	// programming practices natively available with newer APIs such as Vulkan
	// and DirectX. Rumors are floating about that doing this in OpenGL *may*
	// circumvent the post-transform cache, which could negatively affect
	// performance. This assumption has yet to be tested.
	// and DirectX. Doing this in OpenGL *may* circumvent the post-transform
	// cache, which could negatively affect performance. This assumption has
	// yet to be tested.
	// https://www.khronos.org/opengl/wiki/Post_Transform_Cache

	// Bind dummy vertex array to pass glDrawArrays() checks

A src/gui_util.c => src/gui_util.c +21 -0
@@ 0,0 1,21 @@
#include "forge/gui_util.h"

#include "forge/matrix.h"
#include "forge/vector.h"

void
gui_transform_rect(
		struct gui_context const* context,
		vec4 out,
		vec4 const rect) {
	if (!context->transform_active) {
		vec4_copy(out, rect);
		return;
	}
	vec4 v1 = {rect[0], rect[1], 0.0, 1.0};
	vec4 v2 = {rect[0] + rect[2], rect[1] + rect[3], 0.0, 1.0};
	mat4_mul_vec4(out, context->transform, v1);
	mat4_mul_vec4(v1, context->transform, v2);
	out[2] = v1[0] - out[0];
	out[3] = v1[1] - out[1];
}