feat(graphic): add gbufferpass#427
Conversation
📝 WalkthroughWalkthroughAdds a G-buffer render pass and shader, creates a 3D render graph with normal/albedo/depth targets and resize-aware textures, switches normal-matrix layout to mat4, updates texture/format handling and depth formats, integrates Create3DGraph into the default pipeline, and adds a headless G-buffer test and build/test wiring. Changes
Sequence Diagram(s)sequenceDiagram
participant App as Application
participant Sys as Create3DGraph
participant Core as Engine::Core
participant Tex as TextureContainer
participant Graph as RenderGraph
participant Pass as GBufferPass
participant GPU as GPU
App->>Sys: Create3DGraph(core)
Sys->>Core: access containers & context
Sys->>Tex: create Normal (RGBA16Float), Albedo (BGRA8Unorm), Depth (Depth32Float)
Sys->>Core: register OnResize -> recreate textures
Sys->>Graph: register GBuffer shader & pass
Graph->>Core: store RenderGraph
Note over App,GPU: At render time
App->>Pass: Execute UniqueRenderCallback
Pass->>Core: select active camera
loop per entity (Transform + Mesh)
Pass->>Pass: bind camera, model, material bind-groups
Pass->>GPU: bind vertex/index buffers
Pass->>GPU: draw indexed
end
GPU->>Tex: write Normal, Albedo, Depth outputs
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
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. Comment |
…reRetrieveCallback
…GBA16Float and Depth32Float formats
…ing for GBuffer outputs
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this comment.
Actionable comments posted: 6
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/plugin/default-pipeline/src/resource/buffer/TransformGPUBuffer.hpp (1)
17-29: Update outdated documentation and comment.The struct documentation (lines 20-23) and the inline comment (line 27) still reference the old
mat3x3<f32>layout with 112 bytes total. The struct now usesmat4fornormalMatrix, making the total size 128 bytes (64 + 64).📝 Proposed documentation fix
/** * `@brief` GPU buffer structure for model transform data * * Contains the model matrix and normal matrix for proper vertex/normal transformations. * The normal matrix (inverse transpose of the upper-left 3x3 of modelMatrix) is required * for correct normal transformation when the model has non-uniform scaling. * * Layout (WGSL std140 alignment): * - modelMatrix: mat4x4<f32> (64 bytes, offset 0) - * - normalMatrix: mat3x3<f32> (48 bytes, offset 64) - each column is 16-byte aligned - * Total: 112 bytes + * - normalMatrix: mat4x4<f32> (64 bytes, offset 64) + * Total: 128 bytes */ struct TransformGPUData { glm::mat4 modelMatrix; - // mat3x3 in WGSL has each column aligned to 16 bytes, so we use vec4 for each column + // Normal matrix stored as mat4 for shader compatibility glm::mat4 normalMatrix; };
🤖 Fix all issues with AI agents
In `@src/plugin/default-pipeline/src/plugin/PluginDefaultPipeline.cpp`:
- Around line 32-34: The two setup systems CreateDefaultRenderPipeline and
Create3DGraph both call renderPassContainer.SetDefault(), and because
RegisterSystems<RenderingPipeline::Setup> registers Create3DGraph after
CreateDefaultRenderPipeline its SetDefault() overwrites the earlier default; fix
by either removing the redundant renderPassContainer.SetDefault() call from
CreateDefaultRenderPipeline (if the GBuffer 3D graph should be the primary
default) or by removing CreateDefaultRenderPipeline from the registration (if
it’s no longer needed), or alternatively change registration order and document
the intended fallback behavior so the default is set only once and intentionally
(update CreateDefaultRenderPipeline, Create3DGraph, and the
RegisterSystems<RenderingPipeline::Setup> registration accordingly).
In `@src/plugin/default-pipeline/src/resource/pass/GBuffer.hpp`:
- Around line 73-76: The normal is being transformed with a w=1.0 which applies
translation; in the GBuffer shader change the normal transformation in the line
that assigns output.fragNormal (the normalize((object.normal * vec4(normal,
1.0)).xyz) expression) to use vec4(normal, 0.0) instead so the normal is treated
as a direction (no translation) before normalization.
In `@src/plugin/default-pipeline/src/system/initialization/Create3DGraph.cpp`:
- Around line 73-84: The OnResize callback for the depth texture is
removing/adding the NORMAL ID by mistake; locate the lambda registered on
eventManager for Window::Event::OnResize in Create3DGraph.cpp that uses
CreateGBufferPassOutputDepthTextureDescriptor and textureContainer, and change
the calls that reference
DefaultPipeline::Resource::GBUFFER_PASS_OUTPUT_NORMAL_ID to use
DefaultPipeline::Resource::GBUFFER_PASS_OUTPUT_DEPTH_ID instead (both the Remove
and Add calls) so the depth texture is resized and stored under the correct
DEPTH ID.
- Around line 61-72: The resize callback in Create3DGraph.cpp mistakenly removes
and re-adds DefaultPipeline::Resource::GBUFFER_PASS_OUTPUT_NORMAL_ID instead of
the albedo ID, causing the albedo texture to not be recreated and the normal
texture to be overwritten; update the lambda registered via
eventManager.RegisterCallback<Window::Event::OnResize> to call
textureContainer.Remove(DefaultPipeline::Resource::GBUFFER_PASS_OUTPUT_ALBEDO_ID)
and
textureContainer.Add(DefaultPipeline::Resource::GBUFFER_PASS_OUTPUT_ALBEDO_ID,
std::move(resizedTexture)) so the albedo texture (created by
CreateGBufferPassOutputAlbedoTextureDescriptor and managed in textureContainer)
is replaced on resize rather than the normal texture.
- Around line 31-40: The depth texture descriptor in
CreateGBufferPassOutputDepthTextureDescriptor omits
wgpu::TextureUsage::TextureBinding; update the descriptor. In the function
CreateGBufferPassOutputDepthTextureDescriptor (resource label
DefaultPipeline::Resource::GBUFFER_PASS_OUTPUT_DEPTH) add TextureBinding to
descriptor.usage so it matches the normal and albedo outputs (RenderAttachment |
CopySrc | CopyDst | TextureBinding), enabling sampling of the depth buffer for
deferred shading or other uses.
In
`@src/plugin/default-pipeline/src/system/initialization/CreateDefaultRenderPipeline.cpp`:
- Line 3: Remove the unused include "resource/ASingleExecutionRenderPass.hpp"
from CreateDefaultRenderPipeline.cpp; the function only uses
Utils::DefaultRenderPass and Graphic::Resource types (no references to
ASingleExecutionRenderPass), so delete that include line to clean up unused
headers.
🧹 Nitpick comments (8)
src/plugin/graphic/src/resource/Texture.hpp (3)
47-64: Consider combining identical switch cases.The
RGBA8UnormSrgbandRGBA8Unormcases have identical handling. Use fallthrough to reduce duplication.♻️ Proposed fix
switch (data->format) { case wgpu::TextureFormat::RGBA8UnormSrgb: - pixel.r = mapped[i * 4 + 0]; - pixel.g = mapped[i * 4 + 1]; - pixel.b = mapped[i * 4 + 2]; - pixel.a = mapped[i * 4 + 3]; - break; - case wgpu::TextureFormat::RGBA8Unorm: + case wgpu::TextureFormat::RGBA8Unorm: // fallthrough pixel.r = mapped[i * 4 + 0]; pixel.g = mapped[i * 4 + 1]; pixel.b = mapped[i * 4 + 2]; pixel.a = mapped[i * 4 + 3]; break;
65-82: Endianness assumption in bit_cast.The
std::bit_castfromstd::array<uint8_t, N>to numeric types assumes little-endian byte order. This is typically fine for WebGPU targets but worth noting in a comment for maintainability.
208-216: Consider adding Depth16Unorm to depth format check.The depth format detection is missing
wgpu::TextureFormat::Depth16Unorm. If this format is used, the copy would fail or produce incorrect results.♻️ Proposed fix
if (_webgpuTexture.getFormat() == wgpu::TextureFormat::Depth24Plus || _webgpuTexture.getFormat() == wgpu::TextureFormat::Depth24PlusStencil8 || _webgpuTexture.getFormat() == wgpu::TextureFormat::Depth32Float || - _webgpuTexture.getFormat() == wgpu::TextureFormat::Depth32FloatStencil8) + _webgpuTexture.getFormat() == wgpu::TextureFormat::Depth32FloatStencil8 || + _webgpuTexture.getFormat() == wgpu::TextureFormat::Depth16Unorm)src/plugin/default-pipeline/src/utils/DefaultRenderPass.hpp (1)
224-230: Stale comment: Update to reflect actual buffer layout.The comment says "mat4 modelMatrix (64 bytes) + 3 * vec4 normalMatrix columns (48 bytes) = 112 bytes" but the actual layout is now two
mat4matrices (128 bytes total), matching thesizeof(glm::mat4) + sizeof(glm::mat4)on line 228.📝 Suggested fix
- // Model buffer contains: mat4 modelMatrix (64 bytes) + 3 * vec4 normalMatrix columns (48 bytes) = 112 bytes + // Model buffer contains: mat4 modelMatrix (64 bytes) + mat4 normalMatrix (64 bytes) = 128 bytes auto modelLayout = Graphic::Utils::BindGroupLayout("ModelLayout")src/plugin/default-pipeline/tests/shader/GBufferTest.cpp (1)
29-47: Consider adding basic assertions to validate extracted textures.The extracted textures (
normalImage,albedoImage,depthImage) are retrieved but never validated. Even for a smoke test, consider adding minimal assertions to ensure the textures have expected dimensions or are non-empty.💡 Example assertions
void ExtractTextures(Engine::Core &core) { auto &context = core.GetResource<Graphic::Resource::Context>(); auto &textures = core.GetResource<Graphic::Resource::TextureContainer>(); auto &normalTexture = textures.Get(DefaultPipeline::Resource::GBUFFER_PASS_OUTPUT_NORMAL_ID); auto normalImage = normalTexture.RetrieveImage(context); + EXPECT_GT(normalImage.width, 0); + EXPECT_GT(normalImage.height, 0); auto &albedoTexture = textures.Get(DefaultPipeline::Resource::GBUFFER_PASS_OUTPUT_ALBEDO_ID); auto albedoImage = albedoTexture.RetrieveImage(context); + EXPECT_GT(albedoImage.width, 0); auto &depthTexture = textures.Get(DefaultPipeline::Resource::GBUFFER_PASS_OUTPUT_DEPTH_ID); auto depthImage = depthTexture.RetrieveImage(context); + EXPECT_GT(depthImage.width, 0); // Uncomment to save the images // ... }src/plugin/default-pipeline/src/resource/pass/GBuffer.hpp (1)
158-164: Stale comment: same issue as in DefaultRenderPass.hpp.The comment references the old layout "3 * vec4 normalMatrix columns (48 bytes) = 112 bytes" but the actual buffer is now two
mat4matrices (128 bytes).📝 Suggested fix
- // Model buffer contains: mat4 modelMatrix (64 bytes) + 3 * vec4 normalMatrix columns (48 bytes) = 112 bytes + // Model buffer contains: mat4 modelMatrix (64 bytes) + mat4 normalMatrix (64 bytes) = 128 bytes auto modelLayout = Graphic::Utils::BindGroupLayout("Model").addEntry(src/plugin/default-pipeline/src/system/initialization/Create3DGraph.cpp (2)
42-47: Stale TODO comment.The TODO says "resize on window resize" but resize handling is already implemented via the
OnResizecallbacks below.📝 Suggested fix
static void CreateGBufferTextures(Engine::Core &core) { - // TODO: resize on window resize const auto &context = core.GetResource<Graphic::Resource::Context>();
50-60: Consider extracting hardcoded texture size to a constant.The initial texture size
{800, 800}is hardcoded in three places. Consider extracting to a named constant for maintainability and to make it clear this is an initial/default size before the first resize event.📝 Example
static constexpr glm::uvec2 DEFAULT_GBUFFER_SIZE{800, 800}; // Then use: auto descriptor = CreateGBufferPassOutputNormalTextureDescriptor(DEFAULT_GBUFFER_SIZE);Also applies to: 62-64, 74-76
…pass integration in rendering pipeline
…texture usage in GBuffer pass
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @Miou-zora. * #427 (comment) The following files were modified: * `src/plugin/default-pipeline/src/plugin/PluginDefaultPipeline.cpp` * `src/plugin/default-pipeline/src/resource/buffer/TransformGPUBuffer.hpp` * `src/plugin/default-pipeline/src/resource/pass/GBuffer.hpp` * `src/plugin/default-pipeline/src/system/initialization/Create3DGraph.cpp` * `src/plugin/default-pipeline/src/utils/DefaultRenderPass.hpp` * `src/plugin/graphic/src/resource/ShaderDescriptor.hpp` * `src/plugin/graphic/src/resource/Texture.hpp` * `src/plugin/graphic/src/system/preparation/PrepareEndRenderTexture.cpp` * `src/plugin/graphic/src/utils/GetBytesPerPixel.cpp`
Docstrings generation was requested by @Miou-zora. * #427 (comment) The following files were modified: * `src/plugin/default-pipeline/src/plugin/PluginDefaultPipeline.cpp` * `src/plugin/default-pipeline/src/resource/buffer/TransformGPUBuffer.hpp` * `src/plugin/default-pipeline/src/resource/pass/GBuffer.hpp` * `src/plugin/default-pipeline/src/system/initialization/Create3DGraph.cpp` * `src/plugin/default-pipeline/src/utils/DefaultRenderPass.hpp` * `src/plugin/graphic/src/resource/ShaderDescriptor.hpp` * `src/plugin/graphic/src/resource/Texture.hpp` * `src/plugin/graphic/src/system/preparation/PrepareEndRenderTexture.cpp` * `src/plugin/graphic/src/utils/GetBytesPerPixel.cpp` <details> <summary>These files were ignored</summary> * `src/plugin/default-pipeline/tests/shader/GBufferTest.cpp` </details> <details> <summary>These file types are not supported</summary> * `.github/workflows/ci.yml` </details> <details> <summary>ℹ️ Note</summary><blockquote> CodeRabbit cannot perform edits on its own pull requests yet. </blockquote></details> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/plugin/default-pipeline/src/utils/DefaultRenderPass.hpp (1)
218-271: Update the model-buffer size comment (now 128 bytes).
The comment still describes the old 3×vec4 layout, which no longer matches the mat4+mat4 buffer size.💡 Suggested update
- // Model buffer contains: mat4 modelMatrix (64 bytes) + 3 * vec4 normalMatrix columns (48 bytes) = 112 bytes + // Model buffer contains: mat4 modelMatrix (64 bytes) + mat4 normalMatrix (64 bytes) = 128 bytes
🤖 Fix all issues with AI agents
In `@src/plugin/default-pipeline/src/resource/pass/GBuffer.hpp`:
- Around line 159-165: The inline comment above modelLayout is outdated
(mentions 3×vec4 = 112 bytes) but the code uses sizeof(glm::mat4) +
sizeof(glm::mat4) = 128 bytes; update the comment to state the model buffer is
now 128 bytes (two mat4: modelMatrix + normalMatrix or equivalent) and remove
the old 3×vec4 wording so it matches the BindGroup created by
Graphic::Utils::BindGroupLayout("Model") and the
BufferBindGroupLayoutEntry("model").setMinBindingSize(sizeof(glm::mat4) +
sizeof(glm::mat4)) call.
In `@src/plugin/default-pipeline/src/system/initialization/Create3DGraph.cpp`:
- Around line 44-87: CreateGBufferTextures currently creates default GBuffer
textures at 800×800; instead query the current window size at initialization and
use it for the descriptor sizes. In CreateGBufferTextures, get the Window
resource (e.g. auto &window = core.GetResource<Window::Resource::Window>()) and
call window.GetSize(), then pass that size into
CreateGBufferPassOutputNormalTextureDescriptor /
CreateGBufferPassOutputAlbedoTextureDescriptor /
CreateGBufferPassOutputDepthTextureDescriptor for the initial texture creation;
keep the existing Window::Event::OnResize callbacks as-is.
🧹 Nitpick comments (1)
src/plugin/default-pipeline/tests/shader/GBufferTest.cpp (1)
61-66: Make the smoke test fail if the GBuffer pass is missing.
Right now the test succeeds even when the pass isn’t registered, so regressions won’t be caught.💡 Suggested tweak
core.RegisterSystem<RenderingPipeline::CommandCreation>([](Engine::Core &core) { auto &renderPassContainer = core.GetResource<Graphic::Resource::RenderGraphContainer>(); - if (renderPassContainer.Contains(DefaultPipeline::Resource::GBUFFER_PASS_ID)) - { - renderPassContainer.Get(DefaultPipeline::Resource::GBUFFER_PASS_ID).Execute(core); - } + ASSERT_TRUE(renderPassContainer.Contains(DefaultPipeline::Resource::GBUFFER_PASS_ID)) + << "GBuffer pass not registered"; + renderPassContainer.Get(DefaultPipeline::Resource::GBUFFER_PASS_ID).Execute(core); });
… if window resource is unavailable
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Fix all issues with AI agents
In `@src/plugin/default-pipeline/src/utils/DefaultRenderPass.hpp`:
- Around line 236-240: The inline comment above the modelLayout binding is stale
(mentions 112 bytes) while the code uses sizeof(glm::mat4) + sizeof(glm::mat4) =
128 bytes; update or remove that comment in DefaultRenderPass.hpp so it matches
the current TransformGPUData layout. Locate the modelLayout creation
(Graphic::Utils::BindGroupLayout("ModelLayout") and
BufferBindGroupLayoutEntry("model")) and change the comment to state 128 bytes
(or describe the fields generically) to reflect the actual size computed by
sizeof(glm::mat4) + sizeof(glm::mat4).
In `@src/plugin/graphic/src/resource/Texture.hpp`:
- Around line 65-99: RetrieveImage selects Depth24Plus, Depth24PlusStencil8, and
Depth32FloatStencil8 as depth-only, but TextureRetrieveCallback (the switch
handling in Texture.hpp) only supports wgpu::TextureFormat::Depth32Float and
throws for other depth formats; update them to be consistent. Add switch cases
for wgpu::TextureFormat::Depth24Plus, wgpu::TextureFormat::Depth24PlusStencil8,
and wgpu::TextureFormat::Depth32FloatStencil8 in the TextureRetrieveCallback
readback switch (alongside Depth32Float) and convert their packed depth bytes
into a normalized float/byte like the Depth32Float path (or extract the 24-bit
depth component and scale to 0..255), and/or adjust RetrieveImage and its doc
comment to restrict accepted depth formats to only Depth32Float if you prefer
narrowing; ensure the changes reference TextureRetrieveCallback and
RetrieveImage so both selection/aspect logic and the switch cases remain in
sync.
♻️ Duplicate comments (5)
src/plugin/default-pipeline/src/plugin/PluginDefaultPipeline.cpp (1)
45-47: Registration order looks correct.Based on the current implementation in Create3DGraph.cpp (line 183),
Create3DGraphcallsrenderPassContainer.Add(GBUFFER_PASS_ID, ...)rather thanSetDefault(), so it adds a named render graph without overwriting the default set byCreateDefaultRenderPipeline. This is the expected behavior for having both a default render pipeline and a GBuffer-based 3D graph available.src/plugin/default-pipeline/src/system/initialization/Create3DGraph.cpp (2)
54-64: LGTM! Depth texture now includes TextureBinding usage.The depth texture descriptor correctly includes
TextureBinding | RenderAttachment | CopySrc | CopyDst, enabling sampling for deferred shading passes.
75-119: LGTM! GBuffer texture creation and resize handling looks correct.The implementation now:
- Uses
window.GetSize()for initial texture dimensions instead of hardcoded values- Correctly uses the matching texture IDs in each resize callback (NORMAL, ALBEDO, DEPTH respectively)
src/plugin/default-pipeline/src/resource/pass/GBuffer.hpp (2)
33-93: LGTM! GBuffer shader is well-structured.The shader correctly:
- Uses
mat4x4<f32>for the normal matrix- Transforms normals with
vec4(normal, 0.0)to exclude translation- Outputs normalized world-space normals and albedo to the G-buffer
177-257: LGTM! CreateShader is correctly configured.The shader descriptor properly sets up:
- Camera, model (128 bytes), and material bind group layouts
- Vertex layout with position/normal/uv attributes
- RGBA16Float for normal output, BGRA8Unorm for albedo
- Depth32Float with depth write enabled
🧹 Nitpick comments (1)
src/plugin/graphic/src/system/preparation/PrepareEndRenderTexture.cpp (1)
108-120: Consider using designated initializers for consistency.The existing
EnsurePlaceholderEndRenderTexturefunction (line 16) uses designated initializers forwgpu::Extent3D:textureDesc.size = {.width = 1920u, .height = 1080u, .depthOrArrayLayers = 1};For consistency and improved readability, consider using the same style here.
♻️ Suggested change
- textureDesc.size = {requiredSize.x, requiredSize.y, 1}; + textureDesc.size = {.width = requiredSize.x, .height = requiredSize.y, .depthOrArrayLayers = 1};
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/plugin/default-pipeline/src/system/initialization/Create3DGraph.cpp`:
- Around line 67-73: Update the docstring above the G-buffer setup in
Create3DGraph.cpp to reflect that textures are initialized to the current window
size (using Window::GetSize() or equivalent) with a fallback/default of 800×800,
rather than always being created at 800×800; mention that the textures (normal
RGBA16F, albedo BGRA8Unorm, depth Depth32Float) are stored in the engine's
TextureContainer under the pipeline G-buffer IDs and that
Window::Event::OnResize handlers recreate/replace them when the window size
changes.
|
|
duplicated code seems to only be for docs ?? so it will be merged as is even though sonar is not happy |


This pull request introduces a new GBuffer render pass and updates the GPU transform buffer and rendering pipeline to support it. The changes improve the handling of normal matrices in GPU buffers and shaders, and refactor the render graph initialization to include the new GBuffer pass. Additionally, there are minor improvements to resource and shader management for consistency.
Here the result textures of the gbuffer test:
GBuffer Render Pass Integration:
GBufferrender pass (GBuffer.hpp) that outputs normal, albedo, and depth textures, including its shader definition and rendering logic.Create3DGraphsystem for initializing the render graph with the GBuffer pass and its output textures. [1] [2]Create3DGraphsystem in the default pipeline plugin, ensuring it runs during pipeline setup. [1] [2]GPU Transform Buffer and Shader Updates:
TransformGPUBuffer.hpp) to store the normal matrix as a fullmat4instead of threevec4columns, updating both the struct and buffer writing logic. [1] [2]DefaultRenderPass.hppto usemat4for the normal matrix, and adjusted how normals are transformed in the vertex shader. [1] [2] [3]Resource and Shader Management Improvements:
ASingleExecutionRenderPassand removed legacy includes for single execution render passes. [1] [2]Depth32Floatfor consistency across passes.Summary by CodeRabbit
New Features
Improvements
Tests
Chores
Breaking Changes
✏️ Tip: You can customize this high-level summary in your review settings.