makepad-3d-css.md
This review focuses on Makepad internals under makepad/ and only touches HAVI where needed to frame integration.
Goal: determine whether Makepad can support a HAVI-side compositor path that preserves browser-style CSS 3D semantics when needed:
preserve-3d participation across a subtreeThe question is not whether Makepad can become a browser engine. The question is whether Makepad can expose enough low-level compositor primitives for HAVI to submit surfaces/quads/layers with 4x4 transforms while leaving the normal 2D path intact.
Files:
libs/math/src/math_f32.rsdraw/src/draw_list_2d.rsplatform/src/draw_pass.rsMakepad already has the needed math primitives:
Mat4f::perspectiveMat4f::orthoMat4f::look_atMat4f::mulMat4f::transform_vec4Mat4f::invertThis is enough for a compositor that keeps transforms as 4x4 matrices until final GPU submission.
Important detail: draw/src/draw_list_2d.rs already treats DrawListUniforms.view_transform as a full Mat4f and its helper methods do a perspective divide when mapping points:
map_point_to_localmap_point_from_localThat means the core math model is not limited to affine 2D even though most widgets use it that way.
Files:
platform/src/draw_list.rsdraw/src/draw_list_2d.rsdraw/src/shader/draw_quad.rsDrawListUniforms contains:
view_transform: Mat4fview_clip: Vec4fview_shift: Vec2fDrawList2d exposes:
set_view_transformset_view_transform_self_onlyget_view_transformDrawQuad shaders multiply by draw_list.view_transform before pass projection.
This is a real compositor-relevant primitive: a subtree draw list can already be transformed by a 4x4 matrix.
Limit: the standard 2D shaders clip to axis-aligned rectangles in local/pass space before transform. That is acceptable for ordinary 2D widgets, but it is not enough for true CSS 3D projected clipping.
Files:
draw/src/cx_draw.rsplatform/src/draw_pass.rswidgets/src/view.rswidgets/src/window.rsplatform/src/texture.rsExisting pass primitives:
CxDraw::make_child_passCxDraw::begin_passCxDraw::end_passDrawPass::set_color_textureDrawPass::add_color_textureDrawPass::set_depth_textureExisting render-target textures:
TextureFormat::RenderBGRAu8TextureFormat::RenderRGBAf16TextureFormat::RenderRGBAf32TextureFormat::DepthD32Existing use:
widgets/src/view.rs texture-caches a subtree by rendering it into a child pass and then drawing the resulting texture.widgets/src/window.rs creates a pass-attached depth texture for the main window pass.This is the exact base needed for CSS flattening boundaries:
Files:
platform/src/geometry.rsdraw/src/geometry/geometry_gen.rsdraw/src/shader/draw_pbr.rsMakepad is not restricted to rect-only rendering internally.
It already has:
GeometryGeometry::updateDrawVarsThis matters because a CSS compositor does not need full triangle meshes, but it does need more than rect_pos + rect_size. It needs at minimum:
A companion compositor crate can build this on top of existing Geometry + DrawVars + custom shader without replacing Makepad’s renderer.
Files:
draw/src/shader/draw_pbr.rsdraw/src/shader/draw_text_3d.rswidgets/src/3d/scene_3d.rswidgets/src/3d/gltf_bridge.rsWhat exists:
DrawPbr uses model/view/projection 4x4 matricesDrawText3d projects world positions to screenScene3D builds a 3D camera and submits 3D contentThis proves that Makepad’s GPU/shader/backend stack can already carry 4x4 transforms and depth-tested 3D content.
It does not by itself solve CSS 3D compositing, because CSS needs textured surfaces and browser flattening rules rather than PBR meshes. But it shows the lower levels are capable.
Files:
widgets/src/window.rsplatform/src/draw_pass.rsplatform/src/os/apple/metal.rsplatform/src/os/linux/opengl.rsplatform/src/os/windows/d3d11.rsplatform/src/os/linux/vulkan.rsplatform/src/os/web/web_gl.rsBackends generally enable depth testing and use LESS_OR_EQUAL style depth behavior.
This is enough for a preserve-3d compositor path as long as the submitted quads are actual 3D geometry and not just painter-sorted 2D rects.
File:
platform/src/draw_matrix.rsThere is a DrawMatrix / CxDrawMatrixPool type that appears intended for hierarchical transform nodes.
Current state:
This is evidence that hierarchical transform storage was considered, but it is not a usable foundation today.
Evidence:
Mat4f is a script pod typeuniform(mat4x4f(...))This means a compositor shader can carry 4x4 transforms today. No large shader-language rewrite is required for the base design.
Files:
draw/src/draw_list_2d.rsplatform/src/area.rsplatform/src/event/finger.rsUseful part:
DrawList2d::map_point_to_local and map_point_from_local can invert a 4x4 transform with perspective divide.Blocking part:
Area::clipped_rect is axis-aligned rect logicEvent::hits* tests against Rect / inset-expanded rectsFor HAVI this is acceptable if HAVI owns hit-testing for compositor surfaces. It is not acceptable if the goal is generic Makepad widget-level projected hit-testing.
Files:
draw/src/shader/draw_quad.rsdraw/src/shader/draw_vector.rsplatform/src/area.rsplatform/src/event/finger.rsplatform/src/os/*Current clipping model:
draw_clip and view_clip are axis-aligned rectanglesBackend reality:
ScissorEnable: FALSEglScissor but the draw path does not use it per drawFor CSS 3D, projected ancestor clips need either:
Makepad does not currently provide this.
preserve-3d subtree participation is not modeled anywhereThere is no compositor scene graph concept for:
This is not a backend problem. It is an API/model-layer gap.
DrawPbr is about meshes and material state.
A CSS compositor needs:
That API does not exist today.
Files:
platform/src/draw_shader.rsplatform/src/draw_vars.rsdraw/src/shader/draw_pbr.rsplatform/src/os/*The option exists in the draw system:
CxDrawShaderOptions.depth_writeDrawVarsDrawPbr::set_depth_writeBackend status:
.depth_write_enable(true) in pipeline creationDepthWriteMask = ALLglDepthMaskSo the API exists, but it is not consistently implemented.
This matters for a compositor because:
DrawPbr advertises clip/depth controls that are currently deadFile:
draw/src/shader/draw_pbr.rsDrawPbr exposes:
clip_ndcdepth_rangedepth_forward_biasScene3D computes and supplies these values.
But in the shader code they are uploaded and never actually used in the vertex or fragment path.
So current Makepad 3D does not already provide projected clip rectangles or depth remapping through this API, despite the surface appearance.
Current pieces are low-level and scattered:
DrawPassTextureGeometryThere is no focused API for:
If HAVI uses a companion compositor path, HAVI should own hit-testing for those surfaces.
Trying to extend existing widget Event::hits() semantics to projected polygons would be invasive and unnecessary for the immediate HAVI need.
There is no machinery for:
This is the right omission for now.
For a browser-style layer compositor, projected quads plus offscreen surfaces are enough for the overwhelming majority of CSS 3D usage. Full plane splitting is only required for exact handling of pathological cases where independently transformed layers geometrically intersect in 3D in ways that cannot be represented by simple per-surface depth ordering.
For HAVI’s target, plane splitting is not a first implementation requirement.
Yes for the practical target.
They are enough to implement:
preserve-3d participation inside a compositor groupThey are not enough for exact general handling of all intersecting 3D surfaces if you want mathematically exact order for arbitrary penetrating quads. That requires polygon splitting or a more complex surface decomposition pass.
Recommendation: do not make plane splitting part of the initial Makepad/HAVI CSS 3D compositor plan.
What is required in the first version beyond projected quads and offscreen surfaces is projected clipping. That can be done in shader space with clip planes or projected polygon tests rather than full backend stencil infrastructure.
Technically viable.
This would add a first-class compositor surface/quad API inside existing Makepad crates, likely in makepad-draw plus small makepad-platform backend changes.
This is viable because the critical low-level pieces already exist:
At minimum:
pub struct CompositorSurface {
pub color: Texture,
pub depth: Option<Texture>,
pub size: Vec2d,
}
pub struct CompositorNode {
pub texture: Texture,
pub local_rect: Rect,
pub uv_rect: Rect,
pub transform: Mat4f,
pub opacity: f32,
pub premultiplied: bool,
pub backface_visible: bool,
pub depth_test: bool,
pub depth_write: bool,
pub clip_mode: CompositorClipMode,
pub clip_planes: SmallVec<[Vec4f; 8]>,
}
pub enum CompositorClipMode {
None,
ScreenPlanes,
}
pub struct CompositorPass {
pub pass: DrawPass,
}
impl CompositorPass {
pub fn begin_surface(...)
pub fn end_surface(...)
pub fn draw_node(...)
pub fn draw_nodes(...)
}
Optional higher-level grouping API if you want Makepad itself to understand flattening/preserve-3d boundaries:
pub enum CompositorGroupMode {
Flat,
Preserve3d,
}
I would not put browser semantics such as CSS transform-style enums or DOM concepts into Makepad. Only generic compositor concepts.
The narrowest useful addition is not a full scene graph. It is:
That is enough for HAVI to own the actual compositor tree logic.
Likely:
draw/src/lib.rsdraw/src/shader/draw_compositor_quad.rsdraw/src/compositor.rsplatform/src/draw_shader.rs if adding new draw optionsplatform/src/draw_vars.rs if adding option plumbing or helper settersplatform/src/os/apple/metal.rsplatform/src/os/windows/d3d11.rsplatform/src/os/linux/opengl.rsplatform/src/os/linux/vulkan.rsplatform/src/os/web/web_gl.rsPossible shader-language touch points only if desired:
Minimal useful version:
350-600200-400250-500150-350200-350Total: 1,150-2,200 LOC
If you try to make Makepad itself own compositor groups and flattening logic, increase that to roughly:
2,000-3,500 LOC
API scope creep
Backend consistency work
Projected clipping design
This duplicates logic HAVI already owns
Viable, but only if kept very narrow.
The right Strategy A is not “put CSS 3D in Makepad.” It is “add a generic projected-surface compositor primitive to Makepad.”
makepad-compositor crateThis is the best fit and is technically viable.
A companion crate can sit on top of existing Makepad low-level APIs:
DrawPassTextureGeometryDrawVarsIt can own the compositor model needed by HAVI without polluting core widget/render abstractions.
HAVI already does:
What it needs from Makepad is a rendering/compositor backend, not a second browser engine.
A companion crate can model exactly the missing layer:
I would build the companion crate around three concepts.
pub struct MpSurface {
pub color: Texture,
pub depth: Option<Texture>,
pub pass: DrawPass,
pub size: Vec2d,
}
impl MpSurface {
pub fn begin(cx: &mut Cx2d, parent: Option<&DrawPass>, size: Vec2d, with_depth: bool) -> Self;
pub fn end(&mut self, cx: &mut Cx2d);
}
pub struct MpCompositedQuad {
pub texture: Texture,
pub uv_rect: Rect,
pub local_rect: Rect,
pub transform: Mat4f,
pub opacity: f32,
pub premultiplied: bool,
pub backface_visible: bool,
pub depth_test: bool,
pub depth_write: bool,
pub clip_planes: SmallVec<[Vec4f; 8]>,
}
pub struct MpCompositor {
// internal quad geometry, shader state, temp buffers
}
impl MpCompositor {
pub fn begin_frame(&mut self, cx: &mut Cx2d, target_pass: &DrawPass, viewport: Rect);
pub fn draw_quad(&mut self, cx: &mut Cx2d, quad: &MpCompositedQuad);
pub fn draw_batch(&mut self, cx: &mut Cx2d, quads: &[MpCompositedQuad]);
pub fn end_frame(&mut self, cx: &mut Cx2d);
}
This is enough for HAVI to decide:
I would still upstream a few narrow changes to Makepad itself.
Per-draw depth-write support on all backends
A dedicated projected-quad shader wrapper or enough low-level access to build one cleanly
Small shader-language convenience for clip planes
Optional scissor API
New companion crate, likely under Makepad workspace:
makepad-compositor/src/lib.rsmakepad-compositor/src/surface.rsmakepad-compositor/src/quad.rsmakepad-compositor/src/shader.rsmakepad-compositor/src/batch.rsmakepad-compositor/examples/... or test harnessSmall upstream Makepad touches:
draw/src/lib.rs only if re-export desireddraw/ if crate directly uses makepad_draw and makepad_platformplatform/src/os/apple/metal.rsplatform/src/os/windows/d3d11.rsplatform/src/os/linux/opengl.rsplatform/src/os/linux/vulkan.rsplatform/src/os/web/web_gl.rsCompanion crate itself:
150-300300-550250-500150-300200-400Subtotal: 1,050-2,050 LOC
Small upstream Makepad fixes:
250-50050-150Grand total: 1,350-2,700 LOC
This is similar raw size to Strategy A, but it keeps ownership in the right place.
Backend depth-write inconsistency
Projected clipping policy
Surface lifetime / pooling
Hit-testing boundary
Viable and clean.
This gives HAVI exactly the layer it needs while minimizing Makepad core changes.
makepad-compositor crateThis is the better architecture.
The missing piece is compositor policy, not low-level GPU capability
HAVI already owns browser semantics
Core Makepad should stay general-purpose
The required core fixes are narrow
Fix per-draw depth_write on:
platform/src/os/linux/vulkan.rsplatform/src/os/windows/d3d11.rsplatform/src/os/linux/opengl.rsplatform/src/os/web/web_gl.rsDo not extend widget hit-testing.
Do not add plane splitting.
makepad-compositor crateImplement:
DrawPass + TextureHAVI uses the companion crate to:
| Work item | Files | LOC estimate |
|---|---|---|
| Backend per-draw depth-write fixes | platform/src/os/* | 250-500 |
| Projected textured quad shader/wrapper | new draw or companion files | 300-600 |
| Offscreen surface helper | companion crate or draw helper | 150-300 |
| Batch encoder / node submission | companion crate | 250-500 |
| Clip-plane support | companion shader/API | 150-300 |
| Tests/examples/docs | companion + examples | 200-400 |
| Strategy A total, narrow in-Makepad API | mixed | 1,150-2,200 |
| Strategy B total, companion crate + small Makepad fixes | mixed | 1,350-2,700 |
| Strategy A if Makepad owns compositor groups too | mixed | 2,000-3,500 |
draw/src/shader/draw_quad.rs clips local rects with draw_clip and view_clip before final projection.platform/src/area.rs reconstructs clipped rects as axis-aligned rectangles.platform/src/event/finger.rs hit-tests those rectangles.That is correct for existing UI and wrong for browser CSS 3D semantics.
DrawListUniforms.view_transform is a full Mat4fDrawList2d::map_point_to_local already does inverse + perspective divideThis is enough for a compositor layer, not for widget hit-testing.
widgets/src/view.rs proves Makepad can render subtrees into textures and composite them later.This is the core flattening primitive required by CSS 3D.
DrawPbr is useful as proof of backend capability.DrawPbr clip/depth API currently overstates what existsclip_ndc, depth_range, depth_forward_bias are present in Rust state and uploaded as uniforms.So current Scene3D is not already a reusable CSS compositor substrate.
platform/src/draw_matrix.rs should not be the basis of the design.Makepad already has enough low-level rendering machinery for a proper HAVI CSS 3D compositor path:
What it does not already have is the right compositor-facing API.
The missing work is medium-sized, not large.
The right design is:
makepad-compositor crate around projected textured quads and offscreen surfacesProjected quads + offscreen surfaces are enough for the first real implementation. Projected clipping is required. Plane splitting is not a first-version requirement.
Makepad is not a browser compositor today, but it is close enough at the GPU primitive level that HAVI does not need a renderer rewrite.
What Makepad already has:
Mat4fWhat is missing:
Decision:
makepad-compositor crate is the better choice.Recommended path:
makepad-compositorBest estimate: