Commit Graph
11 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.7 398eee551d feat(pipeline): per-instance visibility for batched hatches (Phase 4-B, step 4)
Adds a dedicated `visibility: array<u32>` storage buffer (binding 4)
alongside the existing batched hatch buffers. Vertex shader reads
`visibility[v.instance_index]` — 0 → emit an out-of-NDC clip
position so the GPU rasterizer clips the primitive before the
fragment stage runs.

`compute_hatch_lod` now drives this buffer instead of (or alongside,
for the per-hatch fallback) `hatch_skip_flags`. Walks a per-instance
CPU AABB mirror that the builder fills at upload time (`instance_aabbs`),
applies the same `aabb_below_pixel` (sub-pixel LOD) OR `aabb_offscreen`
(frustum cull) rule the legacy path used, and pushes the resulting
0/1 flags via `queue.write_buffer` on a separate 4 B-per-hatch buffer
— ~40 KB per pan tick for 10 k hatches, far cheaper than re-writing
the 112 B-per-instance data.

Step 5 will remove the legacy `gpu_hatches` Vec, the per-hatch draw
loop, and `hatch_skip_flags` once the batched path has soaked.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-17 20:11:28 +03:00
Hakan SevenandClaude Opus 4.7 cfe53bd789 fix(pipeline): batched-hatch pattern math + invisible-instance clip
Two issues in the step-3 batched path surfaced during testing:

1. WGSL rejects compile-time NaN literals (`0.0 / 0.0`), so the
   visibility-skip path that emitted a NaN clip position failed
   `Device::create_shader_module` validation. Replaced with an
   out-of-frustum clip position `(2, 2, 2, 1)` — the GPU rasterizer
   clips the primitive and the fragment shader never runs.

2. Pattern hatches rendered with the wrong spacing / direction. The
   batched builder was rotating `perp_step` / `along_step` into the
   global frame, but the per-hatch shader (`hatch.wgsl`) expects them
   in QCAD PAT local-frame convention: `perp_step = family.dy`,
   `along_step = family.dx`, with the cos_off/sin_off rotation applied
   inside the shader. Now matches `build_family_batch` from
   `hatch_gpu.rs` line-for-line. `line_width` also reset to 0 (the
   shader picks 1 px from a screen-space derivative; the stored field
   is unused).

3. Padding's `max_spacing` likewise dropped its bogus rotation and
   now uses `|dy|` per family, matching the new perp_step convention.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-17 20:04:16 +03:00
Hakan SevenandClaude Opus 4.7 60c742e21a feat(pipeline): batched-hatch WGSL shader (Phase 4-B, step 2)
Companion to the storage-buffer layout from step 1. Vertex shader
reads `(corner, instance_index)` per vertex, fetches the matching
`HatchInstance`, projects an AABB-quad corner; a zero `visible` flag
emits a NaN clip position so the GPU skips the fragment stage for
culled hatches without any per-frame buffer re-upload.

Fragment shader runs the same three-stage in-polygon → mode dispatch
→ pattern evaluation pipeline as `hatch.wgsl`, but with all ranges
indexed: `BoundaryBuffer[boundary_offset..offset+count]` for the
ray-cast, `FamilyBuffer[family_offset..offset+count]` for pattern
lines, `DashBuffer[dash_offset..]` for dash sequences. Phase 3.3
sub-pixel LOD substitution is preserved.

Step 3 will wire a pipeline + bind-group-layout around this and call
`upload_hatches_batched` from render.rs (the per-hatch
`upload_hatches` path stays in place for the bisect window).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-17 19:46:51 +03:00
Hakan SevenandClaude Opus 4.7 172c538159 fix(scene): hatch boundary + pattern precision for large-WCS drawings
Two precision problems were producing visibly wrong hatches on UTM-scale
(and similarly-offset) drawings even after world_offset was correct:

1. **Boundary tessellation** — `hatch_model_from_dxf`'s polyline-bulge
   arc tessellation ran the whole arc-center computation in f32. With
   vertices at WCS magnitude ~1e5 the f32 ULP is ~1 cm, which propagated
   into the sampled arc points and showed up as visibly wavy / jagged
   hatch boundaries on edges that crossed the long axis of the drawing.
   Re-run the full bulge → arc math in f64; only cast at the to_xy
   call that feeds the (also f64) boundary buffer.

2. **Pattern phase** — the per-fragment `perp / perp_step` modulo blew
   precision when `xz` was at offset-rel WCS magnitude and perp_step
   was sub-cm. Move the precision-critical bits out of f32 by keeping
   each hatch's anchor in f64 on the model side: `HatchModel` now
   carries a `world_origin: [f64; 2]` and the `boundary: [f32; 2]`
   vertices are stored as small offsets from it. The GPU vertex shader
   adds `origin` back inside the view_proj multiply for clip position
   (sub-pixel-lossy at large hatches but invisible); the `xz` varying
   that the fragment shader uses for in_polygon + pattern math stays
   in hatch-local space at full f32 precision.

   For pattern alignment across adjacent hatches with the same family,
   the GPU pipeline snaps the anchor to the family's perp/along grid
   in f64 before sending it down. Snap uses the same QCAD-PAT
   convention (`fam.dy = perp_step`, `fam.dx = along_step`) the
   shader's check_family uses — an earlier attempt used the world-frame
   `(-dx*sin + dy*cos)` formula, which produced a meaningless grid and
   no alignment.

   Trade-off: pattern phase is "global modulo per-family-grid" rather
   than tied to WCS (0,0). Same hatches still align with each other;
   the absolute phase relative to the WCS origin may differ from the
   pre-fix behaviour on small drawings.

Consumers that read `model.boundary` directly (hit_test, paper_canvas,
add_hatch round-trip) reconstruct WCS via `boundary[i] + world_origin`.
The hatch wgsl bind-group entry for binding 0 was bumped to
`VERTEX | FRAGMENT` because the vertex shader now reads `h.origin`.

The fragment-shader hatch LOD substitute + sub-pixel skip from e7675be
and the scissor plumbing from fbbac42 still work — those touched
different fields of the same struct.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-16 08:41:23 +03:00
Hakan SevenandClaude Opus 4.7 e7675be0bc perf(scene): hatch LOD — solid-fill substitute + sub-pixel skip (Phase 3.3)
Hatch fills drew the full per-family pattern loop every frame regardless
of zoom. Two LOD tiers, neither of which existed:

  * **Shader-side solid substitute.** When the densest line family's
    spacing × scale projects to less than 2 px, individual lines blur
    into a solid color and the per-fragment family loop is wasted ALU
    (and produces moiré). Pass `world_per_pixel` through Uniforms; the
    hatch fragment shader now returns `h.color` directly in that case.
  * **CPU-side sub-pixel skip.** When the entire hatch AABB projects
    to less than 2 px, drop the draw call. Adds `world_aabb` to
    HatchGpu plus a per-frame `compute_hatch_lod` pass (mirrors the
    existing scissor pass) and `hatch_skip_flags`. The render pass
    skips flagged hatches.

Factored a shared `aabb_below_pixel` helper for future LOD callers.

The Uniforms layout is unchanged in size (96 B) — the trailing
`_pad: vec2<f32>` becomes `world_per_pixel: f32, _pad: f32`, so wgsl
modules that don't read the new field keep working with their old
`_pad: vec2<f32>` declarations.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-15 23:18:21 +03:00
Hakan Seven 773a5f74cb fix(hatch): more improvement on arc boundary and pattern 2026-05-10 20:29:39 +03:00
Hakan SevenandClaude Sonnet 4.6 298098e764 perf: dedicated Face3D shader — N draw calls → 2
3DFACE entities previously created one WireGpu buffer per entity,
resulting in N separate draw calls per frame. For files with large
triangle meshes this was the dominant GPU CPU overhead.

New pipeline:
- face3d.wgsl: simple flat-color vertex + fragment shader (MVP only,
  no quad expansion, no pattern distance computation)
- Face3DGpu: batches ALL Face3D entities into a single TriangleList
  buffer (6 vertices per face, per-vertex color) — 1 draw call
- WireGpu::from_batch: merges N Face3D edge wire models into one
  quad-expanded buffer for edge rendering — 1 draw call
- split_face3d_wires: separates face3d from other wires at Primitive
  build time via document handle lookup (O(N), once per epoch)

Hit-testing via WireModel (CPU) is unaffected — face3d wires remain
in entity_wires_arc() and hit_test_wires() as before.

Before: N 3DFACE → N draw calls, N × 96 B/vertex WireVertex
After:  N 3DFACE → 2 draw calls (fill + edges), 28 B/vertex fill

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-28 21:53:31 +03:00
Hakan SevenandClaude Sonnet 4.6 a6592cc9bc fix: ViewCube hover, glow, centroids, corner geometry and snap angles
- Hide OS crosshair and drawn crosshair when cursor is over ViewCube area
- Enhanced hover glow in WGSL shader (blue tint + brightness boost)
- Fixed region_centroids() Y-axis sign for all FRONT/BACK-related regions
- Swapped corner geometry assignments (TPF↔TBK, BTF↔BBK) to match
  intended FRONT/BACK semantics (sy=-1 = FRONT, sy=+1 = BACK)
- Corrected all snap_angles: yaw_pitch_to_quat(0,0) places eye at -Y
  (FRONT view), so FRONT=0 and BACK=PI — cascaded through edges/corners

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-23 19:11:19 +03:00
Hakan SevenandClaude Sonnet 4.6 42048362e2 fix: MSAA resolve no longer overwrites widget backgrounds
MSAA and depth textures are now sized to the shader widget's clip
bounds instead of the full surface. The resolve writes to a matching
clip-sized intermediate texture; a blit pass (blit.wgsl, viewport
trick) then copies the result to the exact clip region of the surface
target, leaving all other widgets untouched.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-08 12:20:47 +03:00
Hakan SevenandClaude Sonnet 4.6 4312b6c3a5 feat: raster image render pipeline (IMAGE command + GPU textured quad)
- Add IMAGE / IMAGEATTACH / IM command with async file-picker (rfd)
- src/scene/image_model.rs: ImageModel with RGBA pixel data + quad corners
- src/scene/pipeline/image_gpu.rs: ImageGpu with wgpu texture, sampler, opacity uniform
- src/scene/pipeline/mod.rs: image render pipeline (pass 2, between hatches and meshes)
- src/scene/render.rs: include images in Primitive, call upload_images in prepare
- src/scene/mod.rs: images HashMap, populate_images_from_document, cache on add_entity
- src/modules/home/draw/raster_image.rs: ImageCommand (two-click: origin + width, aspect-correct preview)
- src/io/mod.rs: pick_image_file() async helper (file dialog + dimension decode)
- app: ImagePick / ImagePickResult messages, populate on file open and undo/redo

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
2026-04-06 23:19:08 +03:00
Hakan Seven d2fdac7f1e first commit 2026-03-22 14:56:27 +03:00