feat: xorg viewer scale modes, resize fix, arch notes

Scale modes (STRETCH/FIT/FILL/1:1) with CENTER/TOP_LEFT anchor:
- UV crop via u_uv_scale/u_uv_offset uniforms in vertex shader
- glViewport sub-rect + glClear for FIT and 1:1 modes
- xorg_viewer_set_scale() / xorg_viewer_set_anchor() setters
- Stub implementations for both

Resize fix: glfwSetWindowUserPointer + framebuffer_size_callback calls
render() synchronously during resize so image tracks window edge
immediately. Forward declaration added to fix implicit decl error.

Q/Escape close the window via key_callback.

xorg_cli: --scale and --anchor arguments added.

architecture.md:
- Scale mode table and anchor docs in Frame Viewer Sink section
- Render loop design note: frame-driven not timer-driven, resize callback
  rationale, threading note (GL context ownership, frame queue)
- Text overlay section: tier 1 bitmap atlas (Pillow build tool, skyline
  packing, quad rendering), tier 2 HarfBuzz+FreeType, migration path

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-28 21:30:28 +00:00
parent ef0319b45b
commit 7fd79e6120
6 changed files with 413 additions and 56 deletions

125
dev/cli/xorg_cli.c Normal file
View File

@@ -0,0 +1,125 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "test_image.h"
#include "xorg.h"
static void usage(void)
{
fprintf(stderr,
"usage: xorg_cli [--pattern bars|ramp|grid]\n"
" [--width N] [--height N]\n"
" [--format yuv420|bgra]\n"
" [--scale stretch|fit|fill|1:1]\n"
" [--anchor center|topleft]\n"
" [--x N] [--y N]\n"
"\n"
"Opens a window and renders a test image using the xorg viewer sink.\n"
"Q or Escape closes the window.\n"
"\n"
"defaults: bars 1280x720 yuv420 stretch center at 0,0\n");
}
int main(int argc, char **argv)
{
Test_Pattern pattern = TEST_PATTERN_BARS;
Test_Fmt fmt = TEST_FMT_YUV420;
Xorg_Scale scale = XORG_SCALE_STRETCH;
Xorg_Anchor anchor = XORG_ANCHOR_CENTER;
int width = 1280;
int height = 720;
int win_x = 0;
int win_y = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "--pattern") == 0 && i + 1 < argc) {
i++;
if (strcmp(argv[i], "bars") == 0) { pattern = TEST_PATTERN_BARS; }
else if (strcmp(argv[i], "ramp") == 0) { pattern = TEST_PATTERN_RAMP; }
else if (strcmp(argv[i], "grid") == 0) { pattern = TEST_PATTERN_GRID; }
else { fprintf(stderr, "unknown pattern: %s\n", argv[i]); usage(); return 1; }
} else if (strcmp(argv[i], "--width") == 0 && i + 1 < argc) {
width = atoi(argv[++i]);
} else if (strcmp(argv[i], "--height") == 0 && i + 1 < argc) {
height = atoi(argv[++i]);
} else if (strcmp(argv[i], "--x") == 0 && i + 1 < argc) {
win_x = atoi(argv[++i]);
} else if (strcmp(argv[i], "--y") == 0 && i + 1 < argc) {
win_y = atoi(argv[++i]);
} else if (strcmp(argv[i], "--scale") == 0 && i + 1 < argc) {
i++;
if (strcmp(argv[i], "stretch") == 0) { scale = XORG_SCALE_STRETCH; }
else if (strcmp(argv[i], "fit") == 0) { scale = XORG_SCALE_FIT; }
else if (strcmp(argv[i], "fill") == 0) { scale = XORG_SCALE_FILL; }
else if (strcmp(argv[i], "1:1") == 0) { scale = XORG_SCALE_1_1; }
else { fprintf(stderr, "unknown scale: %s\n", argv[i]); usage(); return 1; }
} else if (strcmp(argv[i], "--anchor") == 0 && i + 1 < argc) {
i++;
if (strcmp(argv[i], "center") == 0) { anchor = XORG_ANCHOR_CENTER; }
else if (strcmp(argv[i], "topleft") == 0) { anchor = XORG_ANCHOR_TOP_LEFT; }
else { fprintf(stderr, "unknown anchor: %s\n", argv[i]); usage(); return 1; }
} else if (strcmp(argv[i], "--format") == 0 && i + 1 < argc) {
i++;
if (strcmp(argv[i], "yuv420") == 0) { fmt = TEST_FMT_YUV420; }
else if (strcmp(argv[i], "bgra") == 0) { fmt = TEST_FMT_BGRA; }
else { fprintf(stderr, "unknown format: %s\n", argv[i]); usage(); return 1; }
} else {
usage(); return 1;
}
}
if (!xorg_available()) {
fprintf(stderr, "xorg_cli: built without HAVE_GLFW — viewer not available\n");
return 1;
}
if (width < 2 || height < 2) {
fprintf(stderr, "xorg_cli: width and height must be >= 2\n");
return 1;
}
Test_Frame *f = test_image_alloc(width, height, fmt);
if (!f) {
fprintf(stderr, "xorg_cli: allocation failed\n");
return 1;
}
test_image_generate(f, pattern);
const char *pat_name = pattern == TEST_PATTERN_BARS ? "bars"
: pattern == TEST_PATTERN_RAMP ? "ramp"
: "grid";
const char *fmt_name = fmt == TEST_FMT_YUV420 ? "yuv420" : "bgra";
const char *scale_name = scale == XORG_SCALE_STRETCH ? "stretch"
: scale == XORG_SCALE_FIT ? "fit"
: scale == XORG_SCALE_FILL ? "fill"
: "1:1";
const char *anchor_name = anchor == XORG_ANCHOR_CENTER ? "center" : "topleft";
printf("opening %dx%d %s %s scale=%s anchor=%s at (%d,%d)\n",
width, height, fmt_name, pat_name, scale_name, anchor_name, win_x, win_y);
Xorg_Viewer *v = xorg_viewer_open(win_x, win_y, width, height, "xorg_cli");
xorg_viewer_set_scale(v, scale);
xorg_viewer_set_anchor(v, anchor);
if (!v) {
fprintf(stderr, "xorg_cli: failed to open viewer window\n");
test_image_free(f);
return 1;
}
if (fmt == TEST_FMT_YUV420) {
xorg_viewer_push_yuv420(v,
f->plane[0], f->plane[1], f->plane[2],
f->width, f->height);
} else {
xorg_viewer_push_bgra(v, f->plane[0], f->width, f->height);
}
test_image_free(f);
while (xorg_viewer_poll(v)) { /* wait for window close */ }
xorg_viewer_close(v);
return 0;
}