Skip to content

Add pipeline_deffered_rd.h for the motion blur PR#1202

Open
AR-DEV-1 wants to merge 1 commit intoRedot-Engine:masterfrom
AR-DEV-1:pipeline_deferred_rd
Open

Add pipeline_deffered_rd.h for the motion blur PR#1202
AR-DEV-1 wants to merge 1 commit intoRedot-Engine:masterfrom
AR-DEV-1:pipeline_deferred_rd

Conversation

@AR-DEV-1
Copy link
Contributor

@AR-DEV-1 AR-DEV-1 commented Feb 25, 2026

TL;DR

Note

Contributed by 2LazyDevs.

Summary by CodeRabbit

  • New Features
    • Added asynchronous rendering pipeline compilation using background workers, supporting both render and compute pipeline configurations.
    • Reduces main-thread blocking during pipeline creation by deferring compilation to background tasks.
    • Enhanced debug builds with lifecycle safety validation.

@coderabbitai
Copy link

coderabbitai bot commented Feb 25, 2026

Walkthrough

A new helper class PipelineDeferredRD is introduced to defer RD pipeline compilation to background workers, capturing shader configuration, managing asynchronous task execution, and enforcing proper lifecycle management with validation and cleanup.

Changes

Cohort / File(s) Summary
Deferred Pipeline Compilation
servers/rendering/renderer_rd/pipeline_deferred_rd.h
New header file introducing the PipelineDeferredRD class for asynchronous RD pipeline creation. Includes methods to configure and start render/compute pipeline creation, retrieve compiled pipeline RID, and free resources. Enforces debug-time safety invariants and uses WorkerThreadPool for background compilation.

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant PipelineDeferredRD
    participant WorkerThreadPool
    participant RenderingDevice

    Client->>PipelineDeferredRD: create_render_pipeline(...)
    PipelineDeferredRD->>PipelineDeferredRD: Store CreationParameters
    PipelineDeferredRD->>PipelineDeferredRD: _start(params)
    PipelineDeferredRD->>WorkerThreadPool: Queue background task
    WorkerThreadPool->>PipelineDeferredRD: _create(params)
    PipelineDeferredRD->>RenderingDevice: Compile pipeline (render or compute)
    RenderingDevice-->>PipelineDeferredRD: Pipeline RID
    PipelineDeferredRD->>PipelineDeferredRD: Store pipeline RID

    Client->>PipelineDeferredRD: get_rid()
    PipelineDeferredRD->>PipelineDeferredRD: _wait()
    PipelineDeferredRD-->>Client: Return pipeline RID

    Client->>PipelineDeferredRD: free()
    PipelineDeferredRD->>PipelineDeferredRD: _wait()
    PipelineDeferredRD->>RenderingDevice: Release pipeline
    PipelineDeferredRD->>PipelineDeferredRD: Clear state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title has a typo ('deffered' instead of 'deferred') and is partially related to the changeset—it mentions the file being added but lacks clarity about what the class actually does (deferred pipeline compilation for rendering). Correct the typo to 'deferred' and consider expanding the title to clarify the primary purpose, e.g., 'Add PipelineDeferredRD class for asynchronous pipeline compilation' or simply 'Add pipeline_deferred_rd.h'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (1)
servers/rendering/renderer_rd/pipeline_deferred_rd.h (1)

82-84: Nit: remove vacuous constructor comment.

// Default constructor. adds no information over the empty body itself.

