Commit graph

25 commits

Author SHA1 Message Date
catbot
65c19ea84a perf(ui): gate per-pixel clip compares behind a flags bit (#50)
uiResolveScreenPixel ran four float compares against hdr.clipRectPx for
every pixel of every UI dispatch, even though the clip rect is the
full-surface default {0,0,1e9,1e9} in the overwhelmingly common case.

Reserve the high bit of the header flags word (UI_FLAG_CLIP) and have
FillHeader set it only when the clip rect is narrower than the surface
(extracted into the unit-testable UIRenderer::ClipFlags). The shader
gates the compares on the bit; the branch is push-constant-uniform so it
never diverges. When the rect covers the surface the skipped compares
could never reject an in-surface pixel, so output is pixel-identical.

Mirrored on the WebGPU/WGSL path (dom-webgpu.js) for parity. Adds the
UIClipFlag CPU test covering the gate decision.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:57:07 +00:00
catbot
294c1378b5 fix(device): add preferred mask + required-only fallback to GetMemoryType (#59)
GetMemoryType was a bare first-superset match that threw whenever no
memory type satisfied the requested property flags. Callers combining a
mandatory flag with a perf-only one (HOST_VISIBLE | DEVICE_LOCAL for the
descriptor heaps) would then fail allocation on any device without a
host-visible device-local heap (no resizable BAR).

It now takes a `preferred` mask distinct from `required`: a type
satisfying both is chosen first, falling back to a required-only match
when the preference is unavailable, and throwing only when even
`required` cannot be met. VulkanBuffer::Create/Resize gain an optional
trailing `preferredPropertyFlags`, and the descriptor heaps now treat
DEVICE_LOCAL as a preference on top of mandatory HOST_VISIBLE.

This does not touch any flush/barrier behaviour — coherency is not
assumed anywhere here.

Adds tests/MemoryTypeFallback, a pure-CPU test that installs synthetic
memory layouts and exercises selection, preference, fallback, and the
unsatisfiable-required throw.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:03:50 +00:00
991a39e094 Merge pull request 'perf(font): upload only the dirty sub-rect of the glyph atlas (#51)' (#83) from claude/issue-51 into master 2026-06-16 17:43:23 +02:00
catbot
e4f8b057f3 Merge remote-tracking branch 'origin/master' into claude/issue-51
# Conflicts:
#	project.cpp
2026-06-16 15:43:11 +00:00
1451e3aeb2 Merge pull request 'feat(window): multi-frame-in-flight frame pacing (#40)' (#81) from claude/issue-40 into master 2026-06-16 17:42:13 +02:00
catbot
ca48f79465 perf(text): cache shaped runs in ShapeText (#52)
ShapeText re-decoded UTF-8, re-looked-up every glyph, and recomputed
layout every frame for static strings (DrawText / EmitCenteredLabel run
inside per-frame onBuild). DrawText and EmitCenteredLabel also did a
second full pass over the glyphs to apply alignment.

Memoize the shaped run, keyed by (Font*, pxSize, color, utf8), as an
origin-relative vector<GlyphItem>. A cache hit skips decode/lookup/layout
and just translates the cached run into the output buffer by
(x + alignShift, baselineY). The alignment shift (Center = -advance/2,
Right = -advance) is folded into ShapeText's single pass, dropping the
second loop in both callers. TextAlign moves to the :UI partition so
ShapeText can take it.

Pure CPU memoization — the glyph buffer is fully rewritten and re-flushed
every frame, so a hit is byte-equivalent. A miss still calls Ensure so new
glyphs rasterise and dirty the atlas; hits don't (atlas entries are never
evicted). InvalidateFont drops a font's runs before it is destroyed (the
cache, like FontAtlas's glyph cache, keys on the Font pointer). A soft cap
bounds memory against pathological per-frame-unique text.

Adds the ShapeTextCache test (15 checks): hit == miss byte-for-byte, hits
don't touch the atlas, translate/alignment/truncation/InvalidateFont.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:40:52 +00:00
catbot
97c79c23ee perf(font): upload only the dirty sub-rect of the glyph atlas (#51)
FontAtlas::Update re-uploaded the entire 1024x1024 R8 atlas (1 MiB)
whenever a single glyph was rasterized, so warm-up / language-switch /
size-churn cost roughly one full-atlas copy per frame that added a
codepoint.

Accumulate a dirty bounding box (FontAtlas::DirtyRect) in MarkDirty as
glyphs are placed, and copy only that sub-rect. On Vulkan this is a new
ImageVulkan::UpdateRegion: the staging buffer keeps the full atlas row
stride, so bufferRowLength stays the atlas width and bufferOffset points
at the rect origin while imageOffset/imageExtent carve out the rect.
Layout transitions stay whole-image (cheap); only the copy extent
shrinks. WebGPU passes the rect straight to wgpuWriteAtlasRegion, which
already took x/y/w/h.

The freshly-zeroed atlas marks its whole extent dirty in Initialize so
the first Update clears the image once; thereafter every untouched texel
stays the zero it was uploaded as, keeping the image byte-identical to
the staging copy.

Tested: new FontAtlasDirtyRect unit test covers the accumulation /
clamp / lockstep-with-`dirty` math (no GPU needed); HelloUI renders text
correctly on both Vulkan (NVIDIA, validation-clean) and WebGPU.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:40:37 +00:00
catbot
621016f264 feat(window): multi-frame-in-flight frame pacing (#40)
The renderer was effectively single-buffered despite allocating
triple-buffered infrastructure: Render() ended with an unconditional
vkQueueWaitIdle, and a single (presentComplete, renderComplete)
semaphore pair with zero fences was shared for the Window's lifetime.

Rework the pacing model so up to numFrames frames overlap:
- Per-swapchain-image VkFence, signaled by the submit and waited+reset
  before that image's command buffer / descriptor-heap slot is
  re-recorded. Keyed by acquired image index (drawCmdBuffers, the heap
  slots, and the swapchain images are all image-indexed — see
  WriteSwapchainDescriptors). Created signaled so first use passes.
- Per-image render-finished (present) semaphore: the presentation engine
  holds it until the image is re-acquired, so per-image is the only safe
  key (per-CPU-frame trips VUID-vkQueueSubmit-pSignalSemaphores-00067).
- Per-CPU-frame acquire semaphores (image index unknown until acquire
  returns), sized numFrames+1: in-flight depth is bounded by the
  per-image fences, so numFrames+1 distinct acquire semaphores guarantee
  the reused one has no pending op (VUID-vkAcquireNextImageKHR-01779).
- Drop the steady-state vkQueueWaitIdle; keep it on resize / OUT_OF_DATE
  / teardown.

Add tests/FrameLoopSync: drives the real frame loop against a live
Wayland compositor for 60 frames (>> in-flight slots) and asserts the
CPU frame counter advanced, the swapchain rotated across multiple
images, and the validation layer stayed silent — the load-bearing check,
since the old single-pair design only avoided being an active race
because the wait-idle masked it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 15:37:11 +00:00
catbot
ce0114607a test(vulkan-rt): headless BLAS build-options + refit test (#36)
Exercises the real hardware AS-build path: builds and refits triangle
and procedural BLASes with fast-build/fast-trace + allow-update flags
via one-time command buffers, asserting the requested flags land, that
an allowUpdate refit keeps the same AS handle/blasAddr (in-place
UPDATE), that the no-update / topology-change fallback still produces a
valid BLAS, and that the validation layer reports zero errors.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 13:37:46 +00:00
catbot
419730f46b feat(input): wire native mouse-wheel scroll on Wayland + Win32, normalize all backends to ±1/detent (#32)
Window::onMouseScroll now fires on every backend and speaks one unit:
signed whole detents packed in the uint32 payload, +1 = wheel down
(DOM deltaY sign).

- Wayland: implement the previously-stubbed PointerListenerHandleAxis —
  vertical axis only, wl_fixed_to_double / 15 (libinput's units-per-
  detent), sub-detent remainder accumulated and reset on pointer leave.
- Win32: add the missing WM_MOUSEWHEEL case — -GET_WHEEL_DELTA_WPARAM /
  WHEEL_DELTA with a remainder accumulator for free-spinning wheels.
- DOM: dom-env.js normalizes WheelEvent.deltaY per deltaMode (100px /
  3 lines / 1 page per detent) with the same remainder scheme, so wasm
  delivers the identical unit instead of raw browser-specific deltas.
- Document the contract on Window::onMouseScroll.
- tests/MouseScroll: compositor-free regression test driving the
  Wayland axis handler directly.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-12 15:02:28 +00:00
catbot
471f480c5d test(vulkan-rt): spirv-val coverage for the push-constant rewrite (#18)
Adds tests/PushConstantRewrite, a host test that compiles representative
ray-generation shaders with glslang, runs the real WorkaroundNvidiaAS::Patch
over them, and asserts with spirv-val (the same invocation vkCreateShaderModule
uses) that the result is valid and contains exactly one push-constant block —
covering both the merge path (shaders that already declare a push constant,
including mat4/vec3/uint, a lone uint, and an array layout) and the synthesize
path, plus a no-op case (push constant but no AS read). It also checks the
published TLAS push offset for each layout.

The workaround namespace is exported so the test can drive Patch directly; both
go away with the rest of the workaround. project.cpp wires the test as an
executable that recompiles the module and requires glslang + spirv-val.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 02:28:09 +00:00
162d98cf5b got rid of --local 2026-05-27 04:38:30 +02:00
8347467e1e webgpu improvements 2026-05-24 13:32:08 +02:00
b5d0f52da0 webgpu sponza 2026-05-19 00:27:09 +02:00
5553ded476 webgpu triangle 2026-05-18 18:43:30 +02:00
64116cd980 custom shader webgpu 2026-05-18 05:39:17 +02:00
dedf6b0467 webgpu support 2026-05-18 04:58:52 +02:00
5352ef69a2 browser DOM support 2026-05-18 02:07:48 +02:00
ac2eb7fb0a new input system 2026-05-12 00:24:48 +02:00
c054f1e0b3 update 2026-05-03 02:45:38 +02:00
1f5697326c UI rewrite 3rd attempt 2026-05-02 21:08:20 +02:00
216972e73a new UI system 2026-05-01 23:35:37 +02:00
d29f5609cd fix 2026-04-30 02:05:16 +02:00
7f5297ca57 add Rendertarget and Shm implementations to project.cpp
Both were on disk but missing from the V2 port's implementations list;
Rendertarget is required for RendertargetVulkan linkage, Shm is the
Wayland shared-memory helper.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-30 02:00:17 +02:00
8a2fd33efc crafter-build V2 2026-04-30 01:29:17 +02:00