Skip to content

Port webgpu_app to render graph - #251

Open
MatzeOGH wants to merge 10 commits into
AlpineMapsOrg:mainfrom
MatzeOGH:feature/rendergraph-app
Open

Port webgpu_app to render graph#251
MatzeOGH wants to merge 10 commits into
AlpineMapsOrg:mainfrom
MatzeOGH:feature/rendergraph-app

Conversation

@MatzeOGH

@MatzeOGH MatzeOGH commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Ports the webgpu app onto the render graph added in #250. The frame is now
built by declaring resources and passes each frame instead of the previous hand-ordered
rendering.

Changes

  • webgpu/engine/Window builds the graph each frame and drives execute().
  • The renderers and overlays declare their passes and resources through the graph:
    atmosphere, clouds, track, and the height-lines, screen-space-snow, texture, and
    tile-debug overlays, plus OverlayRenderer and the shared Overlay interface.
  • RenderGraphPanel is a debug UI panel for inspecting the compiled graph (passes,
    resources, ordering, culling), plumbed in through ImGuiManager and RenderingContext.
  • App.cpp and apps/webgpu_app/CMakeLists.txt register the panel.
  • Small additions to webgpu/base/gpu_utils used by the integration.

Files

  • App: App.cpp, ImGuiManager.{h,cpp}, RenderingContext.{h,cpp},
    ui/RenderGraphPanel.{h,cpp}, CMakeLists.txt
  • Engine: Window.{h,cpp}, the atmosphere/cloud/track renderers, the overlays and
    OverlayRenderer, webgpu/base/gpu_utils.{h,cpp}

@MatzeOGH MatzeOGH changed the title Feature/rendergraph app Port webgpu_app to render graph Jul 19, 2026
@MatzeOGH
MatzeOGH force-pushed the feature/rendergraph-app branch from 487d552 to e341435 Compare July 20, 2026 07:11
@adam-ce

adam-ce commented Jul 20, 2026

Copy link
Copy Markdown
Member

Failing pipeline are unrelated unit tests. Review will be for Gerald :)

@MatzeOGH
MatzeOGH marked this pull request as ready for review July 27, 2026 09:04
@MatzeOGH
MatzeOGH force-pushed the feature/rendergraph-app branch from a8cac59 to 36aab61 Compare July 27, 2026 11:04
Matthias Huerbe added 8 commits August 7, 2026 13:16
@MatzeOGH
MatzeOGH force-pushed the feature/rendergraph-app branch from ada13c6 to 6ca2849 Compare August 7, 2026 12:13
@MatzeOGH

MatzeOGH commented Aug 7, 2026

Copy link
Copy Markdown
Contributor Author

ready to merge upstream.
I can squash everything into one commit

@adam-ce

adam-ce commented Aug 10, 2026

Copy link
Copy Markdown
Member

Qt Code Review Report

Recommendation: Request changes
Scope: PR #251, f3fbb81416951c
Files reviewed: 34 C++ headers/sources
Confirmed issues: 11 (1 lint, 10 deep analysis)

All current CI checks pass. I did not submit a GitHub review or modify the checkout.


Lint findings

[L-001] Release builds accept a null allocator

File: webgpu/base/RenderGraph.cpp:909

Rule: LCY-4

Finding: start_recording() uses Q_ASSERT(allocator) as its only null guard, then immediately dereferences it. The guard disappears in release builds.

Mitigation: Make null handling part of the runtime contract or express non-nullness through the API.

The deterministic scan produced 78 raw diagnostics. The remaining hits were primarily formatting/style rules or unchanged-line matches and are not proposed as review comments.


Deep analysis findings

[D-001] Spontaneous profiling callback has data races

File: webgpu/base/RenderGraph.cpp:2921

Category: Thread Safety

Confidence: 97/100

Finding: WGPUCallbackMode_AllowSpontaneous may execute on an arbitrary thread, but the callback reads and writes ordinary GpuProfiler and Slot fields also accessed by the render/UI thread.

Trace: The callback changes results and slot->pending; execute(), free_slot(), sample_history(), and RenderGraphPanel access the same fields without synchronization.

Mitigation: Use AllowProcessEvents with the existing main-thread event pump, or synchronize publication and slot reuse.

[D-002] Pending callback can outlive its allocator

File: webgpu/base/RenderGraph.cpp:2922

Category: Ownership & Lifecycle

Confidence: 91/100

Finding: Callback userdata points inside GraphAllocator, but destroy_allocator() neither cancels nor drains pending mappings before deleting it.

Trace: The callback dereferences both GpuProfiler* and Slot* after asynchronous completion.

Mitigation: Drain/cancel pending callbacks before teardown or put callback state in independently owned, reference-counted storage.

[D-003] GPU profiler resources are leaked

File: webgpu/base/RenderGraph.cpp:508

Category: Ownership & Lifecycle

Confidence: 98/100

Finding: Profiling creates a query set, resolve buffer, and three readback buffers, but none are released.

Trace: destroy_allocator() tears down the resource pools and arenas only; no corresponding query-set or buffer releases exist.

Mitigation: Add explicit GpuProfiler teardown after pending operations are settled.

[D-004] Integer overflow bypasses buffer bounds validation

File: webgpu/base/RenderGraph.cpp:2180

Category: Error Handling

Confidence: 95/100

Finding: bufOffset + bufSize > bufferSize can wrap, allowing an invalid range through compilation.

Trace: Public buffer ranges accept uint64_t; execution later forwards the unchecked range to WebGPU.

Mitigation: Check offset > bufferSize || size > bufferSize - offset. Add overflow-boundary tests.

[D-005] Texture subresource indices are silently truncated

File: webgpu/base/RenderGraph.cpp:3067

Category: Error Handling

Confidence: 96/100

Finding: Public mip/layer fields are uint32_t, but accesses store them as uint8_t/uint16_t before validation.

Trace: Mip 256 or layer 65536 becomes zero, so validation can accept and execute a different subresource.

Mitigation: Preserve uint32_t values or reject unrepresentable inputs before conversion.

[D-006] RenderGraph GPU timing UI can never collect data

File: apps/webgpu_app/App.cpp:245

Category: Performance & Quality

Confidence: 100/100

Finding: The only RenderGraph::execute() call hardcodes profiling to false.

Trace: No queries are resolved, pendingSlot remains unset, and collect_gpu_timings() immediately returns. The panel’s timing history therefore stays empty.

Mitigation: Feed an actual profiling request from the panel, gated on timestamp-query support.

[D-007] RenderGraph panel becomes inactive on idle frames

File: apps/webgpu_app/ui/RenderGraphPanel.cpp:2739

Category: Performance & Quality

Confidence: 98/100

Finding: The panel clears g_render_graph every GUI frame, while App only publishes a graph when the scene redraws.

Trace: On a static scene, the next UI frame has no replacement and displays “Render graph inactive.”

Mitigation: Retain a durable diagnostic snapshot, or keep the last graph until begin_frame() invalidates it.

[D-008] Surface acquisition failure leaves CPU timing unbalanced

File: apps/webgpu_app/App.cpp:182

Category: Error Handling

Confidence: 88/100

Finding: The PR moved m_cputimer->start() before surface acquisition, but the failure branch returns without calling stop().

Mitigation: Start after successful acquisition or use scoped cleanup.

[D-009] std::format lacks its required include

File: webgpu/engine/overlay/OverlayRenderer.cpp:106

Category: API & C++ Correctness

Confidence: 99/100

Finding: The new code uses std::format without including , relying on build-order or transitive-header behavior.

Mitigation: Include directly.

[D-010] Resolve accesses display as unknown

File: apps/webgpu_app/ui/RenderGraphPanel.cpp:57

Category: API & C++ Correctness

Confidence: 96/100

Finding: rg_access_name() omits AccessType::ResolveAttachment, so resolve accesses appear as "?".

Mitigation: Add the missing case and preferably keep the enum switch exhaustive.


Investigation targets (human verification needed)

[I-001] Allocator ownership depends on manual teardown

File: apps/webgpu_app/RenderingContext.h:81

Category: Ownership & Lifecycle

Confidence: 74/100

Finding: RenderingContext owns a raw GraphAllocator* without destructor or repeated-initialization protection.

How to verify: Confirm that every native/Wasm lifecycle calls destroy() exactly once and initialize() cannot repeat. Prefer RAII ownership.

[I-002] Profiler resource creation is unchecked

File: webgpu/base/RenderGraph.cpp:508

Category: Error Handling

Confidence: 72/100

Finding: Initialization is marked complete without checking query-set or buffer creation results.

How to verify: Confirm failure-object semantics for every supported WebGPU implementation and disable profiling cleanly when creation fails.

[I-003] Headers depend on transitive declarations

File: webgpu/engine/Window.h:57

Category: API & C++ Correctness

Confidence: 72/100

Finding: TextureHandle currently reaches Window.h through unrelated renderer headers.

How to verify: Run standalone-header compilation; add a direct include or forward declaration if independence is required.