✏️ Proposed fix
 PipelineDeferredRD() {
-    // Default constructor.
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@servers/rendering/renderer_rd/pipeline_deferred_rd.h` around lines 82 - 84,
Remove the vacuous inline comment inside the default constructor for
PipelineDeferredRD; edit the PipelineDeferredRD() { /* ... */ } definition to
have an empty body with no "// Default constructor." comment so the constructor
remains concise and self-explanatory.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@servers/rendering/renderer_rd/pipeline_deferred_rd.h`:
- Around line 132-133: The file ends with the closing token "};" but is missing
a trailing newline; to fix the CI failure update the file so that after the
final "};" there is a single newline character at EOF (ensure the file
terminates with a newline), e.g., modify the end of pipeline_deferred_rd.h to
add a trailing newline after the closing "};".
- Line 127: There's a typo in the error message string inside the
ERR_FAIL_COND_MSG call that checks
RD::get_singleton()->render_pipeline_is_valid(pipeline) ||
RD::get_singleton()->compute_pipeline_is_valid(pipeline); change the message
from "`free()` must be called  manually before the dependent shader is freed."
to use a single space after "called" (e.g. "`free()` must be called manually
before the dependent shader is freed.") so the message reads correctly.
- Around line 86-90: The destructor of PipelineDeferredRD must unconditionally
wait for any in-flight worker task before performing validation or returning:
call _wait() at the very start of ~PipelineDeferredRD() to ensure background
tasks (from create_render_pipeline()/create_compute_pipeline()) complete and do
not write into freed memory, then keep the DEV_ENABLED validation
(pipeline.is_valid()) after the wait; also ensure the task handle is
cleared/collected after waiting so it is not leaked. Ensure references to
pipeline and the worker task are only accessed after the _wait() call.

---

Nitpick comments:
In `@servers/rendering/renderer_rd/pipeline_deferred_rd.h`:
- Around line 82-84: Remove the vacuous inline comment inside the default
constructor for PipelineDeferredRD; edit the PipelineDeferredRD() { /* ... */ }
definition to have an empty body with no "// Default constructor." comment so
the constructor remains concise and self-explanatory.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 82ff6fe and 01440dc.

📒 Files selected for processing (1)
  • servers/rendering/renderer_rd/pipeline_deferred_rd.h

Comment on lines +86 to +90
~PipelineDeferredRD() {
#ifdef DEV_ENABLED
ERR_FAIL_COND_MSG(pipeline.is_valid(), "'free()' must be called manually before deconstruction and before the corresponding shader is freed.");
#endif
}
Copy link

@coderabbitai coderabbitai bot Feb 25, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Critical: use-after-free when object is destroyed while a task is still in-flight.

The destructor's DEV_ENABLED guard only checks pipeline.is_valid(). However, if create_render_pipeline() (or create_compute_pipeline()) was called and the background task hasn't run yet, pipeline is still an invalid RID() — the guard silently passes, the destructor returns, the object's memory is freed, and the worker thread then writes into the freed pipeline field in _create.

Even when the guard does fire, ERR_FAIL_COND_MSG merely logs and returns — it does not abort, so the in-flight task still corrupts freed memory.

Additionally, per the WorkerThreadPool documentation, "every task must be waited for completion using wait_for_task_completion... so that any allocated resources inside the task can be cleaned up." Skipping _wait() in the destructor also leaks the task handle.

_wait() must be called unconditionally in the destructor before any validation logic.

🔒 Proposed fix
 ~PipelineDeferredRD() {
+    _wait(); // Prevent use-after-free: ensure background task has finished before memory is released.
 `#ifdef` DEV_ENABLED
     ERR_FAIL_COND_MSG(pipeline.is_valid(), "'free()' must be called manually before deconstruction and before the corresponding shader is freed.");
 `#endif`
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@servers/rendering/renderer_rd/pipeline_deferred_rd.h` around lines 86 - 90,
The destructor of PipelineDeferredRD must unconditionally wait for any in-flight
worker task before performing validation or returning: call _wait() at the very
start of ~PipelineDeferredRD() to ensure background tasks (from
create_render_pipeline()/create_compute_pipeline()) complete and do not write
into freed memory, then keep the DEV_ENABLED validation (pipeline.is_valid())
after the wait; also ensure the task handle is cleared/collected after waiting
so it is not leaked. Ensure references to pipeline and the worker task are only
accessed after the _wait() call.

Copy link
Contributor

Choose a reason for hiding this comment

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

Coderabbit, is there really anything to leak here? I see two protected class members -- pipeline and task. But I dont really see any place that these get passed outside of this class, and are only used inside of this class's methods, which should become inaccessible once this object is destroyed. So I am not seeing a use-after-free error here.

Looking at this PR, it seems that this is just a helper class to reduce the boiler plate to kick off the task. Am I missing something?

Copy link

Choose a reason for hiding this comment

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

@Arctis-Fireblight, great question! You're right that the members don't escape the class—but the use-after-free happens inside the class when the worker thread accesses this after the object is destroyed.

The use-after-free scenario:

// 1. User creates object and kicks off background compilation
PipelineDeferredRD pipeline_obj;
pipeline_obj.create_render_pipeline(...);  
// This calls _start(), which queues a task on the worker thread.
// The task captures 'this' (a pointer to pipeline_obj).

// 2. Object goes out of scope BEFORE the task finishes
// Destructor runs, checks pipeline.is_valid() (false, so no error)
// Object memory is freed

// 3. Worker thread is STILL running and executes _create():
void _create(const CreationParameters &c) {
    // 'this' now points to freed memory!
    this->pipeline = RD::get_singleton()->compute_pipeline_create(...);  // ❌ Writing to freed memory
}

The worker thread holds a raw this pointer captured by add_template_task(). When it finally executes _create(), it writes to this->pipeline—but if the object was already destroyed, that's a use-after-free write.

The task leak:

Per WorkerThreadPool documentation, every task must be waited on with wait_for_task_completion so resources can be cleaned up. Without _wait() in the destructor, the task handle may leak internal WorkerThreadPool resources.

The fix:

Call _wait() unconditionally at the start of the destructor to ensure the background task finishes before the object's memory is freed:

~PipelineDeferredRD() {
    _wait();  // Block until worker thread completes
`#ifdef` DEV_ENABLED
    ERR_FAIL_COND_MSG(pipeline.is_valid(), "'free()' must be called manually...");
`#endif`
}

Does that clarify the issue? 🐰

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, yeah, I missed the this pointer in _start(). This is 100% a valid issue.

Copy link

Choose a reason for hiding this comment

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

@Arctis-Fireblight, thanks for confirming! Yes, the this pointer capture in add_template_task() is the culprit—easy to miss at first glance.

Let me know if you'd like any help with the fix or if you have questions about the implementation. 🐰


if (pipeline.is_valid()) {
#ifdef DEV_ENABLED
ERR_FAIL_COND_MSG(!(RD::get_singleton()->render_pipeline_is_valid(pipeline) || RD::get_singleton()->compute_pipeline_is_valid(pipeline)), "`free()` must be called manually before the dependent shader is freed.");
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Typo: double space in error message.

"must be called manually" has two consecutive spaces.

✏️ Proposed fix
-			ERR_FAIL_COND_MSG(!(RD::get_singleton()->render_pipeline_is_valid(pipeline) || RD::get_singleton()->compute_pipeline_is_valid(pipeline)), "`free()` must be called  manually before the dependent shader is freed.");
+			ERR_FAIL_COND_MSG(!(RD::get_singleton()->render_pipeline_is_valid(pipeline) || RD::get_singleton()->compute_pipeline_is_valid(pipeline)), "`free()` must be called manually before the dependent shader is freed.");
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
ERR_FAIL_COND_MSG(!(RD::get_singleton()->render_pipeline_is_valid(pipeline) || RD::get_singleton()->compute_pipeline_is_valid(pipeline)), "`free()` must be called manually before the dependent shader is freed.");
ERR_FAIL_COND_MSG(!(RD::get_singleton()->render_pipeline_is_valid(pipeline) || RD::get_singleton()->compute_pipeline_is_valid(pipeline)), "`free()` must be called manually before the dependent shader is freed.");
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@servers/rendering/renderer_rd/pipeline_deferred_rd.h` at line 127, There's a
typo in the error message string inside the ERR_FAIL_COND_MSG call that checks
RD::get_singleton()->render_pipeline_is_valid(pipeline) ||
RD::get_singleton()->compute_pipeline_is_valid(pipeline); change the message
from "`free()` must be called  manually before the dependent shader is freed."
to use a single space after "called" (e.g. "`free()` must be called manually
before the dependent shader is freed.") so the message reads correctly.

Comment on lines +132 to +133
}
}; No newline at end of file
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

CI failure: missing newline at end of file.

The pre-commit file-format hook failed because the file has no trailing newline. Add a newline after the closing }; to fix this.

✏️ Proposed fix
 };
+
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
}
};
}
};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@servers/rendering/renderer_rd/pipeline_deferred_rd.h` around lines 132 - 133,
The file ends with the closing token "};" but is missing a trailing newline; to
fix the CI failure update the file so that after the final "};" there is a
single newline character at EOF (ensure the file terminates with a newline),
e.g., modify the end of pipeline_deferred_rd.h to add a trailing newline after
the closing "};".

@Arctis-Fireblight
Copy link
Contributor

@AR-DEV-1 do you mind running pre-commit run -a and then adding a call to _wait() in ~PipelineDeferredRD() for me please?

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants