Commit Graph
19 Commits
Author SHA1 Message Date
Hakan SevenandClaude Opus 4.8 66f7d24bc9 feat(statusbar): transparency-display toggle (TPY pill)
Add a transparency_enable uniform read by the wire shader: when the new
TPY pill is off, every line is forced opaque; when on, baked entity
transparency shows through. Toggling is a uniform write, no retessellate
(same mechanism as the lineweight-display toggle). Pill is toggleable
from the status-bar customization menu.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-04 23:25:09 +03:00
Hakan SevenandClaude Opus 4.8 09e7e90aae feat(draworder): cross-type draw order + context menu
DRAWORDER now affects rendering immediately (bump geometry epoch so the
sort cache rebuilds) and front/back anchor to the block's full effective
sort range instead of only existing table entries, so bring-to-front
actually lands on top. The render fallback sorts table-absent entities by
their own handle.

Draw order now applies across entity types, not just within a type. A
per-entity draw-order rank (SortEntitiesTable override or own handle,
ranked per block) is encoded as a small signed clip-z bias in the wire,
hatch, image, and 3DFACE pipelines. The shared LessEqual depth buffer then
orders fills, lines, rasters and 2D solids against each other regardless of
pass order — without touching model coordinates. 3D meshes keep real
geometric depth; wipeouts stay always-on-top.

Adds a Draw Order submenu to the viewport context menu (Bring to Front,
Send to Back, Bring Above/Send Under Object). The above/under items run an
interactive reference-object pick, then reorder the captured selection.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-05-30 21:29:01 +03:00
Hakan SevenandClaude Opus 4.7 14a981dc87 perf(wire): collapse sub-pixel dash patterns to solid line
When the smallest dash / gap element of a linetype pattern would project
below one pixel on screen, the gaps either shimmer as aliasing or vanish
entirely — the user reads the line as solid anyway. Compute the min
non-zero element of `pat0 / pat1` once in the vertex shader, flat-
interpolate it across the instance, and let the fragment stage skip the
arc-length dash test (and the `discard` for gap fragments) when
`min_elem >= u.world_per_pixel` fails. Mirrors the existing hatch-LOD
branch in `hatch.wgsl`; primary win is visual quality at extreme
zoom-out, with a small fragment-shader op saving as a side effect.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-30 02:02:43 +03:00
Hakan SevenandClaude Opus 4.7 6994837d0e fix(shader): satisfy DX12/FXC X3507 in hatch fs_main (#31)
Both hatch fragment shaders ended their `fs_main` with a bare
`discard;` after the pattern-match for-loop falls through without
matching. WGSL accepts this — discard kills the fragment, so there is
nothing left to return — and naga (the Linux/Vulkan path wgpu defaults
to) lowers it without complaint. DX12's FXC (the default Windows
compiler) is stricter and rejects it with `X3507: 'fs_main': Not all
control paths return a value`, taking the entire pipeline create-down
on every drawing open.

Append an unreachable `return vec4<f32>(0.0);` after each terminal
`discard;` so every control path syntactically ends in a return. The
return never executes at runtime; it only exists to make FXC's
all-paths-return analysis succeed. Naga still accepts the shader
unchanged, so the Vulkan / Metal / WebGPU paths are unaffected.

Reported by @Ward-Vandepitte (Windows 11, DX12, wgpu 27.0.1).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-29 21:16:21 +03:00
Hakan SevenandClaude Opus 4.7 3d24dd5168 fix(viewport): blit UV crop for off-canvas viewports
Refactor the per-viewport blit so a content viewport whose screen rect
extends off the canvas composites the correct slice of its MSAA target
to the visible portion of the surface, instead of being slammed against
the canvas corner by an `(f32 as u32)` saturation on the negative
offset.

- blit.wgsl + Pipeline gain a small uniform buffer (`uv_offset` /
  `uv_scale`). `Primitive::prepare` computes the per-viewport crop from
  `screen_rect`'s on-canvas fraction and writes it via the new
  `upload_blit_uv` helper while it still has the `Queue`.
