c/3d/vml3d_tri.c · 11388 字节 · 下载原文
/* vml3d_tri.c —— **VML 3D(600–699 号段)的证明链最小例子**。
* vml3d_tri.c — the minimal example that closes the **VML 3D (syscall band 600–699)** proof chain.
*
* 它只做一件事,但把链子上每一环都走了一遍:
* It does one thing, but it exercises every link in the chain:
*
* 600 ui_3d_surface_create(w,h) → 建一块 w×h 的离屏表面(macOS 上是 CGL 上下文 + 自建 FBO)
* 600 ui_3d_surface_create(w,h) -> create a w x h off-screen surface (on macOS a CGL context + a self-made FBO)
* 601 ui_3d_surface_set_clear(surf, A) → 清成已知色 A
* 601 ui_3d_surface_set_clear(surf, A) -> clear to the known colour A
* 602 ui_3d_surface_tri(...) → 画一个已知色 B 的实心三角形(**屏幕像素坐标**)
* 602 ui_3d_surface_tri(...) -> draw a solid triangle in the known colour B (**screen pixel coordinates**)
* 603 ui_3d_surface_present(surf) → 提交并呈现(清屏 + 一次 draw call)
* 603 ui_3d_surface_present(surf) -> submit and present (clear + one draw call)
* 605 ui_3d_surface_get_pixel(surf,x,y) → **在 GPU 的输出上读回一个像素**
* 605 ui_3d_surface_get_pixel(surf,x,y) -> **read one pixel back from the GPU output**
*
* 判据(判定脚本 `scripts/vml-3d-probe/chain/3d-check-chain.sh` 会核对):
* Criteria (checked by `scripts/vml-3d-probe/chain/3d-check-chain.sh`):
* · 三角形**内**的点必须等于 B —— 证明 GPU 真的把这个三角形画出来了
* · points **inside** the triangle must equal B -- proving the GPU really drew the triangle
* · 三角形**外**的点必须等于 A —— 证明清屏色也真的生效了
* · points **outside** the triangle must equal A -- proving the clear colour really took effect too
* 反证:把下面的 `CHAIN_CLEAR` 改掉 ⇒ 上面那条必须红(证明读回不是常量、也不在读上一帧)。
* Counter-check: change `CHAIN_CLEAR` below => the criterion above must go red (proving the readback is not a constant and is not reading the previous frame).
*
* ## 为什么坐标是屏幕像素
* ## Why the coordinates are screen pixels
* 本步的 `602` 是**临时基元**(只为把链子打通);保留模式(视口 + 物体)落地后它会被
* The `602` primitive here is **temporary** (only to close the chain); once retained mode (viewport + objects) lands it is
* `ui_3d_surface_verts`/`ui_3d_mesh_create` 那一路取代。像素坐标是给"肉眼 + 断言"用的最直白的坐标系。
* superseded by the `ui_3d_surface_verts`/`ui_3d_mesh_create` path. Pixel coordinates are the most direct system for "eyeball + assertion".
*
* ## ⚠ 为什么这里**一行汇编都没有**(原先有 7 个本地的 `ui_3d_surface_*` 包装)
* ## ⚠ Why there is **no inline assembly here at all** (there used to be 7 local wrappers)
*
* 3D 接口**已经在库里了**(`Lib/shared/src/vml3d.c` → `Lib/c/vml_3d.h`),与本地那 7 个**逐字段一致**
* ⇒ 例程只要 `#include <vml_3d.h>`。以前那 7 个 `asm("SYSCALL #600…")` 是"库还没有 C 头"时代的抄写;
* 今天留着它们只会**多出一份要跟着改的东西**(改一次接口要改两处 —— 本仓点名的第二号形态)。
*
* The 3D API **already lives in the library** (`Lib/shared/src/vml3d.c` -> `Lib/c/vml_3d.h`) with
* field-for-field identical signatures, so `#include <vml_3d.h>` is all this sample needs. Those 7
* hand-rolled wrappers dated from "the library had no C header yet"; keeping them today would just be
* one more copy to keep in sync.
*
* ⚠ 当年在这里踩到的两条写法约束**仍然成立**,只是现在归**库那一层**管(要写新包装时去看):
* ① `asm("SYSCALL #N, ${a}")` 的实参**一律用局部量或形参** —— 全局量会静默丢参数;
* ② `asm` **必须当表达式用**(`return asm(...)`),写成"语句 + return 局部量"会丢返回值。
* 两条的完整实测记录见 `Lib/shared/src/vmlsys.c` 文件头与 `Lib/README-ui.md`。
*
* 跑法(桌面,**需要 macOS**:GL 后端目前只有 macOS 一个实现;其它平台 `ui_3d_surface_create` 返回负错误码):
* dotnet scripts/vmlcli/bin/Release/net10.0/vmlcli.dll Examples/c/vml3d_tri.c
* 手机上:命令行页敲 `vml run examples/c/vml3d_tri.c`(⚠ 本步的 3D 后端还没做移动端)
* On phone: type `vml run examples/c/vml3d_tri.c` on the command-line page (⚠ the 3D backend for this step is not done on mobile yet)
*/
#include <vml_ui.h>
#include <vml_3d.h> /* 3D 声明(含常量)—— 本文件**不再自己包汇编**,见下面那段说明 */
#include <stdio.h>
/* 两个已知色 —— 反证就是改这一行(判定脚本会在 /tmp 的副本上改,见 E126) */
/* Two known colours -- the counter-check is to change this line (the script edits a copy in /tmp; see E126) */
#define CHAIN_CLEAR -15720400 /* 0xFF102030 有符号写法 */
/* 0xFF102030, signed form */
#define CHAIN_TRI -2047936 /* 0xFFE0C040 有符号写法 */
/* 0xFFE0C040, signed form */
/* ⚠ 这里原先有 7 个本地的 `ui_3d_surface_*` 包装(`asm("SYSCALL #600…")` 一个号一个)——
* 它们与 `Lib/shared/src/vml3d.c` 的定义**逐字段相同**,已随"例程不用汇编"一并删除,
* 改由 `#include <vml_3d.h>` 引入。删它们**不改任何行为**(调用点一个字都没动)。
* Those 7 local wrappers were field-for-field identical to `Lib/shared/src/vml3d.c`; they are gone
* in favour of `#include <vml_3d.h>`, with no behaviour change and no edit at any call site. */
int main(void) {
int w = 64;
int h = 64;
int surf = ui_3d_surface_create(w, h);
printf("SURF=%d\n", surf);
if (surf <= 0) {
/* 拿不到 3D 设备(非 macOS / 驱动不给上下文)—— 如实报,不装作画出来了 */
/* could not get a 3D device (not macOS / the driver gave no context) -- report it honestly, do not pretend it drew */
printf("NO_3D_DEVICE=1\n");
return 0;
}
printf("A=%08X\n", CHAIN_CLEAR);
printf("B=%08X\n", CHAIN_TRI);
ui_3d_surface_set_clear(surf, CHAIN_CLEAR);
/* 一个"上下左右都不靠边"的三角形:顶点 (8,8) (56,8) (32,56) */
/* a triangle that touches no edge: vertices (8,8) (56,8) (32,56) */
int x0 = 8; int y0 = 8;
int x1 = 56; int y1 = 8;
int x2 = 32; int y2 = 56;
ui_3d_surface_tri(surf, x0, y0, x1, y1, x2, y2, CHAIN_TRI);
ui_3d_surface_present(surf);
/* 三角形**内**:重心 (32,24);三角形**外**:(2,2) 与 (60,60) */
/* **inside** the triangle: centroid (32,24); **outside**: (2,2) and (60,60) */
int inx = 32; int iny = 24;
int ox = 2; int oy = 2;
int px = 60; int py = 60;
int vin = ui_3d_surface_get_pixel(surf, inx, iny);
int vout = ui_3d_surface_get_pixel(surf, ox, oy);
int vout2 = ui_3d_surface_get_pixel(surf, px, py);
/* ⚠ **再读一次三角形内**(换一个内点 (32,12)):若读回实现"停在缓冲区末尾",
/* ⚠ **Read inside once more** (a different inner point, (32,12)): if the readback "stops at the end of the buffer",
* 它会静默返回**上一个读到的**值(也就是 OUT 的 A)—— 那正是探针栽过的坑。
* it silently returns the **previously read** value (i.e. the OUT colour A) -- exactly the trap the probe fell into.
* 所以这一条不是重复,是"读回真的跟着坐标走"的证据。
* So this line is not a repeat: it is the proof that the readback really follows the coordinates. */
int inx2 = 32; int iny2 = 12;
int vin2 = ui_3d_surface_get_pixel(surf, inx2, iny2);
printf("IN=%08X\n", vin);
printf("OUT=%08X\n", vout);
printf("OUT2=%08X\n", vout2);
printf("IN2=%08X\n", vin2);
/* ── 第二帧:走 **606 ui_3d_surface_verts**(GPU 原生 float32 批量顶点,零封送那条路)──────────
* Second frame: the **606 ui_3d_surface_verts** path (GPU-native float32 vertex batch, zero marshalling).
*
* 顶点数据就写在**一个普通的 float 数组**里 —— 那是**首选形态**:数据的字节布局与
* The vertex data lives in a plain **float array** -- the preferred form: its byte layout is already
* `glVertexAttribPointer(GL_FLOAT, 2)` 要的**逐字节同构**,宿主一个字节都不搬、一次类型都不转。
* byte-for-byte identical to what `glVertexAttribPointer(GL_FLOAT, 2)` wants, so the host moves not a byte and converts not a type.
* 用 `int` 数组摆位模式(**位搬运**,不是数值转换)也能走同一条路,宿主侧完全一样 ——
* an `int` array holding those bit patterns (a **bit move**, not a numeric conversion) works the same way; the host side is identical --
* 它只按 float32 读那段内存,不关心嘉宾用什么容器装。
* it just reads that memory as float32 and does not care what container the guest used. */
float verts[6];
verts[0] = 8.0f; verts[1] = 8.0f;
verts[2] = 56.0f; verts[3] = 8.0f;
verts[4] = 32.0f; verts[5] = 56.0f;
/* ⚠ 取地址要写成 **`&verts[0]`(取元素)**,不要写 `(int)verts`(用**数组名**):
/* ⚠ Take the ADDRESS OF AN ELEMENT: write **`&verts[0]`**, not `(int)verts` (the **array name**):
* 后者走的是 C 的 array-to-pointer 退化那条路,而本前端那条路**时好时坏**
* the latter goes through C's array-to-pointer decay path, and that path is **flaky** in this front end
* (2026-10-01 实测:同一份源码两次跑,`(int)arr` 一次给出正确地址、一次给 0;
* (measured 2026-10-01: the same source run twice, `(int)arr` gave the correct address once and 0 the other time;
* 见 `FRONTEND_DEFECTS.md` 里那条数组名退化,以及 `scripts/vml-3d-probe/chain/f4-array-decay.c`)。
* see the array-decay entry in `FRONTEND_DEFECTS.md` and `scripts/vml-3d-probe/chain/f4-array-decay.c`).
* 地址给成 0 的症状是**画面上什么都没有**(顶点全从 0 号地址读),而程序不报错。
* A zero address shows up as **nothing on screen** (all vertices read from address 0), with no error.
* 取元素地址是稳的 —— 实测多轮全对。
* Taking the address of an element is stable -- many rounds measured, all correct. */
int vptr = (int)&verts[0];
int nv = 6; /* 顶点数(必须是 3 的倍数) */
/* vertex count (must be a multiple of 3) */
int vcol = CHAIN_TRI;
ui_3d_surface_set_clear(surf, CHAIN_CLEAR);
ui_3d_surface_verts(surf, vptr, nv, vcol);
ui_3d_surface_present(surf);
int vin3 = ui_3d_surface_get_pixel(surf, inx, iny);
int vout3 = ui_3d_surface_get_pixel(surf, ox, oy);
printf("VIN=%08X\n", vin3);
printf("VOUT=%08X\n", vout3);
ui_3d_surface_free(surf);
printf("DONE=1\n");
return 0;
}