载入中...
搜索中...
未找到
GraphicsDeferredGraph.cpp
浏览该文件的文档.
1// Deferred shadow/G-buffer graph ownership and recording.
2#include <array>
3#include <memory>
4#include <string>
8#include "thread/Thread.h"
9
10namespace eve::graphics::vulkan {
11
12void Graphics::resetDeferredFrameGraphs() {
13 // Graph command pools/framebuffers borrow these targets. Retire every
14 // submitted slot before destroying graphs, then destroy target images.
15 if (device.instance) device->waitIdle();
16 for (auto &graph : deferredFrameGraphs_) graph.reset();
17 deferredGraphRecorded_ = false;
18 deferredGraphRecordedSlot_ = 0;
19}
20
21vkb::FrameGraph *Graphics::currentDeferredFrameGraph() {
22 if (deferredFrameGraphs_[0] == nullptr) return nullptr;
23 return deferredFrameGraphs_[currentFrameSlot() % deferredFrameGraphs_.size()].get();
24}
25
26void Graphics::buildDeferredFrameGraphs() {
27 // One FrameGraph per in-flight slot imports the engine-owned targets and
28 // owns the deferred passes: the 3 CSM cascades (per-layer views of the
29 // shadow array) + the G-buffer fill share one dependency-free layer, so the
30 // JobSystem executor records all four command buffers concurrently (see
31 // recordDeferredFrameGraph). The engine keeps image ownership so
32 // renderEntityIdMask / readGBufferToImageData and the postFX wrappers are
33 // unaffected. Each graph is only used on its slot's frames, so its command
34 // buffer is reused two frames later — by then the present slot fence
35 // guarantees the previous graph submit completed (same queue, submitted
36 // before the present command buffer).
37 const vk::Format depthFmt = vk::Format::eD32Sfloat;
38 const vk::Format colorFmt = pickGBufferColorFormat(device);
39 const uint32_t mapSize = uint32_t(ShadowConfig::kMapSize);
40 const uint32_t shadowLayers = uint32_t(ShadowConfig::kTotalLayers);
41 const uint32_t w = gbufferWidth > 0 ? uint32_t(gbufferWidth) : 1u;
42 const uint32_t h = gbufferHeight > 0 ? uint32_t(gbufferHeight) : 1u;
43
44 for (size_t i = 0; i < deferredFrameGraphs_.size(); ++i) {
45 auto graph = std::make_unique<vkb::FrameGraph>(&device, 1);
46
47 vkb::TextureDesc shadowDesc;
48 shadowDesc.format = depthFmt;
49 shadowDesc.extent = vk::Extent3D{mapSize, mapSize, 1};
50 shadowDesc.arrayLayers = shadowLayers;
51 shadowDesc.aspect = vk::ImageAspectFlagBits::eDepth;
52 shadowDesc.usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eDepthStencilAttachment;
53 // Shadow descriptors sample the depth array in SHADER_READ_ONLY, matching
54 // the legacy shadow pass and the layout of every imported cascade.
55 shadowDesc.afterLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
56 vk::ClearValue shadowClear{};
57 shadowClear.depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
58 const bool haveShadowSlot = i < shadowMaps.size() && shadowMaps[i].image.layerCount() >= shadowLayers;
59 if (haveShadowSlot) {
60 const vk::Image shadowImage = shadowMaps[i].image.image();
61 for (uint32_t c = 0; c < shadowLayers; ++c) {
62 auto shadowH = graph->importTexture("shadowCascade" + std::to_string(c), shadowImage,
63 shadowMaps[i].image.layerView(c), shadowDesc);
64 graph->addPass("shadow" + std::to_string(c))
65 .depthAttachment(shadowH, vkb::AttachmentOp::clear(shadowClear))
66 .record([this, c](vkb::FrameGraphPassContext &ctx) { recordShadowCascadePass(ctx, int(c)); });
67 }
68 }
69
70 if (i < gbufferSlots.size() && gbufferWidth > 0 && gbufferHeight > 0) {
71 auto &slot = gbufferSlots[i];
72 vkb::TextureDesc colorDesc;
73 colorDesc.format = colorFmt;
74 colorDesc.extent = vk::Extent3D{w, h, 1};
75 colorDesc.usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eColorAttachment |
76 vk::ImageUsageFlagBits::eTransferSrc;
77 colorDesc.afterLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
78 auto normalH = graph->importTexture("gbNormal", slot.normal.image(), slot.normal.imageView(), colorDesc);
79 auto depthColorH =
80 graph->importTexture("gbDepthColor", slot.depthColor.image(), slot.depthColor.imageView(), colorDesc);
81 auto albedoH = graph->importTexture("gbAlbedo", slot.albedo.image(), slot.albedo.imageView(), colorDesc);
82 auto pbrParamsH =
83 graph->importTexture("gbPbrParams", slot.pbrParams.image(), slot.pbrParams.imageView(), colorDesc);
84 auto emissiveH =
85 graph->importTexture("gbEmissive", slot.emissive.image(), slot.emissive.imageView(), colorDesc);
86
87 vkb::TextureDesc depthDesc;
88 depthDesc.format = depthFmt;
89 depthDesc.extent = vk::Extent3D{w, h, 1};
90 depthDesc.aspect = vk::ImageAspectFlagBits::eDepth;
91 depthDesc.usage = vk::ImageUsageFlagBits::eSampled | vk::ImageUsageFlagBits::eDepthStencilAttachment;
92 depthDesc.afterLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
93 auto depthH = graph->importTexture("gbHwDepth", slot.depth.image(), slot.depth.imageView(), depthDesc);
94
95 std::array<vk::ClearValue, 6> clears{};
96 clears[0].color = vk::ClearColorValue(std::array<float, 4>{0, 0, 0, 0});
97 clears[1].color = vk::ClearColorValue(std::array<float, 4>{1, 1, 1, 1});
98 clears[2].color = vk::ClearColorValue(std::array<float, 4>{0, 0, 0, 0});
99 clears[3].color = vk::ClearColorValue(std::array<float, 4>{0, 1, 1, 1});
100 clears[4].color = vk::ClearColorValue(std::array<float, 4>{0, 0, 0, 0});
101 clears[5].depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
102 graph->addPass("gbuffer")
103 .colorAttachment(normalH, vkb::AttachmentOp::clear(clears[0]))
104 .colorAttachment(depthColorH, vkb::AttachmentOp::clear(clears[1]))
105 .colorAttachment(albedoH, vkb::AttachmentOp::clear(clears[2]))
106 .colorAttachment(pbrParamsH, vkb::AttachmentOp::clear(clears[3]))
107 .colorAttachment(emissiveH, vkb::AttachmentOp::clear(clears[4]))
108 .depthAttachment(depthH, vkb::AttachmentOp::clear(clears[5]))
109 .record([this](vkb::FrameGraphPassContext& ctx) { recordGBufferPassDraws(ctx); });
110 }
111 graph->compile();
112 deferredFrameGraphs_[i] = std::move(graph);
113 }
114}
115
116void Graphics::recordShadowCascadePass(vkb::FrameGraphPassContext &ctx, int cascade) {
117 // Runs inside the FrameGraph's "shadow<cascade>" render-pass instance
118 // (already begun with a depth clear); only draw commands go here. The pass
119 // may be recorded on a JobSystem worker, so everything below must be
120 // read-only: shadowCascadeDraws was captured by endShadowPass on the main
121 // thread before the graph records.
122 auto &cb = ctx.commandBuffer();
123 const vk::Extent2D extent = ctx.extent();
124 const uint32_t size = extent.width ? extent.width : uint32_t(ShadowConfig::kMapSize);
125 setViewportAndScissor(cb, size, size);
126 vk::Pipeline boundPipeline{};
127 for (const auto &d : shadowCascadeDraws[cascade]) {
128 if (!d.mesh || !d.mesh->gpuHandle) continue;
129 const bool wantAlpha = d.alphaTest && shadowAlphaPipeline;
130 const bool skinned = d.skinSet && d.mesh->hasGpuSkinning();
131 vk::Pipeline wanted{};
132 if (skinned) {
133 wanted = wantAlpha ? (d.doubleSided ? shadowSkinAlphaPipeline : shadowSkinAlphaSingleSidedPipeline)
134 : (d.doubleSided ? shadowSkinPipeline : shadowSkinSingleSidedPipeline);
135 } else {
136 wanted = wantAlpha ? (d.doubleSided ? shadowAlphaPipeline : shadowAlphaSingleSidedPipeline)
137 : (d.doubleSided ? shadowPipeline : shadowSingleSidedPipeline);
138 }
139 if (wanted != boundPipeline) {
140 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, wanted);
141 boundPipeline = wanted;
142 }
143 auto *gpuMesh = static_cast<GpuMesh *>(d.mesh->gpuHandle);
144 if (skinned) {
145 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, skinPassPipelineLayout, 0, 1, &d.skinSet, 1,
146 &d.skinUboOffset);
147 } else if (wantAlpha) {
148 Texture *alb = d.albedo ? d.albedo : whiteTexture;
149 if (alb && alb->gpuHandle && texSetLayout) {
150 auto *gpuTex = static_cast<GpuTexture *>(alb->gpuHandle);
151 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, shadowAlphaPipelineLayout, 0, 1,
152 gpuTex->descriptorSet.ptr(), 0, nullptr);
153 }
154 }
155 if (!skinned) {
156 if (wantAlpha) {
157 ShadowAlphaPush push{d.mvp, d.lodFade};
158 cb.pushConstants(shadowAlphaPipelineLayout,
159 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
160 0, sizeof(push), &push);
161 } else {
162 cb.pushConstants(shadowPipelineLayout, vk::ShaderStageFlagBits::eVertex,
163 0, sizeof(glm::mat4), &d.mvp);
164 }
165 }
166 drawIndexedMesh(cb, *gpuMesh);
167 }
168}
169
170void Graphics::recordGBufferPassDraws(vkb::FrameGraphPassContext &ctx) {
171 // Runs inside the FrameGraph's "gbuffer" render-pass instance (already
172 // begun with the planned clear values); only draw commands go here. The
173 // pass may be recorded on a JobSystem worker, so everything below must be
174 // read-only: gbufferPassDraws was captured on the main thread.
175 auto &cb = ctx.commandBuffer();
176 const vk::Extent2D extent = ctx.extent();
177 const uint32_t w = extent.width ? extent.width : uint32_t(gbufferWidth);
178 const uint32_t h = extent.height ? extent.height : uint32_t(gbufferHeight);
179 setViewportAndScissor(cb, w, h);
180 vk::Pipeline boundPipeline{};
181 for (const auto &d : gbufferPassDraws) {
182 if (!d.mesh || !d.mesh->gpuHandle) continue;
183 const bool wantAlpha = d.alphaTest && gbufferAlphaPipeline;
184 const bool skinned = d.skinSet && d.mesh->hasGpuSkinning();
185 vk::Pipeline wanted = skinned ? (wantAlpha ? gbufferSkinAlphaPipeline : gbufferSkinPipeline)
186 : (wantAlpha ? gbufferAlphaPipeline : gbufferPipeline);
187 if (wanted != boundPipeline) {
188 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, wanted);
189 boundPipeline = wanted;
190 }
191 auto *gpuMesh = static_cast<GpuMesh *>(d.mesh->gpuHandle);
192 Texture *alb = d.albedo ? d.albedo : whiteTexture;
193 if (skinned) {
194 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, skinPassPipelineLayout, 0, 1, &d.skinSet, 1,
195 &d.skinUboOffset);
196 } else if (alb && alb->gpuHandle && texSetLayout) {
197 auto *gpuTex = static_cast<GpuTexture *>(alb->gpuHandle);
198 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, gbufferPipelineLayout, 0, 1,
199 gpuTex->descriptorSet.ptr(), 0, nullptr);
200 }
201 if (!skinned)
202 cb.pushConstants(gbufferPipelineLayout,
203 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
204 sizeof(GBufferPush), &d.push);
205 drawIndexedMesh(cb, *gpuMesh);
206 }
207}
208
209void Graphics::recordDeferredFrameGraph() {
210 // Voxel / script begin3DFrame paths never fill G-buffer or CSM. Submitting
211 // the empty four-pass graph every frame still waits on the JobSystem and
212 // can hitch the orbit camera (MAILBOX then shows a newer pose, then an
213 // older in-flight image — a short reverse jump).
214 if (!gbufferPending && shadowPendingMask == 0) {
215 dropPendingOffscreenPasses();
216 return;
217 }
218 const size_t slot = currentFrameSlot();
219 if (deferredGraphRecorded_ && deferredGraphRecordedSlot_ == slot) {
220 // render3D can be called several times per script frame (e.g. the
221 // render tests call it 3x before present). The deferred graph's command
222 // buffers must only be recorded once per slot per frame — re-recording
223 // them while the previous submit is still in flight would reset
224 // in-use command buffers (UB, GPU hang).
225 return;
226 }
227 auto *graph = currentDeferredFrameGraph();
228 if (!graph && (!shadowMaps.empty() || !gbufferSlots.empty())) {
229 // Shadows can be enabled without a G-buffer pass (or vice versa); build
230 // the deferred graphs on demand from whatever targets exist today.
231 buildDeferredFrameGraphs();
232 graph = currentDeferredFrameGraph();
233 }
234 if (!graph || !gbufferPipeline || !gbufferRenderPass || !shadowPipeline) {
235 dropPendingOffscreenPasses();
236 return;
237 }
238 // Record the declarative deferred passes (3 CSM cascades + G-buffer, one
239 // independent layer) with the JobSystem executor — the four command
240 // buffers are recorded concurrently on workers — then submit them on the
241 // graphics queue before the swapchain pass begins. Layout transitions and
242 // the render-pass instances are planned by the FrameGraph. Same-queue
243 // submission order plus the present slot fence (waited in Present::begin)
244 // keep this slot's graph command buffers safe to reuse two frames later.
245 auto *jobs = thread::Thread::create()->getJobSystem();
246 jobs->beginFrame(); // idempotent wait; recycles the per-frame arena
247 // Re-plan every frame (cheap; device objects are cached) so the graph is
248 // in the compiled phase for this record cycle — vkb::FrameGraph enforces
249 // build -> compile -> record -> submit and record() exactly once per
250 // compile.
251 graph->compile();
252 // Parallel executor: each pass owns a dedicated command pool (one pool per
253 // frame slot per pass), so the workers never share a pool while recording
254 // concurrently — the Vulkan external-synchronization rule for command
255 // pools is satisfied structurally.
257 graph->submit();
258 if (!shadowMaps.empty()) currentShadowMap().image.setCurrentLayout(vk::ImageLayout::eDepthStencilReadOnlyOptimal);
259 jobs->endFrame();
260 for (auto &d : shadowCascadeDraws) d.clear();
261 gbufferPassDraws.clear();
262 shadowPendingMask = 0;
263 gbufferPending = false;
264 deferredGraphRecorded_ = true;
265 deferredGraphRecordedSlot_ = slot;
266}
267
268} // namespace eve::graphics::vulkan
float w
Definition AnimClip.cpp:738
vk::UniqueImage image
std::int32_t c
int h
bool doubleSided
std::map< std::string, std::vector< std::string > > graph
Definition Package.cpp:59
float d
float size
Definition TreeMesh.cpp:156
virtual void reset()
Resets the current color, background color, line style, and so forth.
void push(bool all)
Pushes .
void clear(std::optional< Color > color, std::optional< int > stencil, std::optional< double > depth) override
Clears .
void recordFrameGraphWithJobSystem(vkb::FrameGraph &graph, eve::thread::JobSystem *jobs)
Record a vkb::FrameGraph with the engine JobSystem as the parallel recording executor.
static constexpr int kMapSize
Definition Shadow.h:28
static constexpr int kTotalLayers
Definition Shadow.h:27