- `Pipeline::render` now takes `vp_size` (full vp — MSAA / camera size)
  and `surface_dest` (visible intersection — blit destination) and
  reads the crop from the uniform; passes that don't have a non-empty
  surface intersection are skipped.
- `Primitive::render` clips the full vp rect against the surface clip
  in `i32` and skips empty intersections. The earlier "skip GPU for
  inactive off-canvas viewports" workaround in `active_viewports` is
  no longer needed.
- ViewCube draws only when its hosting vp is fully on-canvas (its own
  pipeline writes directly to the surface and would distort under a
  clamped `set_viewport`).

With the GPU path correct for every vp position, drop
`viewport_content_wires` from `paper_canvas_wires` — the inactive
viewport CPU projection was only there to mask the blit bug and was
duplicating the unified shader's output. The hit-test / snap path
(`entity_wires_arc`, `wires_for_block`) still calls
`viewport_content_wires` directly, so picking through inactive
viewports keeps working.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-28 12:15:00 +03:00
Hakan SevenandClaude Opus 4.7 132efe6574 feat(render): Flat/Gouraud shading, WithEdges overlay, paper-viewport mode
Faz 3 — FlatShaded vs GouraudShaded
- Uniforms.flat_shade (f32, 0/1) + mesh shader fragment derivative
  bazlı per-triangle face normal hesabı; GouraudShaded ise interpolate
  edilen vertex normalini kullanıyor.

Faz 4 — *WithEdges varyantları
- Pipeline::render.show_3d_edges parametresi:
  - FlatShaded / GouraudShaded → 3DFACE edge pass'i atlanıyor
  - *WithEdges varyantları → shaded fill'in üstüne mesh_wireframe
    pipeline ile triangle-edge overlay (LessEqual depth test ile)
  - face3d_edges pass'i show_3d_edges ile gate'lendi

Faz 5 — Paper-space viewport entity round-trip
- build_viewport_primitive / build_active_viewport_primitive artık
  render_mode parametresi almıyor; viewport entity'sinin kendi
  render_mode alanından okuyor. PaperViewportPane'dan render_mode
  field'i kaldırıldı. Model layout'taki tab.render_mode hala
  pick_list ile yönetiliyor.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-27 05:57:19 +03:00
HakanandClaude Opus 4.7 4b4f5c94cd perf(wire): switch wire pipeline to instanced rendering
Step 3 of the wire pipeline cold-open optimization plan — the biggest
single lever.

Each segment used to upload as 6 vertices × 96 B = 576 B with the segment
payload (pos_a/pos_b/color/half_width/distance/pattern_length/pat0/pat1)
duplicated across all six corners of the quad. Wire pipeline now uploads
one INSTANCE per segment (88 B) and the vertex shader derives `which_end`
and `side` from `@builtin(vertex_index)` against a 6-entry corner table.

Net effect on cold open: ~6.5× less wire vertex memory and PCIe upload,
plus a smaller Vec to allocate and copy on the CPU side.

Wire instance attributes also collapse distance_a / distance_b into a
single mix() inside the vertex shader, so per-segment distance still
interpolates correctly for the GPU-side linetype dash test.

Touches the wire, wire_xray, and face3d-edges draw passes.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-23 09:52:23 +03:00
HakanandClaude Opus 4.7 e9488f99dd perf(wire): move LWDISPLAY toggle from CPU rebake to wire shader uniform
LWDISPLAY (Show Lineweight) flipping the toggle no longer triggers a full
retessellate. `render_style_for` always bakes the entity's resolved (layer-
inherited) lineweight, and the wire shader reads `Uniforms.lwdisplay_enable`
to either honour the per-vertex half_width or collapse the quad to a 1 px line.

Side effect: PDF export now uses the real lineweight regardless of LWDISPLAY,
matching AutoCAD's plot semantics (LWDISPLAY only affects screen render).

Co-Authored-By: Claude Opus 4.7 <[email protected]>
2026-05-23 09:45:24 +03:00
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