Summary

Category Lint Deep Investigate Total

Ownership & Lifecycle 1 2 1 4
Thread Safety 0 1 0 1
Error Handling 0 3 1 4
API & C++ Correctness 0 2 1 3
Performance & Quality 0 2 0 2
Total 1 10 3 14

The checkout remains clean: ## main...origin/main.

@GeraldKimmersdorfer GeraldKimmersdorfer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I already mentioned this is an amazing tool. It's very low-level such that really rolling it out to all the features of webigeo including the forks that are currently in development will take a while, but I'm sure we'll get there.

I didnt really do a thorough review on code level, but more on a feature level. My immediate suggestions before we go on with the merge:

  1. When the RenderGraph Window opens please make sure that it already fills a good portion of the screen. When I opened it I couldnt find it at first.
  2. Is it possible that when we don't "repaint each frame" that the Panel shows the last render graph configuration instead of "Render Graph inactivate" (i know this destroys immediate mode paradigm - please only implement when easily possible) OR another suggestions which would make that actually a non-issue: Is it possible to have the GuiPipeline also be run via the RenderGraph? Such that most of the time we would only see the easy BlitGraph with the backbuffertexture and when we do a full repaint we see the whole graph?
  3. As pointed out in a comment, would it be possible to either remove all of the profiling code or at least activate it such that we can use the functionality? As we discussed this code will at some point be removed by a more general profiler thats why I lean more towards the deletion of all this code.
  4. Deactivating VSync seems to be broken now. At least it always seems to cap at 60FPS for me. Please have a look at that.
  5. Please add your copyright to all the files you touch. I know its cumbersome but it also is helpful in giving a first glimpse on who worked on the various files.
  6. I also wrote it as a comment: It's obviously not your job but maybe you could remove the inline shader code that I added a long time ago for the GuiPipeline in the app
  7. As commented: I think the BindGroups for CloudRenderer are now constructed in the hot loop? Please have a look at that

More long term refactors that I think are good ideas, but out of scope for the pr (will add in my backlog or as github issues eventually):

  1. Make the Panel code more lean by reusing the ImNodes library. (Which requires to add certain functionality to the ImNodes code though)
  2. Write a general Profiler which the RenderGraph can hook into (already partly done on my branch)

@@ -0,0 +1,2821 @@
/*****************************************************************************
* Mostly written by Opus 4.8

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say its still your copyright, but that's up to you. Dont know how it works with Coding Agents but they are no legal entity. :/

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd say, leave opus out of it. and it's definitely your copyright (as well). I don't think it's useful to put opus 4.8 there, and fable, and opus 5, and 5.6 sol, and 5.6 terra and clang format 21 etc. there. you are responsible for the code. if you don't want to own it, don't push it!

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oh this seems useful thx

GuiPipelineUBO initial { glm::vec2(1280.0f, 1024.0f) };
m_blit_ubo->write(m_queue, &initial);

const char preprocessed_code[] = R"(

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only did this inline because at the first version of webigeo the App didnt have access to the shader manager. Now with the shared Manager I'd prefer to have it as standalone shader file. Would it be okay for you to do that? (Otherwise you can leave it for me)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only brainstorming here, but I think we should modify imnodes in a way such that it allows zoom and then use this library here instead? No need to reinvent the wheel I guess. At the same time ImNodes doesnt have a lot of functionalities that you coded here (like building groups). Anyway thats only a suggestion for further down the line. Of no importance right now.

namespace webgpu_app {

//TODO: move
webgpu::rg::RenderGraph* g_render_graph = nullptr;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean this place is as good as any, but maybe it would make sense to be inside the Context-Object?

}

// per-pass GPU us over time, one line per pass. sampled in draw(), so it keeps running on any tab.
static void rg_draw_timings(RenderGraphStorage& s)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The profiler that you have implemented is quite cool, but as we already discussed will be obsolete once we will write a more general Profiler-Implementation somewhere down the line. For the time being I'd propose two paths:

  1. Delete all of your profiling related code
  2. Activate the profiler such that we can use it within webigeo and we'll remove it once it becomes obsolete.

},
[this, lo_color, lo_depth, gbuffer_depth, shared_config_bind_group](webgpu::rg::PassContext& c) {

webgpu::raii::BindGroup bind_group(c.device, *m_render_clouds_layout,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We used to only construct the bindgroups on resize. With this - if im not mistaken - we do construct two of them each frame? Is that negligible? Since they are static anyway could we still have them only reconstructed on size_change?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants