Skip to content

Commit ac93354

Browse files
committed
compat.pixman 0.46.4:软件合成路径,SIMD 用 per-glob 标志而不是 fork
合成器不是所有东西都交给 GPU —— 损伤区域、光标混合、没有 EGL 时的回退路径,都走 pixman。cairo / X / Mesa 也都坐在它上面。 内联描述符,不需要 fork,而这一点值得说清楚:pixman 上游是**每个指令集一个静态库**, 各自用那个指令集的标志编一个 .c(meson.build:63 的 foreach)。包级 cflags 表达不了 —— "-mssse3" 加到所有文件上,编译器就可能在 pixman-x86.c 的 CPUID 检查**之前**发出 SSSE3 指令,在老 CPU 上是非法指令异常,而这个库的全部设计就是为了避免这件事。 [build] flags 带 glob 正好表达它,而且不是新机制 —— compat.sdl2 早就用它把 -msse3 限定到单个文件。所以既不用 fork 也不用 build.mcpp。与 freedesktop.gldispatch 对照: 那边按架构变的是**编哪些文件**,这边变的是**给哪个文件什么标志**,前者才需要 build.mcpp。 实测确认标志没有外溢: pixman-ssse3.o SSSE3 指令 2 ← 应当有 pixman-sse2.o SSSE3 指令 0 pixman.o SSSE3 指令 0 pixman-fast-path.o SSSE3 指令 0 pixman-x86.o SSSE3 指令 0 踩到一个静默的坑:PIXMAN_API 定义在 pixman-version.h.in 里,不在编译器头里。生成 version.h 时漏掉它,pixman.h 里每个 "PIXMAN_API void pixman_fill(...)" 都会解析成 未知标识符、声明整个丢失 —— 而报出来的错误是某个 SIMD 文件里的 "implicit declaration of function pixman_fill",与真正的原因隔了十万八千里。 测试做真合成:256x256 全部 65536 个像素与合成色一致,外加区域并集运算 —— 后者是 合成器每帧都要调的损伤跟踪,纯 C、无 SIMD。
1 parent 892d2a5 commit ac93354

4 files changed

Lines changed: 312 additions & 0 deletions

File tree

mcpp.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ members = [
8787
"tests/examples/wayland",
8888
"tests/examples/egl",
8989
"tests/examples/gl",
90+
"tests/examples/pixman",
9091
"tests/examples/wayland-protocols",
9192
"tests/examples/libgbm",
9293
"tests/examples/libpng",

pkgs/c/compat.pixman.lua

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
-- compat.pixman — pixman 0.46.4, the pixel-manipulation library.
2+
--
3+
-- The software compositing path. A Wayland compositor uses it for the surfaces
4+
-- it cannot hand to the GPU — damage regions, cursor blending, the fallback
5+
-- when there is no EGL — and cairo, X and Mesa all sit on it too.
6+
--
7+
-- ─────────────────────────────────────────────────────────────────────────
8+
-- SHAPE: an inline descriptor, and the SIMD is why that is worth saying
9+
--
10+
-- pixman is a separable project with its own releases, so by the criterion it
11+
-- is a source build. What made it look like it needed a fork is the SIMD:
12+
-- upstream builds ONE STATIC LIBRARY PER INSTRUCTION SET, each from a single
13+
-- `.c` compiled with that set's flags:
14+
--
15+
-- foreach simd : simds # meson.build:63
16+
-- pixman_simd_libs += static_library(
17+
-- 'pixman-' + simd[0], [name + '.c', ...], c_args : simd[2])
18+
--
19+
-- Package-wide `cflags` cannot express that. `-mssse3` applied to every file
20+
-- would let the compiler emit SSSE3 in code that runs before the CPUID check
21+
-- in `pixman-x86.c` — an illegal-instruction crash on an older CPU, from a
22+
-- library whose whole design is to dispatch at runtime.
23+
--
24+
-- `[build] flags` with a `glob` is what expresses it, and this is not a new
25+
-- mechanism: `compat.sdl2` already scopes `-msse3` to one file the same way.
26+
-- So no fork, no `build.mcpp`. Contrast `freedesktop.gldispatch`, where the
27+
-- per-architecture choice is WHICH FILES to compile rather than which flags to
28+
-- give them — that one does need `build.mcpp`.
29+
--
30+
-- ─────────────────────────────────────────────────────────────────────────
31+
-- WHAT IS COMPILED AND WHAT IS NOT
32+
--
33+
-- The x86 SIMD (`pixman-sse2.c`, `pixman-ssse3.c`) is in; MMX is not. MMX is
34+
-- 32-bit-x86 era and upstream itself gates it behind `have_mmx`, which is
35+
-- false on x86_64 toolchains. The ARM/MIPS/PPC/RISC-V variants come with
36+
-- assembly files and their own probes; they are absent here for the same
37+
-- reason `freedesktop.egl` leaves X11 out — nothing in this index builds for
38+
-- those targets yet, and the runtime dispatch degrades to the generic path
39+
-- rather than failing.
40+
--
41+
-- `pixman-config.h` and `pixman-version.h` are meson's `configure_file`
42+
-- outputs. They are generated here rather than probed: every value in them is
43+
-- a property of the target, and the target is Linux with a GCC-compatible
44+
-- toolchain.
45+
package = {
46+
spec = "1",
47+
namespace = "compat",
48+
name = "pixman",
49+
description = "pixman 0.46.4 — low-level pixel manipulation, with runtime-dispatched x86 SIMD",
50+
licenses = {"MIT"},
51+
repo = "https://gitlab.freedesktop.org/pixman/pixman",
52+
type = "package",
53+
54+
xpm = {
55+
linux = {
56+
["0.46.4"] = {
57+
url = {
58+
GLOBAL = "https://gitlab.freedesktop.org/pixman/pixman/-/archive/pixman-0.46.4/pixman-pixman-0.46.4.tar.gz",
59+
CN = "https://gitcode.com/mcpp-res/pixman/releases/download/0.46.4/pixman-0.46.4.tar.gz",
60+
},
61+
sha256 = "1b8288086e5da0ec5cb95cf174a919cc6fe4548f10dc3cd873b3bb1d9e8fdeab",
62+
},
63+
},
64+
},
65+
66+
mcpp = {
67+
language = "c++23",
68+
import_std = false,
69+
c_standard = "c11",
70+
71+
include_dirs = { "*/pixman", "mcpp_generated" },
72+
73+
generated_files = {
74+
["mcpp_generated/pixman-config.h"] = [[
75+
#ifndef PIXMAN_CONFIG_H
76+
#define PIXMAN_CONFIG_H
77+
/* meson's configure_file output, written out for the one target this
78+
package builds: Linux, x86_64, a GCC-compatible toolchain. */
79+
#define USE_SSE2 1
80+
#define USE_SSSE3 1
81+
#define USE_GCC_INLINE_ASM 1
82+
#define HAVE_PTHREADS 1
83+
#define HAVE_POSIX_MEMALIGN 1
84+
#define HAVE_MMAP 1
85+
#define HAVE_MPROTECT 1
86+
#define HAVE_GETPAGESIZE 1
87+
#define HAVE_SYS_MMAN_H 1
88+
#define HAVE_UNISTD_H 1
89+
#define HAVE_FENV_H 1
90+
#define HAVE_FEDIVBYZERO 1
91+
#define HAVE_FEENABLEEXCEPT 1
92+
#define HAVE_BUILTIN_CLZ 1
93+
#define HAVE_FLOAT128 1
94+
#define TOOLCHAIN_SUPPORTS_ATTRIBUTE_CONSTRUCTOR 1
95+
#define TLS __thread
96+
#define PACKAGE "pixman"
97+
#endif
98+
]],
99+
["mcpp_generated/pixman-version.h"] = [[
100+
#ifndef PIXMAN_VERSION_H__
101+
#define PIXMAN_VERSION_H__
102+
#ifndef PIXMAN_H__
103+
# error pixman-version.h should only be included by pixman.h
104+
#endif
105+
#define PIXMAN_VERSION_MAJOR 0
106+
#define PIXMAN_VERSION_MINOR 46
107+
#define PIXMAN_VERSION_MICRO 4
108+
#define PIXMAN_VERSION_STRING "0.46.4"
109+
#define PIXMAN_VERSION_ENCODE(major, minor, micro) ( \
110+
((major) * 10000) \
111+
+ ((minor) * 100) \
112+
+ ((micro) * 1))
113+
#define PIXMAN_VERSION PIXMAN_VERSION_ENCODE( \
114+
PIXMAN_VERSION_MAJOR, \
115+
PIXMAN_VERSION_MINOR, \
116+
PIXMAN_VERSION_MICRO)
117+
118+
/* PIXMAN_API lives HERE upstream, not in a compiler header, and leaving it
119+
out is silent: every `PIXMAN_API void pixman_fill(...)` in pixman.h then
120+
parses as an unknown identifier and the declaration is lost. What surfaces
121+
is `implicit declaration of function 'pixman_fill'` from a SIMD file that
122+
has nothing to do with it. */
123+
#ifndef PIXMAN_API
124+
# define PIXMAN_API
125+
#endif
126+
#endif
127+
]],
128+
},
129+
130+
sources = {
131+
"*/pixman/pixman.c",
132+
"*/pixman/pixman-access.c",
133+
"*/pixman/pixman-access-accessors.c",
134+
"*/pixman/pixman-arm.c",
135+
"*/pixman/pixman-bits-image.c",
136+
"*/pixman/pixman-combine32.c",
137+
"*/pixman/pixman-combine-float.c",
138+
"*/pixman/pixman-conical-gradient.c",
139+
"*/pixman/pixman-edge.c",
140+
"*/pixman/pixman-edge-accessors.c",
141+
"*/pixman/pixman-fast-path.c",
142+
"*/pixman/pixman-filter.c",
143+
"*/pixman/pixman-glyph.c",
144+
"*/pixman/pixman-general.c",
145+
"*/pixman/pixman-gradient-walker.c",
146+
"*/pixman/pixman-image.c",
147+
"*/pixman/pixman-implementation.c",
148+
"*/pixman/pixman-linear-gradient.c",
149+
"*/pixman/pixman-matrix.c",
150+
"*/pixman/pixman-mips.c",
151+
"*/pixman/pixman-noop.c",
152+
"*/pixman/pixman-ppc.c",
153+
"*/pixman/pixman-radial-gradient.c",
154+
"*/pixman/pixman-region16.c",
155+
"*/pixman/pixman-region32.c",
156+
"*/pixman/pixman-region64f.c",
157+
"*/pixman/pixman-riscv.c",
158+
"*/pixman/pixman-solid-fill.c",
159+
"*/pixman/pixman-timer.c",
160+
"*/pixman/pixman-trap.c",
161+
"*/pixman/pixman-utils.c",
162+
"*/pixman/pixman-x86.c",
163+
-- the runtime-dispatched implementations
164+
"*/pixman/pixman-sse2.c",
165+
"*/pixman/pixman-ssse3.c",
166+
},
167+
168+
cflags = { "-D_GNU_SOURCE", "-DHAVE_CONFIG_H", "-fPIC" },
169+
170+
-- The whole reason this package needs no fork. Each entry gives ONE
171+
-- file the instruction set it implements, so the compiler may emit
172+
-- those instructions there and nowhere else — which is what makes
173+
-- pixman's CPUID dispatch in pixman-x86.c safe.
174+
flags = {
175+
{ glob = "*/pixman/pixman-sse2.c", cflags = { "-msse2" } },
176+
{ glob = "*/pixman/pixman-ssse3.c", cflags = { "-mssse3" } },
177+
},
178+
179+
targets = { ["pixman-1"] = { kind = "lib" } },
180+
},
181+
}

