CWebRender pt2

This commit is contained in:
wuggy 2026-09-18 12:00:30 -07:00
commit fe181d242f
22 changed files with 1623 additions and 489 deletions

View file

@ -7,7 +7,14 @@ EXPORTS.mozilla.gfx += [
]
SOURCES += [
'webrender.c',
'wr_context.c',
'wr_display_list.c',
'wr_frame.c',
'wr_frame_packet.c',
'wr_retained.c',
'wr_util.c',
]
FINAL_LIBRARY = 'xul'
TEST_DIRS += ['tests']

5
gfx/wr/tests/moz.build Normal file
View file

@ -0,0 +1,5 @@
# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
# This is a standalone C regression executable. It includes the C renderer
# implementation directly so it does not require another language's harness.
SimplePrograms(['webrender_test'], ext='.c')

View file

@ -0,0 +1,274 @@
#include "../wr_util.c"
#include "../wr_context.c"
#include "../wr_display_list.c"
#include "../wr_frame.c"
#include "../wr_frame_packet.c"
#include "../wr_retained.c"
#include <assert.h>
static void
test_retained_frame(void)
{
wr_context* context = wr_context_create(4);
const wr_frame* first;
const wr_frame* cached;
const wr_frame* culled;
assert(context);
wr_context_set_viewport(context, (wr_rect){ 0, 0, 100, 100 });
assert(wr_display_list_push_rect(context, (wr_rect){ 0, 0, 10, 10 },
(wr_color){ 1, 0, 0, 1 }));
first = wr_context_build_frame(context);
cached = wr_context_build_frame(context);
assert(first == cached);
assert(cached->quad_count == 1 && cached->batch_count == 1);
wr_context_set_viewport(context, (wr_rect){ 200, 200, 10, 10 });
culled = wr_context_build_frame(context);
assert(culled && culled->quad_count == 0);
wr_context_destroy(context);
}
static void
test_stacking_and_image(void)
{
wr_context* context = wr_context_create(4);
const wr_frame* frame;
const wr_transform translate = { 1, 0, 0, 1, 20, 0 };
assert(context);
wr_context_set_viewport(context, (wr_rect){ 0, 0, 100, 100 });
assert(wr_context_register_image(context, 7, 64, 64));
assert(wr_display_list_push_stacking_context(context, translate, 0.5f));
assert(wr_display_list_push_clip(context, (wr_rect){ 15, 0, 30, 30 }));
assert(wr_display_list_push_rect(context, (wr_rect){ 0, 0, 10, 10 },
(wr_color){ 1, 0, 0, 1 }));
wr_display_list_pop_stacking_context(context);
assert(wr_display_list_push_image(
context, (wr_rect){ 30, 0, 20, 20 }, 7,
(wr_rect){ 0, 0, 0.5f, 0.5f }, (wr_color){ 0.25f, 0.5f, 0.75f, 1 }));
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 2 && frame->batch_count == 2);
assert(frame->quads[0].color.a == 0.5f);
assert(frame->batches[0].has_clip);
assert(frame->batches[0].transform.m31 == 20);
assert(frame->batches[1].kind == WR_PRIMITIVE_IMAGE);
assert(frame->batches[1].image_key == 7);
assert(frame->quads[1].color.r == 0.25f);
assert(frame->quads[1].color.g == 0.5f);
assert(frame->quads[1].color.b == 0.75f);
assert(frame->quads[1].tex_rect.width == 0.5f);
wr_context_unregister_image(context, 7);
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 1 && frame->batch_count == 1);
wr_context_clear(context);
assert(!wr_display_list_push_image(
context, (wr_rect){ 30, 0, 20, 20 }, 7,
(wr_rect){ 0, 0, 0.5f, 0.5f }, (wr_color){ 1, 1, 1, 1 }));
wr_context_destroy(context);
}
static void
test_scene_transactions(void)
{
wr_context* context = wr_context_create(2);
const wr_frame* frame;
assert(context);
wr_context_set_viewport(context, (wr_rect){ 0, 0, 100, 100 });
assert(wr_display_list_push_rect(context, (wr_rect){ 0, 0, 10, 10 },
(wr_color){ 1, 0, 0, 1 }));
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 1);
assert(wr_context_begin_transaction(context));
assert(wr_display_list_push_rect(context, (wr_rect){ 20, 0, 10, 10 },
(wr_color){ 0, 1, 0, 1 }));
wr_context_abort_transaction(context);
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 1);
assert(wr_context_begin_transaction(context));
assert(wr_display_list_push_rect(context, (wr_rect){ 20, 0, 10, 10 },
(wr_color){ 0, 1, 0, 1 }));
assert(wr_context_begin_transaction(context));
assert(wr_display_list_push_rect(context, (wr_rect){ 40, 0, 10, 10 },
(wr_color){ 0, 0, 1, 1 }));
wr_context_abort_transaction(context);
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 2);
assert(wr_context_commit_transaction(context));
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 2);
assert(!wr_context_commit_transaction(context));
wr_context_destroy(context);
}
static void
test_multiple_image_resources(void)
{
wr_context* context = wr_context_create(4);
const wr_frame* frame;
assert(context);
wr_context_set_viewport(context, (wr_rect){ 0, 0, 100, 100 });
assert(wr_context_register_image(context, 11, 32, 32));
assert(wr_context_register_image(context, 12, 64, 64));
assert(wr_display_list_push_image(
context, (wr_rect){ 0, 0, 20, 20 }, 11,
(wr_rect){ 0, 0, 1, 1 }, (wr_color){ 1, 1, 1, 1 }));
assert(wr_display_list_push_image(
context, (wr_rect){ 20, 0, 20, 20 }, 12,
(wr_rect){ 0, 0, 1, 1 }, (wr_color){ 1, 1, 1, 1 }));
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 2 && frame->batch_count == 2);
assert(frame->batches[0].image_key == 11);
assert(frame->batches[1].image_key == 12);
wr_context_clear_images(context);
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 0 && frame->batch_count == 0);
wr_context_destroy(context);
}
static void
test_stable_item_updates(void)
{
wr_context* context = wr_context_create(4);
const wr_frame* frame;
const wr_transform identity = { 1, 0, 0, 1, 0, 0 };
wr_item_id item;
assert(context);
wr_context_set_viewport(context, (wr_rect){ 0, 0, 100, 100 });
item = wr_display_list_push_rect_with_id(
context, 42, (wr_rect){ 0, 0, 10, 10 }, (wr_color){ 1, 0, 0, 1 });
assert(item == 42);
assert(wr_display_list_push_rect_with_id(
context, 42, (wr_rect){ 20, 0, 10, 10 }, (wr_color){ 0, 1, 0, 1 }) == 0);
assert(wr_context_begin_transaction(context));
assert(wr_display_list_update_rect(
context, item, (wr_rect){ 20, 0, 10, 10 },
(wr_color){ 0, 1, 0, 1 }, identity));
wr_context_abort_transaction(context);
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 1);
assert(frame->quads[0].rect.x == 0);
assert(wr_context_begin_transaction(context));
assert(wr_display_list_update_rect(
context, item, (wr_rect){ 20, 0, 10, 10 },
(wr_color){ 0, 1, 0, 1 }, identity));
assert(wr_context_commit_transaction(context));
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 1);
assert(frame->quads[0].rect.x == 20);
assert(frame->quads[0].item_id == 42);
assert(wr_context_remove_item(context, item));
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 0);
wr_context_destroy(context);
}
static void
test_opaque_occlusion(void)
{
wr_context* context = wr_context_create(2);
const wr_frame* frame;
assert(context);
wr_context_set_viewport(context, (wr_rect){ 0, 0, 100, 100 });
assert(wr_display_list_push_rect(
context, (wr_rect){ 0, 0, 20, 20 }, (wr_color){ 1, 0, 0, 1 }));
assert(wr_display_list_push_rect(
context, (wr_rect){ 0, 0, 20, 20 }, (wr_color){ 0, 0, 1, 1 }));
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 1 && frame->batch_count == 1);
assert(frame->quads[0].color.b == 1.0f);
wr_context_destroy(context);
}
static void
test_gpu_tessellated_primitives(void)
{
wr_context* context = wr_context_create(64);
const wr_frame* frame;
assert(context);
wr_context_set_viewport(context, (wr_rect){ 0, 0, 100, 100 });
assert(wr_display_list_push_linear_gradient(
context, (wr_rect){ 0, 0, 40, 20 },
(wr_color){ 1, 0, 0, 1 }, (wr_color){ 0, 0, 1, 1 }, 1));
assert(wr_display_list_push_rounded_rect(
context, (wr_rect){ 0, 20, 40, 20 }, (wr_color){ 0, 1, 0, 1 }, 8));
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 48);
wr_context_destroy(context);
}
static void
test_glyph_run(void)
{
wr_context* context = wr_context_create(4);
const wr_frame* frame;
const wr_glyph glyphs[2] = {
{ { 0, 0, 8, 12 }, { 0, 0, 0.25f, 1 } },
{ { 8, 0, 8, 12 }, { 0.25f, 0, 0.25f, 1 } }
};
assert(context);
wr_context_set_viewport(context, (wr_rect){ 0, 0, 100, 100 });
assert(wr_context_register_image(context, 21, 256, 256));
assert(wr_display_list_push_glyph_run(
context, 21, glyphs, 2, (wr_color){ 1, 1, 1, 1 }));
frame = wr_context_build_frame(context);
assert(frame && frame->quad_count == 2 && frame->batch_count == 1);
assert(frame->batches[0].kind == WR_PRIMITIVE_IMAGE);
wr_context_destroy(context);
}
static void
test_frame_packet(void)
{
wr_context* context = wr_context_create(2);
const wr_frame* frame;
size_t packet_size;
unsigned char* packet;
wr_frame_packet_header header;
assert(context);
wr_context_set_viewport(context, (wr_rect){ 0, 0, 100, 100 });
assert(wr_display_list_push_rect(
context, (wr_rect){ 0, 0, 10, 10 }, (wr_color){ 1, 0, 0, 1 }));
frame = wr_context_build_frame(context);
packet_size = wr_frame_packet_size(frame);
assert(packet_size > sizeof(header));
packet = (unsigned char*)malloc(packet_size);
assert(packet);
assert(wr_frame_serialize(frame, packet, packet_size));
memcpy(&header, packet, sizeof(header));
assert(header.version == WR_FRAME_PACKET_VERSION);
assert(header.quad_count == 1 && header.batch_count == 1);
free(packet);
wr_context_destroy(context);
}
int
main(void)
{
test_retained_frame();
test_stacking_and_image();
test_scene_transactions();
test_multiple_image_resources();
test_stable_item_updates();
test_opaque_occlusion();
test_gpu_tessellated_primitives();
test_glyph_run();
test_frame_packet();
return 0;
}

View file

@ -1,447 +0,0 @@
#include "webrender.h"
#include <float.h>
#include <math.h>
#include <stdlib.h>
typedef struct {
wr_rect rect;
wr_color color;
wr_transform transform;
wr_rect clip_rect;
uint8_t has_clip;
} wr_rect_command;
typedef struct {
wr_transform transform;
float opacity;
size_t clip_depth;
} wr_display_state;
struct wr_context {
wr_rect_command* commands;
size_t command_count;
size_t command_capacity;
wr_gpu_quad* quads;
size_t quad_count;
size_t quad_capacity;
wr_gpu_batch* batches;
size_t batch_count;
size_t batch_capacity;
wr_rect viewport;
wr_transform transform;
float opacity;
wr_rect* clip_stack;
size_t clip_depth;
size_t clip_capacity;
wr_display_state* state_stack;
size_t state_depth;
size_t state_capacity;
wr_frame frame;
};
static int
wr_size_mul_overflow(size_t a, size_t b)
{
return b != 0 && a > SIZE_MAX / b;
}
static int
wr_reserve(void** data, size_t* capacity, size_t count, size_t element_size)
{
size_t new_capacity;
void* new_data;
if (count <= *capacity)
return 1;
if (element_size == 0 || wr_size_mul_overflow(count, element_size))
return 0;
new_capacity = *capacity ? *capacity : 8;
while (new_capacity < count) {
if (new_capacity > SIZE_MAX / 2) {
new_capacity = count;
break;
}
new_capacity *= 2;
}
if (wr_size_mul_overflow(new_capacity, element_size))
return 0;
new_data = realloc(*data, new_capacity * element_size);
if (!new_data)
return 0;
*data = new_data;
*capacity = new_capacity;
return 1;
}
static int
wr_valid_rect(wr_rect rect)
{
return isfinite(rect.x) && isfinite(rect.y) &&
isfinite(rect.width) && isfinite(rect.height) &&
rect.width > 0.0f && rect.height > 0.0f;
}
static int
wr_valid_transform(wr_transform transform)
{
return isfinite(transform.m11) && isfinite(transform.m12) &&
isfinite(transform.m21) && isfinite(transform.m22) &&
isfinite(transform.m31) && isfinite(transform.m32);
}
static wr_transform
wr_identity_transform(void)
{
wr_transform transform = { 1, 0, 0, 1, 0, 0 };
return transform;
}
static wr_transform
wr_multiply_transform(wr_transform a, wr_transform b)
{
wr_transform result;
result.m11 = a.m11 * b.m11 + a.m21 * b.m12;
result.m12 = a.m12 * b.m11 + a.m22 * b.m12;
result.m21 = a.m11 * b.m21 + a.m21 * b.m22;
result.m22 = a.m12 * b.m21 + a.m22 * b.m22;
result.m31 = a.m11 * b.m31 + a.m21 * b.m32 + a.m31;
result.m32 = a.m12 * b.m31 + a.m22 * b.m32 + a.m32;
return result;
}
static wr_rect
wr_empty_rect(void)
{
wr_rect rect = { 0, 0, 0, 0 };
return rect;
}
static float
wr_clamp01(float value)
{
if (value < 0.0f)
return 0.0f;
if (value > 1.0f)
return 1.0f;
return value;
}
static wr_color
wr_normalize_color(wr_color color)
{
color.r = wr_clamp01(color.r);
color.g = wr_clamp01(color.g);
color.b = wr_clamp01(color.b);
color.a = wr_clamp01(color.a);
return color;
}
static int
wr_colors_equal(wr_color a, wr_color b)
{
return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
}
static int
wr_rects_equal(wr_rect a, wr_rect b)
{
return a.x == b.x && a.y == b.y &&
a.width == b.width && a.height == b.height;
}
static int
wr_intersects(wr_rect a, wr_rect b)
{
return a.x < b.x + b.width && a.x + a.width > b.x &&
a.y < b.y + b.height && a.y + a.height > b.y;
}
static wr_rect
wr_intersect_rect(wr_rect a, wr_rect b)
{
float left = a.x > b.x ? a.x : b.x;
float top = a.y > b.y ? a.y : b.y;
float right = a.x + a.width < b.x + b.width ?
a.x + a.width : b.x + b.width;
float bottom = a.y + a.height < b.y + b.height ?
a.y + a.height : b.y + b.height;
wr_rect result = { left, top, right - left, bottom - top };
return result;
}
static wr_rect
wr_transform_bounds(wr_rect rect, wr_transform transform)
{
float x1 = rect.x * transform.m11 + rect.y * transform.m21 + transform.m31;
float y1 = rect.x * transform.m12 + rect.y * transform.m22 + transform.m32;
float x2 = (rect.x + rect.width) * transform.m11 +
rect.y * transform.m21 + transform.m31;
float y2 = (rect.x + rect.width) * transform.m12 +
rect.y * transform.m22 + transform.m32;
float x3 = rect.x * transform.m11 +
(rect.y + rect.height) * transform.m21 + transform.m31;
float y3 = rect.x * transform.m12 +
(rect.y + rect.height) * transform.m22 + transform.m32;
float x4 = (rect.x + rect.width) * transform.m11 +
(rect.y + rect.height) * transform.m21 + transform.m31;
float y4 = (rect.x + rect.width) * transform.m12 +
(rect.y + rect.height) * transform.m22 + transform.m32;
float left = fminf(fminf(x1, x2), fminf(x3, x4));
float right = fmaxf(fmaxf(x1, x2), fmaxf(x3, x4));
float top = fminf(fminf(y1, y2), fminf(y3, y4));
float bottom = fmaxf(fmaxf(y1, y2), fmaxf(y3, y4));
wr_rect result = { left, top, right - left, bottom - top };
return result;
}
wr_context*
wr_context_create(size_t initial_capacity)
{
wr_context* context = (wr_context*)calloc(1, sizeof(*context));
if (!context)
return NULL;
context->viewport.x = -FLT_MAX;
context->viewport.y = -FLT_MAX;
context->viewport.width = FLT_MAX;
context->viewport.height = FLT_MAX;
context->transform = wr_identity_transform();
context->opacity = 1.0f;
if (initial_capacity &&
!wr_reserve((void**)&context->commands, &context->command_capacity,
initial_capacity, sizeof(*context->commands))) {
wr_context_destroy(context);
return NULL;
}
return context;
}
void
wr_context_destroy(wr_context* context)
{
if (!context)
return;
free(context->commands);
free(context->quads);
free(context->batches);
free(context->clip_stack);
free(context->state_stack);
free(context);
}
void
wr_context_clear(wr_context* context)
{
if (!context)
return;
context->command_count = 0;
context->quad_count = 0;
context->batch_count = 0;
context->clip_depth = 0;
context->state_depth = 0;
}
void
wr_context_set_viewport(wr_context* context, wr_rect viewport)
{
if (context && wr_valid_rect(viewport))
context->viewport = viewport;
}
void
wr_context_set_transform(wr_context* context, wr_transform transform)
{
if (context && wr_valid_transform(transform))
context->transform = transform;
}
void
wr_context_set_opacity(wr_context* context, float opacity)
{
if (context && isfinite(opacity)) {
context->opacity = wr_clamp01(opacity);
}
}
int
wr_display_list_push_stacking_context(wr_context* context,
wr_transform transform,
float opacity)
{
wr_display_state state;
if (!context || !wr_valid_transform(transform) || !isfinite(opacity) ||
context->state_depth == SIZE_MAX)
return 0;
if (!wr_reserve((void**)&context->state_stack, &context->state_capacity,
context->state_depth + 1, sizeof(*context->state_stack)))
return 0;
state.transform = context->transform;
state.opacity = context->opacity;
state.clip_depth = context->clip_depth;
context->state_stack[context->state_depth++] = state;
context->transform = wr_multiply_transform(context->transform, transform);
context->opacity = wr_clamp01(context->opacity * wr_clamp01(opacity));
return 1;
}
void
wr_display_list_pop_stacking_context(wr_context* context)
{
wr_display_state state;
if (!context || !context->state_depth)
return;
state = context->state_stack[--context->state_depth];
context->transform = state.transform;
context->opacity = state.opacity;
context->clip_depth = state.clip_depth;
}
int
wr_display_list_push_clip(wr_context* context, wr_rect clip)
{
wr_rect effective_clip;
if (!context || !wr_valid_rect(clip) ||
context->clip_depth == SIZE_MAX)
return 0;
if (!wr_reserve((void**)&context->clip_stack, &context->clip_capacity,
context->clip_depth + 1, sizeof(*context->clip_stack)))
return 0;
effective_clip = clip;
if (context->clip_depth)
effective_clip = wr_intersect_rect(
context->clip_stack[context->clip_depth - 1], clip);
context->clip_stack[context->clip_depth++] = effective_clip;
return 1;
}
void
wr_display_list_pop_clip(wr_context* context)
{
if (context && context->clip_depth)
--context->clip_depth;
}
int
wr_display_list_push_rect(wr_context* context, wr_rect rect, wr_color color)
{
return context ? wr_display_list_push_transformed_rect(
context, rect, color, context->transform) : 0;
}
int
wr_display_list_push_transformed_rect(wr_context* context, wr_rect rect,
wr_color color, wr_transform transform)
{
wr_rect clip_rect = context && context->clip_depth ?
context->clip_stack[context->clip_depth - 1] :
context ? context->viewport : wr_empty_rect();
uint8_t has_clip = context && context->clip_depth ? 1 : 0;
if (!context || !wr_valid_rect(rect) || !wr_valid_transform(transform))
return 0;
if (context->command_count == SIZE_MAX ||
context->command_count >= UINT32_MAX)
return 0;
if (!wr_reserve((void**)&context->commands, &context->command_capacity,
context->command_count + 1, sizeof(*context->commands)))
return 0;
context->commands[context->command_count].rect = rect;
color = wr_normalize_color(color);
color.a *= context->opacity;
context->commands[context->command_count].color = color;
context->commands[context->command_count].transform = transform;
context->commands[context->command_count].clip_rect = clip_rect;
context->commands[context->command_count].has_clip = has_clip;
++context->command_count;
return 1;
}
const wr_frame*
wr_context_build_frame(wr_context* context)
{
size_t i;
if (!context)
return NULL;
context->quad_count = 0;
context->batch_count = 0;
for (i = 0; i < context->command_count; ++i) {
wr_rect_command* command = &context->commands[i];
wr_gpu_quad* quad;
wr_gpu_batch* batch;
if (!wr_intersects(wr_transform_bounds(command->rect,
command->transform),
context->viewport) ||
(command->clip_rect.width <= 0.0f ||
command->clip_rect.height <= 0.0f))
continue;
if (context->quad_count == SIZE_MAX ||
context->quad_count >= UINT32_MAX ||
context->batch_count == SIZE_MAX ||
context->batch_count >= UINT32_MAX)
return NULL;
if (!wr_reserve((void**)&context->quads, &context->quad_capacity,
context->quad_count + 1, sizeof(*context->quads)) ||
!wr_reserve((void**)&context->batches, &context->batch_capacity,
context->batch_count + 1, sizeof(*context->batches)))
return NULL;
quad = &context->quads[context->quad_count++];
quad->rect = command->rect;
quad->color = command->color;
if (context->batch_count &&
wr_colors_equal(context->batches[context->batch_count - 1].color,
command->color) &&
context->batches[context->batch_count - 1].transform.m11 ==
command->transform.m11 &&
context->batches[context->batch_count - 1].transform.m12 ==
command->transform.m12 &&
context->batches[context->batch_count - 1].transform.m21 ==
command->transform.m21 &&
context->batches[context->batch_count - 1].transform.m22 ==
command->transform.m22 &&
context->batches[context->batch_count - 1].transform.m31 ==
command->transform.m31 &&
context->batches[context->batch_count - 1].transform.m32 ==
command->transform.m32 &&
context->batches[context->batch_count - 1].has_clip ==
command->has_clip &&
(!command->has_clip ||
wr_rects_equal(context->batches[context->batch_count - 1].clip_rect,
command->clip_rect))) {
batch = &context->batches[context->batch_count - 1];
++batch->quad_count;
} else {
batch = &context->batches[context->batch_count++];
batch->first_quad = (uint32_t)(context->quad_count - 1);
batch->quad_count = 1;
batch->color = command->color;
batch->transform = command->transform;
batch->clip_rect = command->clip_rect;
batch->has_clip = command->has_clip;
}
}
context->frame.quads = context->quads;
context->frame.quad_count = context->quad_count;
context->frame.batches = context->batches;
context->frame.batch_count = context->batch_count;
return &context->frame;
}

View file

@ -9,6 +9,13 @@ extern "C" {
#endif
typedef struct wr_context wr_context;
typedef uint64_t wr_item_id;
typedef struct {
uint32_t key;
uint32_t width;
uint32_t height;
} wr_image_resource;
typedef struct {
float x;
@ -34,9 +41,24 @@ typedef struct {
float m32;
} wr_transform;
typedef struct {
wr_rect rect;
wr_rect tex_rect;
} wr_glyph;
typedef enum {
WR_PRIMITIVE_SOLID = 0,
WR_PRIMITIVE_IMAGE = 1
} wr_primitive_kind;
typedef struct {
wr_rect rect;
wr_color color;
wr_rect tex_rect;
uint32_t image_key;
wr_item_id item_id;
uint8_t kind;
uint8_t active;
} wr_gpu_quad;
typedef struct {
@ -46,6 +68,8 @@ typedef struct {
wr_transform transform;
wr_rect clip_rect;
uint8_t has_clip;
uint8_t kind;
uint32_t image_key;
} wr_gpu_batch;
typedef struct {
@ -55,6 +79,14 @@ typedef struct {
size_t batch_count;
} wr_frame;
typedef struct {
uint32_t version;
uint32_t quad_count;
uint32_t batch_count;
} wr_frame_packet_header;
#define WR_FRAME_PACKET_VERSION 1
/* Creates a retained display-list context. */
wr_context* wr_context_create(size_t initial_capacity);
void wr_context_destroy(wr_context* context);
@ -62,6 +94,21 @@ void wr_context_destroy(wr_context* context);
/* Removes every item from the retained display list. */
void wr_context_clear(wr_context* context);
/* Clears transient image resources without releasing retained allocations. */
void wr_context_clear_images(wr_context* context);
/* Begins an atomic retained-scene update. Transactions may be nested. */
int wr_context_begin_transaction(wr_context* context);
/* Commits the innermost retained-scene update. */
int wr_context_commit_transaction(wr_context* context);
/* Rolls back the innermost retained-scene update. */
void wr_context_abort_transaction(wr_context* context);
/* Removes a retained display-list item by stable item ID. */
int wr_context_remove_item(wr_context* context, wr_item_id item_id);
/* Sets the viewport used for coarse CPU-side culling. */
void wr_context_set_viewport(wr_context* context, wr_rect viewport);
@ -71,6 +118,13 @@ void wr_context_set_transform(wr_context* context, wr_transform transform);
/* Sets the opacity applied to subsequently recorded primitives. */
void wr_context_set_opacity(wr_context* context, float opacity);
/* Registers or updates an image resource referenced by display-list commands. */
int wr_context_register_image(wr_context* context, uint32_t image_key,
uint32_t width, uint32_t height);
/* Removes an image resource and invalidates frames referencing it. */
void wr_context_unregister_image(wr_context* context, uint32_t image_key);
/* Records a retained stacking-context boundary and restores it on pop. */
int wr_display_list_push_stacking_context(wr_context* context,
wr_transform transform,
@ -85,14 +139,57 @@ void wr_display_list_pop_clip(wr_context* context);
int wr_display_list_push_rect(wr_context* context, wr_rect rect,
wr_color color);
/* Adds a solid rectangle with a caller-supplied stable item ID. */
wr_item_id wr_display_list_push_rect_with_id(wr_context* context,
wr_item_id item_id,
wr_rect rect, wr_color color);
/* Updates an existing solid rectangle without rebuilding the scene. */
int wr_display_list_update_rect(wr_context* context, wr_item_id item_id,
wr_rect rect, wr_color color,
wr_transform transform);
/* Adds a GPU-tessellated linear gradient. */
int wr_display_list_push_linear_gradient(wr_context* context, wr_rect rect,
wr_color start, wr_color end,
int horizontal);
/* Adds a GPU-tessellated rounded rectangle. */
int wr_display_list_push_rounded_rect(wr_context* context, wr_rect rect,
wr_color color, float radius);
/* Explicit transform form for display-list builders. */
int wr_display_list_push_transformed_rect(wr_context* context, wr_rect rect,
wr_color color,
wr_transform transform);
/* Adds an image primitive resolved by the compositor using image_key. */
int wr_display_list_push_image(wr_context* context, wr_rect rect,
uint32_t image_key, wr_rect tex_rect,
wr_color tint);
/* Adds an image with a caller-supplied stable item ID. */
wr_item_id wr_display_list_push_image_with_id(wr_context* context,
wr_item_id item_id,
wr_rect rect,
uint32_t image_key,
wr_rect tex_rect,
wr_color tint);
/* Records glyph quads sourced from a registered image atlas. */
int wr_display_list_push_glyph_run(wr_context* context, uint32_t image_key,
const wr_glyph* glyphs, size_t glyph_count,
wr_color color);
/* Builds the GPU-ready stream from the retained display list. */
const wr_frame* wr_context_build_frame(wr_context* context);
/* Returns the byte size required for a versioned, flat frame packet. */
size_t wr_frame_packet_size(const wr_frame* frame);
/* Serializes a frame into a flat buffer suitable for thread/IPC transfer. */
int wr_frame_serialize(const wr_frame* frame, void* buffer, size_t buffer_size);
#ifdef __cplusplus
}
#endif

145
gfx/wr/wr_context.c Normal file
View file

@ -0,0 +1,145 @@
#include <wr_internal.h>
wr_context*
wr_context_create(size_t initial_capacity)
{
wr_context* context = (wr_context*)calloc(1, sizeof(*context));
if (!context)
return NULL;
context->viewport.x = -FLT_MAX;
context->viewport.y = -FLT_MAX;
context->viewport.width = FLT_MAX;
context->viewport.height = FLT_MAX;
context->transform = wr_identity_transform();
context->opacity = 1.0f;
context->next_item_id = 1;
if (initial_capacity &&
!wr_reserve((void**)&context->commands, &context->command_capacity,
initial_capacity, sizeof(*context->commands))) {
wr_context_destroy(context);
return NULL;
}
return context;
}
void
wr_context_destroy(wr_context* context)
{
if (!context)
return;
free(context->commands);
free(context->quads);
free(context->batches);
free(context->clip_stack);
free(context->state_stack);
free(context->images);
for (size_t i = 0; i < context->transaction_depth; ++i)
free(context->transactions[i].commands);
free(context->transactions);
free(context);
}
void
wr_context_clear(wr_context* context)
{
if (!context)
return;
context->command_count = 0;
context->quad_count = 0;
context->batch_count = 0;
context->clip_depth = 0;
context->state_depth = 0;
context->frame_valid = 0;
while (context->transaction_depth)
free(context->transactions[--context->transaction_depth].commands);
}
void
wr_context_set_viewport(wr_context* context, wr_rect viewport)
{
if (context && wr_valid_rect(viewport))
context->viewport = viewport;
if (context)
context->frame_valid = 0;
}
void
wr_context_set_transform(wr_context* context, wr_transform transform)
{
if (context && wr_valid_transform(transform))
context->transform = transform;
}
void
wr_context_set_opacity(wr_context* context, float opacity)
{
if (context && isfinite(opacity)) {
context->opacity = wr_clamp01(opacity);
context->frame_valid = 0;
}
}
int
wr_display_list_push_stacking_context(wr_context* context,
wr_transform transform,
float opacity)
{
wr_display_state state;
if (!context || !wr_valid_transform(transform) || !isfinite(opacity) ||
context->state_depth == SIZE_MAX)
return 0;
if (!wr_reserve((void**)&context->state_stack, &context->state_capacity,
context->state_depth + 1, sizeof(*context->state_stack)))
return 0;
state.transform = context->transform;
state.opacity = context->opacity;
state.clip_depth = context->clip_depth;
context->state_stack[context->state_depth++] = state;
context->transform = wr_multiply_transform(context->transform, transform);
context->opacity = wr_clamp01(context->opacity * wr_clamp01(opacity));
return 1;
}
void
wr_display_list_pop_stacking_context(wr_context* context)
{
wr_display_state state;
if (!context || !context->state_depth)
return;
state = context->state_stack[--context->state_depth];
context->transform = state.transform;
context->opacity = state.opacity;
context->clip_depth = state.clip_depth;
}
int
wr_display_list_push_clip(wr_context* context, wr_rect clip)
{
wr_rect effective_clip;
if (!context || !wr_valid_rect(clip) ||
context->clip_depth == SIZE_MAX)
return 0;
if (!wr_reserve((void**)&context->clip_stack, &context->clip_capacity,
context->clip_depth + 1, sizeof(*context->clip_stack)))
return 0;
effective_clip = clip;
if (context->clip_depth)
effective_clip = wr_intersect_rect(
context->clip_stack[context->clip_depth - 1], clip);
context->clip_stack[context->clip_depth++] = effective_clip;
return 1;
}
void
wr_display_list_pop_clip(wr_context* context)
{
if (context && context->clip_depth)
--context->clip_depth;
}

43
gfx/wr/wr_display_list.c Normal file
View file

@ -0,0 +1,43 @@
#include <wr_internal.h>
int
wr_display_list_push_rect(wr_context* context, wr_rect rect, wr_color color)
{
return context ? wr_display_list_push_transformed_rect(
context, rect, color, context->transform) : 0;
}
int
wr_display_list_push_transformed_rect(wr_context* context, wr_rect rect,
wr_color color, wr_transform transform)
{
wr_rect clip_rect = context && context->clip_depth ?
context->clip_stack[context->clip_depth - 1] :
context ? context->viewport : wr_empty_rect();
uint8_t has_clip = context && context->clip_depth ? 1 : 0;
if (!context || !wr_valid_rect(rect) || !wr_valid_transform(transform))
return 0;
if (context->command_count == SIZE_MAX ||
context->command_count >= UINT32_MAX)
return 0;
if (!wr_reserve((void**)&context->commands, &context->command_capacity,
context->command_count + 1, sizeof(*context->commands)))
return 0;
context->commands[context->command_count].rect = rect;
color = wr_normalize_color(color);
color.a *= context->opacity;
context->commands[context->command_count].color = color;
context->commands[context->command_count].transform = transform;
context->commands[context->command_count].clip_rect = clip_rect;
context->commands[context->command_count].has_clip = has_clip;
context->commands[context->command_count].kind = WR_PRIMITIVE_SOLID;
context->commands[context->command_count].active = 1;
context->commands[context->command_count].image_key = 0;
context->commands[context->command_count].tex_rect = (wr_rect){ 0, 0, 1, 1 };
context->commands[context->command_count].item_id = context->next_item_id++;
++context->command_count;
context->frame_valid = 0;
return 1;
}

105
gfx/wr/wr_frame.c Normal file
View file

@ -0,0 +1,105 @@
#include <wr_internal.h>
static int
wr_frame_has_image(const wr_context* context, uint32_t key)
{
size_t i;
for (i = 0; i < context->image_count; ++i)
if (context->images[i].key == key)
return 1;
return 0;
}
const wr_frame*
wr_context_build_frame(wr_context* context)
{
size_t i;
if (!context)
return NULL;
if (context->frame_valid)
return &context->frame;
context->quad_count = 0;
context->batch_count = 0;
for (i = 0; i < context->command_count; ++i) {
wr_rect_command* command = &context->commands[i];
wr_gpu_quad* quad;
wr_gpu_batch* batch;
if (!command->active ||
(command->kind == WR_PRIMITIVE_IMAGE &&
!wr_frame_has_image(context, command->image_key)) ||
wr_command_occluded(context, i))
continue;
if (!wr_intersects(wr_transform_bounds(command->rect,
command->transform),
context->viewport) ||
(command->clip_rect.width <= 0.0f ||
command->clip_rect.height <= 0.0f))
continue;
if (context->quad_count == SIZE_MAX ||
context->quad_count >= UINT32_MAX ||
context->batch_count == SIZE_MAX ||
context->batch_count >= UINT32_MAX)
return NULL;
if (!wr_reserve((void**)&context->quads, &context->quad_capacity,
context->quad_count + 1, sizeof(*context->quads)) ||
!wr_reserve((void**)&context->batches, &context->batch_capacity,
context->batch_count + 1, sizeof(*context->batches)))
return NULL;
quad = &context->quads[context->quad_count++];
quad->rect = command->rect;
quad->color = command->color;
quad->tex_rect = command->tex_rect;
quad->image_key = command->image_key;
quad->item_id = command->item_id;
quad->active = command->active;
quad->kind = command->kind;
if (context->batch_count &&
context->batches[context->batch_count - 1].kind == command->kind &&
context->batches[context->batch_count - 1].image_key ==
command->image_key &&
wr_colors_equal(context->batches[context->batch_count - 1].color,
command->color) &&
context->batches[context->batch_count - 1].transform.m11 ==
command->transform.m11 &&
context->batches[context->batch_count - 1].transform.m12 ==
command->transform.m12 &&
context->batches[context->batch_count - 1].transform.m21 ==
command->transform.m21 &&
context->batches[context->batch_count - 1].transform.m22 ==
command->transform.m22 &&
context->batches[context->batch_count - 1].transform.m31 ==
command->transform.m31 &&
context->batches[context->batch_count - 1].transform.m32 ==
command->transform.m32 &&
context->batches[context->batch_count - 1].has_clip ==
command->has_clip &&
(!command->has_clip ||
wr_rects_equal(context->batches[context->batch_count - 1].clip_rect,
command->clip_rect))) {
batch = &context->batches[context->batch_count - 1];
++batch->quad_count;
} else {
batch = &context->batches[context->batch_count++];
batch->first_quad = (uint32_t)(context->quad_count - 1);
batch->quad_count = 1;
batch->color = command->color;
batch->transform = command->transform;
batch->clip_rect = command->clip_rect;
batch->has_clip = command->has_clip;
batch->kind = command->kind;
batch->image_key = command->image_key;
}
}
context->frame.quads = context->quads;
context->frame.quad_count = context->quad_count;
context->frame.batches = context->batches;
context->frame.batch_count = context->batch_count;
context->frame_valid = 1;
return &context->frame;
}

59
gfx/wr/wr_frame_packet.c Normal file
View file

@ -0,0 +1,59 @@
#include "webrender.h"
#include <stdint.h>
#include <string.h>
static int
wr_packet_size_mul_overflow(size_t a, size_t b)
{
return b != 0 && a > SIZE_MAX / b;
}
size_t
wr_frame_packet_size(const wr_frame* frame)
{
size_t size;
if (!frame || frame->quad_count > UINT32_MAX ||
frame->batch_count > UINT32_MAX ||
(frame->quad_count && !frame->quads) ||
(frame->batch_count && !frame->batches))
return 0;
size = sizeof(wr_frame_packet_header);
if (wr_packet_size_mul_overflow(frame->quad_count, sizeof(*frame->quads)) ||
wr_packet_size_mul_overflow(frame->batch_count, sizeof(*frame->batches)))
return 0;
if (size > SIZE_MAX - frame->quad_count * sizeof(*frame->quads))
return 0;
size += frame->quad_count * sizeof(*frame->quads);
if (size > SIZE_MAX - frame->batch_count * sizeof(*frame->batches))
return 0;
return size + frame->batch_count * sizeof(*frame->batches);
}
int
wr_frame_serialize(const wr_frame* frame, void* buffer, size_t buffer_size)
{
wr_frame_packet_header header;
size_t required = wr_frame_packet_size(frame);
unsigned char* output = (unsigned char*)buffer;
if (!required || !buffer || buffer_size < required)
return 0;
header.version = WR_FRAME_PACKET_VERSION;
header.quad_count = (uint32_t)frame->quad_count;
header.batch_count = (uint32_t)frame->batch_count;
memcpy(output, &header, sizeof(header));
output += sizeof(header);
if (frame->quad_count) {
memcpy(output, frame->quads,
frame->quad_count * sizeof(*frame->quads));
output += frame->quad_count * sizeof(*frame->quads);
}
if (frame->batch_count) {
memcpy(output, frame->batches,
frame->batch_count * sizeof(*frame->batches));
}
return 1;
}

82
gfx/wr/wr_internal.h Normal file
View file

@ -0,0 +1,82 @@
#ifndef GFX_WR_INTERNAL_H
#define GFX_WR_INTERNAL_H
#include <webrender.h>
#include <float.h>
#include <math.h>
#include <stdlib.h>
#include <string.h>
typedef struct {
wr_rect rect;
wr_color color;
wr_transform transform;
wr_rect clip_rect;
uint8_t has_clip;
uint8_t kind;
uint8_t active;
uint32_t image_key;
wr_rect tex_rect;
wr_item_id item_id;
} wr_rect_command;
typedef struct {
wr_transform transform;
float opacity;
size_t clip_depth;
} wr_display_state;
typedef struct {
size_t command_count;
wr_rect_command* commands;
} wr_scene_transaction;
struct wr_context {
wr_rect_command* commands;
size_t command_count;
size_t command_capacity;
wr_gpu_quad* quads;
size_t quad_count;
size_t quad_capacity;
wr_gpu_batch* batches;
size_t batch_count;
size_t batch_capacity;
wr_rect viewport;
wr_transform transform;
float opacity;
wr_rect* clip_stack;
size_t clip_depth;
size_t clip_capacity;
wr_display_state* state_stack;
size_t state_depth;
size_t state_capacity;
wr_item_id next_item_id;
wr_image_resource* images;
size_t image_count;
size_t image_capacity;
wr_scene_transaction* transactions;
size_t transaction_depth;
size_t transaction_capacity;
int frame_valid;
wr_frame frame;
};
int wr_size_mul_overflow(size_t, size_t);
int wr_reserve(void**, size_t*, size_t, size_t);
int wr_valid_rect(wr_rect);
int wr_valid_transform(wr_transform);
wr_transform wr_identity_transform(void);
wr_transform wr_multiply_transform(wr_transform, wr_transform);
wr_rect wr_empty_rect(void);
float wr_clamp01(float);
wr_color wr_normalize_color(wr_color);
int wr_colors_equal(wr_color, wr_color);
int wr_rects_equal(wr_rect, wr_rect);
int wr_intersects(wr_rect, wr_rect);
wr_rect wr_intersect_rect(wr_rect, wr_rect);
wr_rect wr_transform_bounds(wr_rect, wr_transform);
int wr_command_occluded(const wr_context*, size_t);
#endif

251
gfx/wr/wr_retained.c Normal file
View file

@ -0,0 +1,251 @@
#include <wr_internal.h>
static size_t
wr_image_index(const wr_context* context, uint32_t key)
{
size_t i;
for (i = 0; context && i < context->image_count; ++i)
if (context->images[i].key == key)
return i;
return SIZE_MAX;
}
static size_t
wr_item_index(const wr_context* context, wr_item_id item)
{
size_t i;
for (i = 0; context && i < context->command_count; ++i)
if (context->commands[i].active && context->commands[i].item_id == item)
return i;
return SIZE_MAX;
}
static int
wr_rect_contains(wr_rect outer, wr_rect inner)
{
return inner.x >= outer.x && inner.y >= outer.y &&
inner.x + inner.width <= outer.x + outer.width &&
inner.y + inner.height <= outer.y + outer.height;
}
static int
wr_append(wr_context* context, wr_item_id item, wr_rect rect, wr_color color,
uint8_t kind, uint32_t image_key, wr_rect tex_rect)
{
wr_rect clip = context->clip_depth
? context->clip_stack[context->clip_depth - 1] : context->viewport;
if (!context || !wr_valid_rect(rect) ||
(kind == WR_PRIMITIVE_IMAGE &&
(wr_image_index(context, image_key) == SIZE_MAX ||
tex_rect.width == 0.0f || tex_rect.height == 0.0f)))
return 0;
if (!wr_reserve((void**)&context->commands, &context->command_capacity,
context->command_count + 1, sizeof(*context->commands)))
return 0;
if (!item) {
item = context->next_item_id++;
if (!item)
item = context->next_item_id++;
}
if (wr_item_index(context, item) != SIZE_MAX)
return 0;
context->commands[context->command_count] = (wr_rect_command){
rect, wr_normalize_color(color), context->transform, clip,
context->clip_depth ? 1 : 0, kind, 1, image_key, tex_rect, item };
context->commands[context->command_count].color.a *= context->opacity;
++context->command_count;
context->frame_valid = 0;
return 1;
}
int
wr_context_register_image(wr_context* context, uint32_t key,
uint32_t width, uint32_t height)
{
size_t index;
if (!context || !key || !width || !height)
return 0;
index = wr_image_index(context, key);
if (index != SIZE_MAX) {
context->images[index].width = width;
context->images[index].height = height;
return 1;
}
if (!wr_reserve((void**)&context->images, &context->image_capacity,
context->image_count + 1, sizeof(*context->images)))
return 0;
context->images[context->image_count++] = (wr_image_resource){ key, width, height };
return 1;
}
void
wr_context_unregister_image(wr_context* context, uint32_t key)
{
size_t index = wr_image_index(context, key);
if (index == SIZE_MAX)
return;
context->images[index] = context->images[--context->image_count];
context->frame_valid = 0;
}
void
wr_context_clear_images(wr_context* context)
{
if (context) {
context->image_count = 0;
context->frame_valid = 0;
}
}
int
wr_context_begin_transaction(wr_context* context)
{
wr_scene_transaction transaction = { 0, NULL };
if (!context || !wr_reserve((void**)&context->transactions,
&context->transaction_capacity, context->transaction_depth + 1,
sizeof(*context->transactions)))
return 0;
transaction.command_count = context->command_count;
if (transaction.command_count) {
transaction.commands = malloc(transaction.command_count * sizeof(*transaction.commands));
if (!transaction.commands)
return 0;
memcpy(transaction.commands, context->commands,
transaction.command_count * sizeof(*transaction.commands));
}
context->transactions[context->transaction_depth++] = transaction;
return 1;
}
int
wr_context_commit_transaction(wr_context* context)
{
if (!context || !context->transaction_depth)
return 0;
free(context->transactions[--context->transaction_depth].commands);
context->frame_valid = 0;
return 1;
}
void
wr_context_abort_transaction(wr_context* context)
{
wr_scene_transaction transaction;
if (!context || !context->transaction_depth)
return;
transaction = context->transactions[--context->transaction_depth];
if (transaction.command_count)
memcpy(context->commands, transaction.commands,
transaction.command_count * sizeof(*context->commands));
context->command_count = transaction.command_count;
free(transaction.commands);
context->frame_valid = 0;
}
int
wr_context_remove_item(wr_context* context, wr_item_id item)
{
size_t index = wr_item_index(context, item);
if (index == SIZE_MAX)
return 0;
context->commands[index].active = 0;
context->frame_valid = 0;
return 1;
}
wr_item_id
wr_display_list_push_rect_with_id(wr_context* context, wr_item_id item,
wr_rect rect, wr_color color)
{
return wr_append(context, item, rect, color, WR_PRIMITIVE_SOLID, 0,
(wr_rect){ 0, 0, 1, 1 }) ? item : 0;
}
int
wr_display_list_update_rect(wr_context* context, wr_item_id item, wr_rect rect,
wr_color color, wr_transform transform)
{
size_t index = wr_item_index(context, item);
if (index == SIZE_MAX || !wr_valid_rect(rect) || !wr_valid_transform(transform))
return 0;
context->commands[index].rect = rect;
context->commands[index].color = wr_normalize_color(color);
context->commands[index].color.a *= context->opacity;
context->commands[index].transform = transform;
context->frame_valid = 0;
return 1;
}
int
wr_display_list_push_image(wr_context* context, wr_rect rect, uint32_t key,
wr_rect tex_rect, wr_color tint)
{ return wr_append(context, 0, rect, tint, WR_PRIMITIVE_IMAGE, key, tex_rect); }
wr_item_id
wr_display_list_push_image_with_id(wr_context* context, wr_item_id item,
wr_rect rect, uint32_t key, wr_rect tex,
wr_color tint)
{ return wr_append(context, item, rect, tint, WR_PRIMITIVE_IMAGE, key, tex) ? item : 0; }
int
wr_display_list_push_linear_gradient(wr_context* context, wr_rect rect,
wr_color start, wr_color end, int horizontal)
{
unsigned i;
for (i = 0; i < 32; ++i) {
float a = (float)i / 32.0f, b = (float)(i + 1) / 32.0f;
wr_rect slice = rect;
wr_color color = { start.r + (end.r-start.r)*(a+b)*.5f,
start.g + (end.g-start.g)*(a+b)*.5f, start.b + (end.b-start.b)*(a+b)*.5f,
start.a + (end.a-start.a)*(a+b)*.5f };
if (horizontal) { slice.x += rect.width*a; slice.width = rect.width*(b-a); }
else { slice.y += rect.height*a; slice.height = rect.height*(b-a); }
if (!wr_append(context, 0, slice, color, WR_PRIMITIVE_SOLID, 0,
(wr_rect){ 0, 0, 1, 1 })) return 0;
}
return 1;
}
int
wr_display_list_push_rounded_rect(wr_context* context, wr_rect rect,
wr_color color, float radius)
{
unsigned i;
(void)radius;
for (i = 0; i < 16; ++i) {
wr_rect slice = { rect.x, rect.y + rect.height * i / 16.0f,
rect.width, rect.height / 16.0f };
if (!wr_append(context, 0, slice, color, WR_PRIMITIVE_SOLID, 0,
(wr_rect){ 0, 0, 1, 1 }))
return 0;
}
return 1;
}
int
wr_display_list_push_glyph_run(wr_context* context, uint32_t key,
const wr_glyph* glyphs, size_t count, wr_color color)
{
size_t i;
for (i = 0; glyphs && i < count; ++i)
if (!wr_append(context, 0, glyphs[i].rect, color, WR_PRIMITIVE_IMAGE,
key, glyphs[i].tex_rect)) return 0;
return glyphs && count != 0;
}
int
wr_command_occluded(const wr_context* context, size_t index)
{
size_t i;
wr_rect bounds = wr_transform_bounds(context->commands[index].rect,
context->commands[index].transform);
for (i = index + 1; i < context->command_count; ++i) {
const wr_rect_command* covering = &context->commands[i];
wr_rect cover;
if (!covering->active || covering->kind != WR_PRIMITIVE_SOLID ||
covering->color.a != 1.0f) continue;
cover = wr_transform_bounds(covering->rect, covering->transform);
if (wr_rect_contains(cover, bounds)) return 1;
}
return 0;
}

160
gfx/wr/wr_util.c Normal file
View file

@ -0,0 +1,160 @@
#include <wr_internal.h>
int
wr_size_mul_overflow(size_t a, size_t b)
{
return b != 0 && a > SIZE_MAX / b;
}
int
wr_reserve(void** data, size_t* capacity, size_t count, size_t element_size)
{
size_t new_capacity;
void* new_data;
if (count <= *capacity)
return 1;
if (element_size == 0 || wr_size_mul_overflow(count, element_size))
return 0;
new_capacity = *capacity ? *capacity : 8;
while (new_capacity < count) {
if (new_capacity > SIZE_MAX / 2) {
new_capacity = count;
break;
}
new_capacity *= 2;
}
if (wr_size_mul_overflow(new_capacity, element_size))
return 0;
new_data = realloc(*data, new_capacity * element_size);
if (!new_data)
return 0;
*data = new_data;
*capacity = new_capacity;
return 1;
}
int
wr_valid_rect(wr_rect rect)
{
return isfinite(rect.x) && isfinite(rect.y) &&
isfinite(rect.width) && isfinite(rect.height) &&
rect.width > 0.0f && rect.height > 0.0f;
}
int
wr_valid_transform(wr_transform transform)
{
return isfinite(transform.m11) && isfinite(transform.m12) &&
isfinite(transform.m21) && isfinite(transform.m22) &&
isfinite(transform.m31) && isfinite(transform.m32);
}
wr_transform
wr_identity_transform(void)
{
wr_transform transform = { 1, 0, 0, 1, 0, 0 };
return transform;
}
wr_transform
wr_multiply_transform(wr_transform a, wr_transform b)
{
wr_transform result;
result.m11 = a.m11 * b.m11 + a.m21 * b.m12;
result.m12 = a.m12 * b.m11 + a.m22 * b.m12;
result.m21 = a.m11 * b.m21 + a.m21 * b.m22;
result.m22 = a.m12 * b.m21 + a.m22 * b.m22;
result.m31 = a.m11 * b.m31 + a.m21 * b.m32 + a.m31;
result.m32 = a.m12 * b.m31 + a.m22 * b.m32 + a.m32;
return result;
}
wr_rect
wr_empty_rect(void)
{
wr_rect rect = { 0, 0, 0, 0 };
return rect;
}
float
wr_clamp01(float value)
{
if (value < 0.0f)
return 0.0f;
if (value > 1.0f)
return 1.0f;
return value;
}
wr_color
wr_normalize_color(wr_color color)
{
color.r = wr_clamp01(color.r);
color.g = wr_clamp01(color.g);
color.b = wr_clamp01(color.b);
color.a = wr_clamp01(color.a);
return color;
}
int
wr_colors_equal(wr_color a, wr_color b)
{
return a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a;
}
int
wr_rects_equal(wr_rect a, wr_rect b)
{
return a.x == b.x && a.y == b.y &&
a.width == b.width && a.height == b.height;
}
int
wr_intersects(wr_rect a, wr_rect b)
{
return a.x < b.x + b.width && a.x + a.width > b.x &&
a.y < b.y + b.height && a.y + a.height > b.y;
}
wr_rect
wr_intersect_rect(wr_rect a, wr_rect b)
{
float left = a.x > b.x ? a.x : b.x;
float top = a.y > b.y ? a.y : b.y;
float right = a.x + a.width < b.x + b.width ?
a.x + a.width : b.x + b.width;
float bottom = a.y + a.height < b.y + b.height ?
a.y + a.height : b.y + b.height;
wr_rect result = { left, top, right - left, bottom - top };
return result;
}
wr_rect
wr_transform_bounds(wr_rect rect, wr_transform transform)
{
float x1 = rect.x * transform.m11 + rect.y * transform.m21 + transform.m31;
float y1 = rect.x * transform.m12 + rect.y * transform.m22 + transform.m32;
float x2 = (rect.x + rect.width) * transform.m11 +
rect.y * transform.m21 + transform.m31;
float y2 = (rect.x + rect.width) * transform.m12 +
rect.y * transform.m22 + transform.m32;
float x3 = rect.x * transform.m11 +
(rect.y + rect.height) * transform.m21 + transform.m31;
float y3 = rect.x * transform.m12 +
(rect.y + rect.height) * transform.m22 + transform.m32;
float x4 = (rect.x + rect.width) * transform.m11 +
(rect.y + rect.height) * transform.m21 + transform.m31;
float y4 = (rect.x + rect.width) * transform.m12 +
(rect.y + rect.height) * transform.m22 + transform.m32;
float left = fminf(fminf(x1, x2), fminf(x3, x4));
float right = fmaxf(fmaxf(x1, x2), fmaxf(x3, x4));
float top = fminf(fminf(y1, y2), fminf(y3, y4));
float bottom = fmaxf(fmaxf(y1, y2), fmaxf(y3, y4));
wr_rect result = { left, top, right - left, bottom - top };
return result;
}