Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/host-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#
# Host Tests
#
# Builds the userland libraries' host tests with the runner's own compiler and
# runs them, for library code that no unit test inside Stellux reaches.
#

name: Host tests

# Pull requests run every branch under review, so pushes run only for master
on:
push:
branches:
- master
pull_request:

permissions:
contents: read

# A newer commit on a pull request cancels its older run, and each push keeps its own
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }}
cancel-in-progress: true

jobs:
host-tests:
name: host tests
runs-on: ubuntu-latest
timeout-minutes: 5

steps:
- name: Checkout
uses: actions/checkout@v4
with:
persist-credentials: false

- name: Run host tests
run: make host-test
6 changes: 5 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ endif
# Primary Targets
# ============================================================================

.PHONY: all kernel userland image run run-headless clean test test-image \
.PHONY: all kernel userland image run run-headless clean test test-image host-test \
image-x86_64 image-aarch64 \
run-qemu-x86_64 run-qemu-aarch64 \
run-qemu-x86_64-headless run-qemu-aarch64-headless \
Expand Down Expand Up @@ -186,6 +186,10 @@ test: test-image
@echo ""
$(Q)QEMU_X2APIC=$(QEMU_X2APIC) ./scripts/run_tests.sh $(ARCH)

# Builds and runs the userland libraries' tests on the build machine, needing no image
host-test:
$(Q)$(MAKE) --no-print-directory -C userland/lib/libstlxgfx/tests

# ============================================================================
# Disk Image Creation
# ============================================================================
Expand Down
29 changes: 29 additions & 0 deletions userland/lib/libstlxgfx/include/stlxgfx/blend.h
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,33 @@ static inline uint8_t stlxgfx_blend_channel(uint8_t dst, uint32_t src_premul,
return (uint8_t)stlxgfx_div255(src_premul + dst * inv_alpha);
}

/* Source-over of `src` onto `dst` in straight alpha, where color is
* not multiplied by alpha. Both are ARGB, and `dst` alpha must be
* below 255, since opaque pixels blend by `stlxgfx_blend_channel`.
* Onto alpha 0 the result is `src`. Otherwise the alpha is the
* coverage of both pixels together, rounded up, and each channel the
* mean of both colors weighted by the coverage each shows, rounded,
* so it lies between them. */
static inline uint32_t stlxgfx_blend_straight(uint32_t dst, uint32_t src) {
uint32_t src_alpha = src >> 24;
uint32_t dst_alpha = dst >> 24;
if (dst_alpha == 0) {
return src;
}

uint32_t src_weight = src_alpha * 255;
uint32_t dst_weight = dst_alpha * (255 - src_alpha);
uint32_t weight = src_weight + dst_weight;
uint32_t blended = (src_alpha + (dst_weight + 254) / 255) << 24;

for (uint32_t shift = 0; shift < 24; shift += 8) {
uint32_t src_channel = (src >> shift) & 0xFF;
uint32_t dst_channel = (dst >> shift) & 0xFF;
uint32_t sum = src_channel * src_weight + dst_channel * dst_weight;
blended |= ((sum + weight / 2) / weight) << shift;
}

return blended;
}

