docs: explain scene composition and 3D panel coordinate flows
This commit is contained in:
@@ -12,10 +12,13 @@ use crate::{
|
||||
#[derive(Clone, Debug, Default)]
|
||||
/// 宿主管理区域的几何布局,供窗口事件分辨页面与工具命中。
|
||||
pub struct SceneLayout {
|
||||
// 开发工具的逻辑坐标布局;页面业务对象的命中几何仍由 RenderDocument 管理。
|
||||
pub developer_tools: DeveloperToolsLayout,
|
||||
}
|
||||
|
||||
/// 组合页面、开发工具和侧栏的设备像素显示列表,同时返回供命中使用的宿主布局。
|
||||
/// shell/app 的窗口绘制路径调用此入口;width/height 是整窗设备像素,scale 将逻辑坐标换成设备坐标。
|
||||
/// 返回的显示列表交给窗口渲染,layout 则保留同帧开发工具命中区域,避免输入使用另一帧的几何。
|
||||
pub fn build_scene(
|
||||
width: u32,
|
||||
height: u32,
|
||||
@@ -32,8 +35,10 @@ pub fn build_scene(
|
||||
.iter()
|
||||
.find(|tab| tab.active)
|
||||
.and_then(|tab| tab.developer_tool.as_deref());
|
||||
// 先切换当前工具,再通知文档变化;工具绘制由此读取与活动标签一致的状态。
|
||||
developer_tools_state.select(tool);
|
||||
developer_tools_state.document_updated(document);
|
||||
// left 是侧栏占用的逻辑宽度,surface 保存业务页面的 pan/zoom;二者属于不同坐标层。
|
||||
let left = sidebar.left();
|
||||
let surface = &document.frame.surface;
|
||||
// width/height 为设备像素,sidebar.left 为逻辑单位,先乘 scale 得到页面可用裁剪区域。
|
||||
@@ -56,6 +61,7 @@ pub fn build_scene(
|
||||
(left - surface.pan.x * zoom) * scale,
|
||||
-surface.pan.y * zoom * scale,
|
||||
));
|
||||
// 从空列表开始按背景、页面、工具、侧栏的顺序叠加;后加入的命令绘制在前面内容之上。
|
||||
let mut display_list = DisplayList::default();
|
||||
// 全窗只做一次 Clear,后续跳过页面列表自身 Clear,防止其覆盖侧栏或突破页面裁剪。
|
||||
display_list.push(DrawCommand::Clear(
|
||||
@@ -63,12 +69,15 @@ pub fn build_scene(
|
||||
.unwrap_or(crate::system_ui::theme::BG),
|
||||
));
|
||||
display_list.push(DrawCommand::PushClip(content));
|
||||
// 按页面原有顺序复制命令;页面自身的嵌套裁剪仍处于 content 这个外层裁剪之内。
|
||||
for command in page.commands() {
|
||||
// Clear 是整目标缓冲清屏操作,只有非 Clear 命令可以并入已建立背景的窗口列表。
|
||||
if !matches!(command, DrawCommand::Clear(_)) {
|
||||
display_list.push(command.clone());
|
||||
}
|
||||
}
|
||||
display_list.push(DrawCommand::PopClip);
|
||||
// 工具先在扣除侧栏的独立画布内构建,宽度用饱和减法防止窄窗口发生无符号下溢。
|
||||
let (mut developer_list, mut developer_layout) = developer_tools::build(
|
||||
width.saturating_sub((left * scale) as u32),
|
||||
height,
|
||||
@@ -89,6 +98,7 @@ pub fn build_scene(
|
||||
);
|
||||
chrome.scale(scale);
|
||||
display_list.extend(chrome);
|
||||
// 把绘制输出与这次构建产生的交互布局绑定返回,调用方无需重新计算工具位置。
|
||||
DisplayScene {
|
||||
display_list,
|
||||
layout: SceneLayout {
|
||||
@@ -99,6 +109,8 @@ pub fn build_scene(
|
||||
|
||||
/// 整窗合成结果:显示列表与同帧管理交互几何一起返回。
|
||||
pub struct DisplayScene {
|
||||
// 已完成缩放与平移的整窗绘制命令,供 shell 的渲染路径消费。
|
||||
pub display_list: DisplayList,
|
||||
// 保留逻辑坐标供宿主输入路由使用,不能直接当成设备像素命中区域。
|
||||
pub layout: SceneLayout,
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ use std::ops::{Add, Mul, Sub};
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Serialize, Deserialize)]
|
||||
/// 三维数学向量;构造不校验分量,需在输入边界显式调用 valid,不是托管 Vec3 对象。
|
||||
// model/scene3d 用它读取场景变换,原生光栅化、网格拾取和 panel 坐标映射共同复用此类型。
|
||||
pub struct Vec3 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
@@ -48,21 +49,21 @@ impl Vec3 {
|
||||
}
|
||||
impl Add for Vec3 {
|
||||
type Output = Self;
|
||||
/// 按分量执行向量加减或标量乘法,保持数学运算不依赖 VM 状态。
|
||||
/// 按分量相加;摄像机射线用它叠加前向与视平面偏移。
|
||||
fn add(self, b: Self) -> Self {
|
||||
Self::new(self.x + b.x, self.y + b.y, self.z + b.z)
|
||||
}
|
||||
}
|
||||
impl Sub for Vec3 {
|
||||
type Output = Self;
|
||||
/// 按分量执行向量加减或标量乘法,保持数学运算不依赖 VM 状态。
|
||||
/// 按分量相减;观察基与三角形求交用它得到相对位置和边向量。
|
||||
fn sub(self, b: Self) -> Self {
|
||||
Self::new(self.x - b.x, self.y - b.y, self.z - b.z)
|
||||
}
|
||||
}
|
||||
impl Mul<f32> for Vec3 {
|
||||
type Output = Self;
|
||||
/// 按分量执行向量加减或标量乘法,保持数学运算不依赖 VM 状态。
|
||||
/// 每个分量乘同一个标量;归一化、射线步进和光照计算用它调整向量长度。
|
||||
fn mul(self, b: f32) -> Self {
|
||||
Self::new(self.x * b, self.y * b, self.z * b)
|
||||
}
|
||||
@@ -70,9 +71,10 @@ impl Mul<f32> for Vec3 {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
/// 仿射 4×4 变换矩阵,支持局部到世界的组合与求逆。
|
||||
// model/scene3d 从 TRS 构造并组合世界矩阵;panel 使用其逆矩阵将射线交点还原为局部 UI 位置。
|
||||
pub struct Matrix(pub [f32; 16]);
|
||||
impl Default for Matrix {
|
||||
/// 建立类型约定的初始值,供尚未发布或尚未交互的状态使用。
|
||||
/// 返回单位矩阵,供场景遍历的根变换使用;与局部矩阵组合时保持原变换。
|
||||
fn default() -> Self {
|
||||
Self([
|
||||
1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1., 0., 0., 0., 0., 1.,
|
||||
@@ -121,7 +123,9 @@ impl Matrix {
|
||||
pub fn compose(self, local: Self) -> Self {
|
||||
// 行主序存储,点视作列向量;self.compose(local) = self × local,即先应用 local 再应用 self。
|
||||
let mut out = [0.; 16];
|
||||
// 逐行逐列计算输出的 16 个元素;k 遍历 self 的行与 local 的列,累加四项点积。
|
||||
for row in 0..4 {
|
||||
// row/col 都属于输出矩阵;不能交换两个乘数,否则父子变换顺序会反转。
|
||||
for col in 0..4 {
|
||||
out[row * 4 + col] = (0..4)
|
||||
.map(|k| self.0[row * 4 + k] * local.0[k * 4 + col])
|
||||
@@ -141,10 +145,11 @@ impl Matrix {
|
||||
/// 由平移、角度旋转和缩放构造局部仿射矩阵。
|
||||
pub fn trs(position: Vec3, rotation: Vec3, scale: Vec3) -> Self {
|
||||
// 输入欧拉角单位为度,先转换为弧度。组合为 T × Rz × Ry × Rx × S,列分别乘对应缩放轴。
|
||||
// 分别缓存三个轴的正弦和余弦,后面的矩阵展开式复用这些值。
|
||||
let (sx, cx) = rotation.x.to_radians().sin_cos();
|
||||
let (sy, cy) = rotation.y.to_radians().sin_cos();
|
||||
let (sz, cz) = rotation.z.to_radians().sin_cos();
|
||||
// Local X, then Y, then Z rotation; scale first, translation last.
|
||||
// 点先缩放,再依次绕局部 X、Y、Z 旋转,最后加平移;这里直接展开组合矩阵以避免临时矩阵。
|
||||
Self([
|
||||
cz * cy * scale.x,
|
||||
(cz * sy * sx - sz * cx) * scale.y,
|
||||
@@ -177,16 +182,21 @@ pub enum Projection {
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
|
||||
/// 原生摄像机描述,包括观察基、近远面和投影参数;宽高比由视口提供。
|
||||
// 场景发布时由 model 验证参数;View 渲染/拾取与 panel 映射随后使用同一份相机描述。
|
||||
pub struct Camera {
|
||||
// 世界空间眼睛位置;透视投影的全部拾取射线从这里出发。
|
||||
pub position: Vec3,
|
||||
// 世界空间观察目标,用 target-position 确定前向,而不是直接保存欧拉角。
|
||||
pub target: Vec3,
|
||||
// 提供上方向参考,basis 会重新正交化;与前向平行时无法形成观察基。
|
||||
pub up: Vec3,
|
||||
// 近远面按相机正向深度裁剪;valid 要求 0 < near < far。
|
||||
pub near: f32,
|
||||
pub far: f32,
|
||||
pub projection: Projection,
|
||||
}
|
||||
impl Default for Camera {
|
||||
/// 建立类型约定的初始值,供尚未发布或尚未交互的状态使用。
|
||||
/// 返回从世界 z=5 观察原点的透视相机,供缺省场景与原生测试构造使用。
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
position: Vec3::new(0., 0., 5.),
|
||||
@@ -211,7 +221,9 @@ impl Camera {
|
||||
// forward 指向观察目标;up 输入只提供大致上方向,叉积后重新正交化。
|
||||
// 位置等于目标、up 为零或与前向平行时,normalized 返回 None。
|
||||
let forward = (self.target - self.position).normalized()?;
|
||||
// 前向叉上方向产生右向;再次归一化,消除输入 up 长度对后续投影的影响。
|
||||
let right = forward.cross(self.up).normalized()?;
|
||||
// 用已确定的右向和前向重建垂直上向,让三个基向量彼此正交。
|
||||
let up = right.cross(forward).normalized()?;
|
||||
Some(Basis { right, up, forward })
|
||||
}
|
||||
@@ -227,6 +239,7 @@ impl Camera {
|
||||
&& self.near > 0.
|
||||
&& self.far > self.near
|
||||
&& self.far <= 1_000_000.
|
||||
// 投影类型决定最后的合法性条件;透视角度受限以避免极端 tan,正交高度必须为正。
|
||||
&& match self.projection {
|
||||
Projection::Perspective { fov } => fov.is_finite() && (1.0..=175.0).contains(&fov),
|
||||
Projection::Orthographic { height } => {
|
||||
@@ -242,6 +255,7 @@ impl Camera {
|
||||
}
|
||||
/// 从视口归一化坐标生成透视或正交射线;宽高比由具体视口提供。
|
||||
pub fn ray(self, u: f32, v: f32, aspect: f32) -> Option<Ray> {
|
||||
// View::pick 和 panel::Mapping 的逆投影都从这里生成射线;退化观察基直接向上传播 None。
|
||||
let basis = self.basis()?;
|
||||
// u/v 通常是视口内 [0,1],映射到 [-1,1] 并翻转屏幕 y。
|
||||
// 此函数不钳制输入,捕获拖动可以从视口外继续生成射线;aspect 由调用方保证有效。
|
||||
@@ -276,6 +290,7 @@ pub struct Ray {
|
||||
}
|
||||
impl Ray {
|
||||
/// 求射线与三角形相交距离和重心坐标,拒绝退化三角形及射线起点后方的交点。
|
||||
// View::pick 遍历网格三角形时调用,使用返回的射线参数选最近命中,重心权重还可插值纹理坐标。
|
||||
pub fn triangle(self, a: Vec3, b: Vec3, c: Vec3) -> Option<(f32, [f32; 3])> {
|
||||
// Möller–Trumbore 求交:使用两条三角形边与叉积解重心系数,不先构造平面方程。
|
||||
let ab = b - a;
|
||||
@@ -286,6 +301,7 @@ impl Ray {
|
||||
if determinant.abs() < 1e-7 {
|
||||
return None;
|
||||
}
|
||||
// 复用行列式倒数以解重心系数;t 此处是从顶点 a 指向射线起点的向量。
|
||||
let inverse = 1.0 / determinant;
|
||||
let t = self.origin - a;
|
||||
// u 是顶点 b 的重心权重,必须位于 [0,1];v 是 c 的权重,a 的权重由 1-u-v 得到。
|
||||
@@ -293,8 +309,10 @@ impl Ray {
|
||||
if !(0.0..=1.0).contains(&u) {
|
||||
return None;
|
||||
}
|
||||
// 第二次叉积用于同时求顶点 c 的权重和射线参数,避免另外求解完整线性方程组。
|
||||
let q = t.cross(ab);
|
||||
let v = self.direction.dot(q) * inverse;
|
||||
// v 为负或 b/c 权重和超过 1 都表示交点落在三角形外,即使它仍位于三角形所在平面。
|
||||
if v < 0. || u + v > 1. {
|
||||
return None;
|
||||
}
|
||||
|
||||
@@ -21,16 +21,20 @@ pub struct Mapping {
|
||||
pub owner: u64,
|
||||
// 面板在场景中的稳定标识,不能仅凭 Block 身份选择某个具体面板出现。
|
||||
pub key: u64,
|
||||
// 保存这次出现所使用的不可变场景和相机;命中必须与生成画面的视图一致。
|
||||
pub view: Arc<View>,
|
||||
// [x,y,width,height] 是所属二维空间中的视口布局矩形,宽高也决定射线的 aspect。
|
||||
pub rect: [f32; 4],
|
||||
// 外层二维相机变换;逆映射先处理它,再进入 3D 摄像机射线计算。
|
||||
pub outer: Transform,
|
||||
// 当前层的二维裁剪,在逆 outer 之前检查;None 表示这一层没有额外矩形裁剪。
|
||||
pub clip: Option<[f32; 4]>,
|
||||
// 嵌套面板的外层链;反投影从父到子,正投影从子到父。
|
||||
pub parent: Option<Arc<Mapping>>,
|
||||
}
|
||||
impl Mapping {
|
||||
/// 沿父面板逆投影到当前平面的 UI 像素;要求可见时使用最近射线命中拒绝被遮挡的目标。
|
||||
// RenderDocument 的面板坐标转换调用这里服务命中与事件派发;clipped=false 用于捕获后的连续拖动。
|
||||
pub fn texture_point(&self, point: Point, clipped: bool) -> Option<Point> {
|
||||
// 先把最外层窗口坐标逐层反投影为父面板像素,再检查当前面板所属空间的裁剪。
|
||||
let point = if let Some(parent) = &self.parent {
|
||||
@@ -45,19 +49,24 @@ impl Mapping {
|
||||
{
|
||||
return None;
|
||||
}
|
||||
// 先撤销当前层二维平移/缩放,后面的 rect 才能按未变换布局坐标归一化。
|
||||
let point = self.outer.inverse(point);
|
||||
let [x, y, width, height] = self.rect;
|
||||
// 零或负视口没有可用面积,不能计算归一化位置或宽高比。
|
||||
if width <= 0. || height <= 0. {
|
||||
return None;
|
||||
}
|
||||
// rect 是 3D 视口在当前二维空间的位置,归一化坐标传给该视口相机;不是面板纹理坐标。
|
||||
let (u, v) = ((point.x - x) / width, (point.y - y) / height);
|
||||
// key 标识当前出现的面板网格;找不到时不退回其他面板,以免把事件发给错误对象。
|
||||
let mesh = self.view.scene.meshes.iter().find(|mesh| {
|
||||
mesh.panel
|
||||
.as_ref()
|
||||
.is_some_and(|panel| panel.key == self.key)
|
||||
})?;
|
||||
// panel 提供实际纹理像素宽高;它与世界几何尺寸分开,缩放网格不会改变 UI 的像素坐标。
|
||||
let panel = mesh.panel.as_ref()?;
|
||||
// 普通命中要求当前可见;捕获手势跳过这一步,使指针离开面板或被遮挡后仍能继续拖动。
|
||||
if clipped {
|
||||
// 指针落在平面投影内还不够:最近三角形必须属于本面板,不能点穿前方网格。
|
||||
let hit = self.view.pick(u, v, width / height)?;
|
||||
@@ -71,6 +80,7 @@ impl Mapping {
|
||||
}
|
||||
// 捕获拖动可以不要求指针仍位于面板内,但仍使用原面板平面的连续逆映射。
|
||||
let ray = self.view.camera.ray(u, v, width / height)?;
|
||||
// 平面中心与两条世界轴共同确定相交平面;不直接把局部 z 轴当作非均匀缩放后的法线。
|
||||
let center = mesh.world.point(Vec3::default());
|
||||
let normal = (mesh.world.point(Vec3::new(1., 0., 0.)) - center)
|
||||
.cross(mesh.world.point(Vec3::new(0., 1., 0.)) - center);
|
||||
@@ -100,6 +110,7 @@ impl Mapping {
|
||||
}
|
||||
|
||||
/// 把当前面板的 UI 像素正向投影到外层页面空间,供坐标查询和交互测试使用。
|
||||
// model/mount 的坐标查询使用此正投影,将面板内布局位置还原到外层页面供调用者定位。
|
||||
pub fn project_point(&self, point: Point) -> Option<Point> {
|
||||
let mesh = self.view.scene.meshes.iter().find(|mesh| {
|
||||
mesh.panel
|
||||
@@ -107,6 +118,7 @@ impl Mapping {
|
||||
.is_some_and(|panel| panel.key == self.key)
|
||||
})?;
|
||||
let panel = mesh.panel.as_ref()?;
|
||||
// 面板几何由矩形网格构造路径保证顶点顺序;这里读取局部宽高,再与纹理分辨率换算。
|
||||
let width = mesh.geometry.vertices[1].x - mesh.geometry.vertices[0].x;
|
||||
let height = mesh.geometry.vertices[0].y - mesh.geometry.vertices[2].y;
|
||||
// 正向路径与 texture_point 对称:纹理像素先转局部平面点,再进入世界空间。
|
||||
@@ -115,6 +127,7 @@ impl Mapping {
|
||||
(0.5 - point.y / panel.height as f32) * height,
|
||||
0.,
|
||||
));
|
||||
// 世界点先转换成相机的右/上/前坐标,后续用正向深度决定裁剪和透视除数。
|
||||
let camera = self.view.camera;
|
||||
let local = camera.view(camera.basis()?, world);
|
||||
// 点在近远面之外时无可用投影;此方法仅算位置,不检查前方遮挡或二维 clip。
|
||||
@@ -127,6 +140,7 @@ impl Mapping {
|
||||
Projection::Perspective { fov } => local.z * (fov.to_radians() / 2.).tan(),
|
||||
Projection::Orthographic { height } => height / 2.,
|
||||
};
|
||||
// 归一化投影恢复为视口逻辑坐标,最后施加所属二维层的 outer 变换。
|
||||
let point = self.outer.point(Point::new(
|
||||
x + (local.x / (half * w / h) + 1.) * w / 2.,
|
||||
y + (1. - local.y / half) * h / 2.,
|
||||
@@ -152,23 +166,28 @@ struct Entry {
|
||||
/// Collect a panel's ordinary paint subtree into its texture. Nested 3D
|
||||
/// commands are assembled recursively before their containing texture.
|
||||
/// 按面板所属视口和稳定 key 分组绘制命令,递归将内部 UI 装配进 3D 面板表面。
|
||||
/// source_paint 在生成各 Mount 命令后调用;输入的 command_clips、command_views、command_mounts 必须与 commands 对齐,归属 ID 必须能查到 Mount。
|
||||
pub fn compose(frame: &mut crate::document::Frame) -> anyhow::Result<()> {
|
||||
use crate::document::RenderCommand;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
// 按 Mount ID 建立查找表,让每条命令直接取到所属面板映射,避免逐条扫描完整 Mount 列表。
|
||||
let mounts: HashMap<_, _> = frame.mounts.iter().map(|mount| (mount.id, mount)).collect();
|
||||
// 分组键包含视口 owner 和面板 key,防止同一场景在两个视口出现时互相混入绘制命令。
|
||||
let mut groups = HashMap::<(u64, u64), Vec<Entry>>::new();
|
||||
let mut roots = Vec::new();
|
||||
// 每条命令都有出现归属;据此分流为窗口根命令或某个世界面板的内部命令。
|
||||
for (index, command) in frame.commands.iter().enumerate() {
|
||||
// owner 是该条绘制命令的 Mount ID;面板分组使用 mount.panel 内的外层视口 owner。
|
||||
let owner = frame.command_mounts[index];
|
||||
let mount = mounts[&owner];
|
||||
// 克隆命令并携带同索引裁剪、二维变换,移动到纹理后仍保持原始绘制语义。
|
||||
let entry = Entry {
|
||||
command: command.clone(),
|
||||
clip: frame.command_clips[index],
|
||||
view: frame.command_views[index],
|
||||
owner,
|
||||
};
|
||||
// 面板后代收进对应纹理分组;不属于面板的命令保留为根层,二者都维持输入相对顺序。
|
||||
if let Some(panel) = &mount.panel {
|
||||
groups
|
||||
.entry((panel.owner, panel.key))
|
||||
@@ -207,6 +226,7 @@ pub fn compose(frame: &mut crate::document::Frame) -> anyhow::Result<()> {
|
||||
path: &mut HashSet<u64>,
|
||||
depth: usize,
|
||||
) -> anyhow::Result<Entry> {
|
||||
// 仅本次条目可变;没有面板的普通命令可原样返回,不需要复制整个场景。
|
||||
let mut entry = entry;
|
||||
if let RenderCommand::Scene3D { view, .. } = &mut entry.command
|
||||
&& view.scene.meshes.iter().any(|mesh| mesh.panel.is_some())
|
||||
@@ -218,8 +238,10 @@ pub fn compose(frame: &mut crate::document::Frame) -> anyhow::Result<()> {
|
||||
);
|
||||
// 只构造新的原生快照,不写回源 Scene3D;同一业务场景仍可被其他视口共享。
|
||||
let mut scene = (*view.scene).clone();
|
||||
// 每个面板取得自己分组的 UI 命令;普通网格没有纹理子树,直接跳过。
|
||||
for mesh in &mut scene.meshes {
|
||||
if let Some(panel) = &mut mesh.panel {
|
||||
// 一张表面保存三组平行数据;每个子条目装配成功后同时追加,保持索引对应。
|
||||
let mut surface = super::UiSurface {
|
||||
commands: Vec::new(),
|
||||
clips: Vec::new(),
|
||||
@@ -232,9 +254,11 @@ pub fn compose(frame: &mut crate::document::Frame) -> anyhow::Result<()> {
|
||||
surface.clips.push(child.clip);
|
||||
surface.views.push(child.view);
|
||||
}
|
||||
// 即使分组为空也发布空表面,避免继承源快照中与本次出现无关的旧绘制内容。
|
||||
panel.surface = Some(Arc::new(surface));
|
||||
}
|
||||
}
|
||||
// 新场景替换本条目引用,相机保持不变;其他命令持有的 Arc<View> 不受影响。
|
||||
*view = Arc::new(View {
|
||||
scene: Arc::new(scene),
|
||||
camera: view.camera,
|
||||
@@ -244,12 +268,14 @@ pub fn compose(frame: &mut crate::document::Frame) -> anyhow::Result<()> {
|
||||
}
|
||||
Ok(entry)
|
||||
}
|
||||
// 所有根条目共用临时祖先集合,每条递归返回时删除标记;depth 从根的 0 开始计算。
|
||||
let mut path = HashSet::new();
|
||||
// 面板内部命令已经移入 UiSurface,根帧只保留外层命令;四张平行元数据表同步重建。
|
||||
frame.commands.clear();
|
||||
frame.command_clips.clear();
|
||||
frame.command_views.clear();
|
||||
frame.command_mounts.clear();
|
||||
// 递归错误会在清空之后返回,此函数本身不保证出错时恢复原帧;调用方需处理整帧失败。
|
||||
for entry in roots {
|
||||
let entry = assemble(entry, &groups, &mut path, 0)?;
|
||||
frame.commands.push(entry.command);
|
||||
@@ -261,11 +287,14 @@ pub fn compose(frame: &mut crate::document::Frame) -> anyhow::Result<()> {
|
||||
}
|
||||
|
||||
/// 为一个面板分配有限像素表面,并调用普通 UI 软件渲染;当前实现不会按面板内容独立缓存纹理。
|
||||
/// 软件光栅化的面板纹理路径调用;本函数每次产生新像素数组,复用策略由外层调用方决定。
|
||||
pub fn texture(panel: &super::Panel) -> Result<Vec<u32>, &'static str> {
|
||||
// 先向统一像素预算申请有限纹理面积,再分配背景缓冲;嵌套面板同样受该预算约束。
|
||||
crate::software_renderer::reserve_pixels(panel.width as usize * panel.height as usize)?;
|
||||
// u32 缓冲按纹理宽高铺满背景;没有 UiSurface 时直接返回这张纯背景纹理。
|
||||
let mut pixels =
|
||||
vec![super::rgb(panel.background); panel.width as usize * panel.height as usize];
|
||||
// 有 UI 子树才生成显示列表并绘制;解析或渲染错误向上返回,不把半成品作为成功纹理。
|
||||
if let Some(surface) = &panel.surface {
|
||||
// 将普通 UI 表面包装为临时文档复用现有显示列表和软件渲染路径;比例 1 对应纹理像素。
|
||||
let document = crate::document::RenderDocument {
|
||||
@@ -278,6 +307,7 @@ pub fn texture(panel: &super::Panel) -> Result<Vec<u32>, &'static str> {
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
// 比例为 1,保持子 UI 坐标和纹理像素一一对应;无效命令转换成纹理路径的简短错误。
|
||||
let list = document
|
||||
.display_list(1.)
|
||||
.map_err(|_| "invalid 3D UI texture commands")?;
|
||||
|
||||
Reference in New Issue
Block a user