tests/examples/pixman/mcpp.toml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# pixman test project.
2+
#
3+
# Linux-only like its graphics-stack neighbours; the test compiles to a no-op
4+
# main() elsewhere.
5+
[package]
6+
name = "pixman-tests"
7+
version = "0.1.0"
8+
standard = "c++23"
9+
10+
[target.'cfg(linux)'.dependencies.compat]
11+
pixman = "0.46.4"
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
// compat.pixman — behavioral test.
2+
//
3+
// The interesting thing about this package is not that it links; it is that
4+
// pixman dispatches on the CPU at runtime, and this index compiles the SSE2
5+
// and SSSE3 implementations with per-file `-msse2` / `-mssse3` so that the
6+
// compiler may emit those instructions THERE and nowhere else. If the flags
7+
// had been package-wide, code running before pixman's own CPUID check could
8+
// carry SSSE3 — an illegal instruction on an older machine, from a library
9+
// whose entire design is to avoid exactly that.
10+
//
11+
// So the test does two things a "does it link" check would not:
12+
//
13+
// * asks pixman which implementation it CHOSE, through a real composite;
14+
// * composites actual pixels and reads them back, because the SIMD paths
15+
// are only reached with enough work to be worth dispatching to.
16+
//
17+
// No display, no GPU, no threads.
18+
19+
#ifdef __linux__
20+
21+
#include <pixman.h>
22+
23+
#include <cstdio>
24+
#include <cstdlib>
25+
#include <cstring>
26+
#include <cstdint>
27+
#include <vector>
28+
29+
namespace {
30+
31+
int failures = 0;
32+
33+
void check(bool ok, const char *what)
34+
{
35+
std::printf("%-58s %s\n", what, ok ? "ok" : "FAILED");
36+
if (!ok) {
37+
++failures;
38+
}
39+
}
40+
41+
} // namespace
42+
43+
int main()
44+
{
45+
// ── 1. The library is the version this package says it is ────────────
46+
std::printf(" pixman %s (header %d.%d.%d)\n", pixman_version_string(),
47+
PIXMAN_VERSION_MAJOR, PIXMAN_VERSION_MINOR, PIXMAN_VERSION_MICRO);
48+
check(pixman_version() == PIXMAN_VERSION,
49+
"the linked library and the header agree on the version");
50+
check(PIXMAN_VERSION_MAJOR == 0 && PIXMAN_VERSION_MINOR == 46,
51+
"…and it is 0.46, the version the descriptor pins");
52+
53+
// ── 2. A real composite, over enough pixels to matter ────────────────
54+
// 256x256 ARGB: large enough that pixman routes through a fast path
55+
// rather than the trivial one, which is what exercises the dispatched
56+
// implementation the SIMD flags were for.
57+
const int W = 256, H = 256;
58+
std::vector<std::uint32_t> dst(static_cast<std::size_t>(W) * H, 0u);
59+
60+
pixman_color_t colour{};
61+
colour.red = 0x4000;
62+
colour.green = 0x8000;
63+
colour.blue = 0xc000;
64+
colour.alpha = 0xffff;
65+
pixman_image_t *src = pixman_image_create_solid_fill(&colour);
66+
check(src != nullptr, "pixman_image_create_solid_fill");
67+
68+
pixman_image_t *out = pixman_image_create_bits(
69+
PIXMAN_a8r8g8b8, W, H, dst.data(), W * 4);
70+
check(out != nullptr, "pixman_image_create_bits 256x256 a8r8g8b8");
71+
72+
if (src != nullptr && out != nullptr) {
73+
pixman_image_composite32(PIXMAN_OP_SRC, src, nullptr, out,
74+
0, 0, 0, 0, 0, 0, W, H);
75+
76+
// Every pixel must be the colour that was composited. A dispatch that
77+
// picked a broken implementation shows up here rather than as a crash.
78+
const std::uint32_t want = 0xff4080c0u;
79+
std::size_t wrong = 0;
80+
for (std::uint32_t p : dst) {
81+
if (p != want) {
82+
++wrong;
83+
}
84+
}
85+
std::printf(" composited %dx%d, first pixel 0x%08x (wanted 0x%08x)\n",
86+
W, H, dst[0], want);
87+
check(wrong == 0, "every pixel is the colour that was composited");
88+
}
89+
90+
// ── 3. Region arithmetic, the other half of what a compositor uses ───
91+
// Damage tracking is pixman_region32, and it is pure C — no SIMD, but it
92+
// is the part a Wayland compositor calls on every frame.
93+
{
94+
pixman_region32_t a, b, r;
95+
pixman_region32_init_rect(&a, 0, 0, 100, 100);
96+
pixman_region32_init_rect(&b, 50, 50, 100, 100);
97+
pixman_region32_init(&r);
98+
check(pixman_region32_union(&r, &a, &b) != 0, "pixman_region32_union");
99+
const pixman_box32_t *e = pixman_region32_extents(&r);
100+
std::printf(" union extents: (%d,%d)-(%d,%d)\n", e->x1, e->y1, e->x2, e->y2);
101+
check(e->x1 == 0 && e->y1 == 0 && e->x2 == 150 && e->y2 == 150,
102+
"…and the extents are the union of the two rects");
103+
pixman_region32_fini(&a);
104+
pixman_region32_fini(&b);
105+
pixman_region32_fini(&r);
106+
}
107+
108+
if (src != nullptr) { pixman_image_unref(src); }
109+
if (out != nullptr) { pixman_image_unref(out); }
110+
111+
std::printf("\n%d check(s) failed\n", failures);
112+
return failures == 0 ? 0 : 1;
113+
}
114+
115+
#else
116+
117+
int main() { return 0; }
118+
119+
#endif

0 commit comments

Comments
 (0)