#endif /* STLXGFX_BLEND_H */
4 changes: 4 additions & 0 deletions userland/lib/libstlxgfx/include/stlxgfx/surface.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
extern "C" {
#endif

/* `flags` bit: blends onto 32 bpp pixels below alpha 255 use `stlxgfx_blend_straight`. */
#define STLXGFX_SURFACE_STRAIGHT_ALPHA (1u << 0)

typedef struct {
uint32_t width;
uint32_t height;
Expand All @@ -18,6 +21,7 @@ typedef struct {
uint8_t blue_shift;
uint8_t* pixels;
int owned;
uint32_t flags;
} stlxgfx_surface_t;

static inline uint8_t stlxgfx_alpha_byte_index(const stlxgfx_surface_t* s) {
Expand Down
42 changes: 38 additions & 4 deletions userland/lib/libstlxgfx/src/surface.c
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
s->green_shift = green_shift;
s->blue_shift = blue_shift;
s->owned = 1;
s->flags = 0;

Check warning on line 50 in userland/lib/libstlxgfx/src/surface.c

View workflow job for this annotation

GitHub Actions / style lint

the stanza from line 41 holds 10 statements, split it into stanzas of at most 6

s->pixels = malloc((size_t)s->pitch * height);
if (!s->pixels) {
Expand Down Expand Up @@ -94,6 +95,7 @@
s->blue_shift = blue_shift;
s->pixels = buffer;
s->owned = 0;
s->flags = 0;

Check warning on line 98 in userland/lib/libstlxgfx/src/surface.c

View workflow job for this annotation

GitHub Actions / style lint

the stanza from line 89 holds 10 statements, split it into stanzas of at most 6

return s;
}
Expand Down Expand Up @@ -341,6 +343,14 @@
return;
}

if (dst->flags & STLXGFX_SURFACE_STRAIGHT_ALPHA) {
uint32_t below = read_pixel(dst_px, dst);
if ((below >> 24) != 0xFF) {
write_pixel(dst_px, dst, stlxgfx_blend_straight(below, src_color));
return;
}
}

uint32_t sr = (src_color >> 16) & 0xFF;
uint32_t sg = (src_color >> 8) & 0xFF;
uint32_t sb = src_color & 0xFF;
Expand Down Expand Up @@ -387,13 +397,25 @@
return 0;
}

uint32_t bytes_pp = s->bpp / 8;
if (s->flags & STLXGFX_SURFACE_STRAIGHT_ALPHA) {
for (int32_t row = y0; row < y1; row++) {
uint8_t* px = s->pixels + (uint32_t)row * s->pitch
+ (uint32_t)x0 * bytes_pp;
for (int32_t col = x0; col < x1; col++, px += bytes_pp) {
blend_pixel(px, s, color);
}
}

return 0;
}

/* Full-screen dim hot path. Packed whole-word blending is what
* stays fast under emulation, measured by gfxbench. */
uint32_t inv = 255 - (uint32_t)sa;
uint32_t spr = ((color >> 16) & 0xFF) * (uint32_t)sa;
uint32_t spg = ((color >> 8) & 0xFF) * (uint32_t)sa;
uint32_t spb = ( color & 0xFF) * (uint32_t)sa;
uint32_t bytes_pp = s->bpp / 8;

if (bytes_pp == 4) {
uint32_t rs = s->red_shift;
Expand Down Expand Up @@ -486,10 +508,22 @@
return 0;
}

/* Per-pixel source alpha: hoist both surfaces' channel offsets and
* read source channels directly, skipping the packed round trip. */
uint32_t dst_bpp = dst->bpp / 8;
uint32_t src_bpp = src->bpp / 8;
if (dst->flags & STLXGFX_SURFACE_STRAIGHT_ALPHA) {
for (int32_t row = 0; row < sh; row++) {
const uint8_t* sp = src->pixels + ((uint32_t)sy + (uint32_t)row) * src->pitch + (uint32_t)sx * src_bpp;
uint8_t* dp = dst->pixels + ((uint32_t)dy + (uint32_t)row) * dst->pitch + (uint32_t)dx * dst_bpp;
for (int32_t col = 0; col < sw; col++, sp += src_bpp, dp += dst_bpp) {
blend_pixel(dp, dst, read_pixel(sp, src));
}
}

return 0;
}

/* Per-pixel source alpha: hoist both surfaces' channel offsets and
* read source channels directly, skipping the packed round trip. */
uint8_t s_rb = src->red_shift / 8;
uint8_t s_gb = src->green_shift / 8;
uint8_t s_bb = src->blue_shift / 8;
Expand Down Expand Up @@ -763,7 +797,7 @@
+ (uint32_t)px * src_bpp, src);
blend_pixel(dst->pixels + (uint32_t)py * dst->pitch
+ (uint32_t)px * dst_bpp,
dst, (cov << 24) | (sample & 0x00FFFFFF));
dst, ((uint32_t)cov << 24) | (sample & 0x00FFFFFF));
}
}
}
Expand Down
6 changes: 6 additions & 0 deletions userland/lib/libstlxgfx/src/text.c
Original file line number Diff line number Diff line change
Expand Up @@ -527,6 +527,7 @@
uint8_t bb = s->blue_shift / 8;
uint8_t ab = stlxgfx_alpha_byte_index(s);
int has_alpha = bytes_pp == 4;
int keeps_straight_alpha = (s->flags & STLXGFX_SURFACE_STRAIGHT_ALPHA) != 0;

Check warning on line 530 in userland/lib/libstlxgfx/src/text.c

View workflow job for this annotation

GitHub Actions / style lint

the stanza from line 523 holds 11 statements, split it into stanzas of at most 6
uint32_t src_r = (color >> 16) & 0xFF;
uint32_t src_g = (color >> 8) & 0xFF;
uint32_t src_b = color & 0xFF;
Expand Down Expand Up @@ -584,6 +585,11 @@
continue;
}

if (keeps_straight_alpha) {
stlxgfx_blend_coverage(s, sx, sy, color, cov[c]);
continue;
}

uint32_t a = cov[c];
if (a == 0) {
continue;
Expand Down
28 changes: 28 additions & 0 deletions userland/lib/libstlxgfx/tests/Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#
# Stellux Userland libstlxgfx Host Tests
#
# Builds each *.test.c with the sources in `SOURCES` by the build machine's own
# compiler and runs it. The top-level `make host-test` runs this.
#

HOST_CC ?= clang

REPO_ROOT := $(abspath ../../../..)
SRC_DIR := ../src
BUILD_DIR := ../build/host
SOURCES := $(SRC_DIR)/surface.c $(SRC_DIR)/text.c
HEADERS := $(wildcard ../include/stlxgfx/*.h ../include/stlxgfx/internal/*.h)
TESTS := $(patsubst %.test.c,$(BUILD_DIR)/%,$(wildcard *.test.c))

CFLAGS := -std=c11 -O2 -g -Wall -Wextra -Werror -I../include \
-DINITRD_DIR='"$(REPO_ROOT)/initrd"'

run: $(TESTS)
@for test in $(TESTS); do echo "[RUN] $$test"; $$test || exit 1; done

$(BUILD_DIR)/%: %.test.c $(SOURCES) $(HEADERS)
@mkdir -p $(BUILD_DIR)
@echo "[CC] $< (host)"
@$(HOST_CC) $(CFLAGS) $< $(SOURCES) -o $@ -lm

.PHONY: run
Loading
Loading