载入中...
搜索中...
未找到
GraphicsPipeline.cpp
浏览该文件的文档.
1// Vulkan backend implementation — swapchain and pipeline creation.
2//
3// Re-split from the merged dev single-TU Graphics.cpp (pure move;
4// dev changes preserved). Shared helpers live in GraphicsInternal.h.
5
10#include "graphics/Light.h"
14#include "graphics/GBuffer.h"
15#include "graphics/Outline.h"
17
18#include <SDL2/SDL.h>
19#include <SDL2/SDL_vulkan.h>
20
21#include <algorithm>
22#include <array>
23#include <cmath>
24#include <cstdio>
25#include <cstdlib>
26#include <cstring>
27#include <functional>
28#include <stdexcept>
29#include <string>
30#include <vector>
31#if !defined(_WIN32)
32#include <unistd.h>
33#endif
34
35#include "common/Exception.h"
37#include "common/config.h"
39#include "image/Image.h"
40#include "image/ImageData.h"
41#include "zeroerr/assert.h"
42
43#include <memory>
44
45
46#include <assimp/mesh.h>
47#include <assimp/matrix3x3.h>
48#include <assimp/matrix4x4.h>
49#include <assimp/vector3.h>
50#include <glm/gtc/matrix_transform.hpp>
51
52#include "graphics/shaders/color_frag_spv.inc"
53#include "graphics/shaders/color_vert_spv.inc"
54#include "graphics/shaders/decal_box_frag_spv.inc"
55#include "graphics/shaders/decal_box_vert_spv.inc"
56#include "graphics/shaders/lit2d_frag_spv.inc"
57#include "graphics/shaders/lit2d_vert_spv.inc"
58#include "graphics/shaders/mesh3d_clustered_frag_spv.inc"
59#include "graphics/shaders/mesh3d_clustered_vert_spv.inc"
60#include "graphics/shaders/mesh3d_frag_spv.inc"
61#include "graphics/shaders/mesh3d_gbuffer_alpha_frag_spv.inc"
62#include "graphics/shaders/mesh3d_gbuffer_frag_spv.inc"
63#include "graphics/shaders/mesh3d_gbuffer_skin_alpha_frag_spv.inc"
64#include "graphics/shaders/mesh3d_gbuffer_skin_frag_spv.inc"
65#include "graphics/shaders/mesh3d_gbuffer_skin_vert_spv.inc"
66#include "graphics/shaders/mesh3d_gbuffer_vert_spv.inc"
67#include "graphics/shaders/mesh3d_gpudriven_frag_spv.inc"
68#include "graphics/shaders/mesh3d_gpudriven_vert_spv.inc"
69#include "graphics/shaders/mesh3d_hair_frag_spv.inc"
70#include "graphics/shaders/mesh3d_hair_vert_spv.inc"
71#include "graphics/shaders/mesh3d_shadow_alpha_frag_spv.inc"
72#include "graphics/shaders/mesh3d_shadow_alpha_vert_spv.inc"
73#include "graphics/shaders/mesh3d_shadow_frag_spv.inc"
74#include "graphics/shaders/mesh3d_shadow_skin_alpha_frag_spv.inc"
75#include "graphics/shaders/mesh3d_shadow_skin_vert_spv.inc"
76#include "graphics/shaders/mesh3d_shadow_vert_spv.inc"
77#include "graphics/shaders/mesh3d_vert_spv.inc"
78#include "graphics/shaders/particle_distortion_frag_spv.inc"
79#include "graphics/shaders/primitive3d_frag_spv.inc"
80#include "graphics/shaders/primitive3d_vert_spv.inc"
81#include "graphics/shaders/resolve_vis_frag_spv.inc"
82#include "graphics/shaders/resolve_vis_vert_spv.inc"
83#include "graphics/shaders/scene_tonemap_frag_spv.inc"
84#include "graphics/shaders/textured_frag_spv.inc"
85#include "graphics/shaders/textured_opaque_frag_spv.inc"
86#include "graphics/shaders/textured_vert_spv.inc"
88
89namespace eve::graphics::vulkan {
90
91namespace {
92
93vk::Pipeline createPrimitive3DPipeline(vkb::Device &device, const vkb::BuiltRenderPass &renderPass,
94 vk::PipelineLayout layout, PrimitiveDepthMode depth, BlendMode blend,
95 PrimitiveCullMode cull, vk::SampleCountFlagBits samples) {
96 const bool depthTest = depth != PrimitiveDepthMode::Ignore;
97 const bool depthWrite = depth == PrimitiveDepthMode::TestAndWrite;
98 const vk::CullModeFlags cullFlags =
100 ? vk::CullModeFlagBits::eBack
101 : (cull == PrimitiveCullMode::Front ? vk::CullModeFlagBits::eFront : vk::CullModeFlagBits::eNone);
102 std::vector<vk::PipelineColorBlendAttachmentState> attachments(1, makeBlendAttachment(blend));
103 vk::PipelineColorBlendStateCreateInfo blending{};
104 blending.attachmentCount = 1;
105 blending.pAttachments = attachments.data();
106 return device.createPipeline()
107 .useClassicPipeline(embeddedSpirv(primitive3d_vert_spv), embeddedSpirv(primitive3d_frag_spv))
108 .setPipelineLayout(layout)
109 .setVertexInputState(vkb::VertexInputStateBuilder()
110 .addInputBinding<Primitive3DVertex>()
111 .addAttributeDescription<Primitive3DVertex>())
112 .setDynamicStatesViewportScissor()
113 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.f, cullFlags, vk::FrontFace::eCounterClockwise)
114 .setDepthStencil(depthTest, depthWrite, depthTest ? vk::CompareOp::eLessOrEqual : vk::CompareOp::eAlways)
115 .setMultisampler(false, samples)
116 .setColorBlending(blending)
117 .build(renderPass);
118}
119} // namespace
120
121// --- Swapchain and graphics pipelines -----------------------------------------
122
123void Graphics::destroyPresentGraphicsPipelines() {
124 auto destroyPipe = [&](vk::Pipeline &p) {
125 if (p) {
126 device->destroyPipeline(p);
127 p = nullptr;
128 }
129 };
130 destroyPipe(pipeline);
131 destroyPipe(solidAlphaPipeline);
132 destroyPipe(additiveSolidPipeline);
133 destroyPipe(premultipliedSolidPipeline);
134 destroyPipe(multiplySolidPipeline);
135 destroyPipe(texPipeline);
136 destroyPipe(additiveTexPipeline);
137 destroyPipe(premultipliedTexPipeline);
138 destroyPipe(multiplyTexPipeline);
139 destroyPipe(opaqueTexPipeline);
140 destroyPipe(particleDistortionPipeline);
141 destroyPipe(sceneTonemapPipeline);
142 destroyPipe(lit2dPipeline);
143 destroyPipe(lit2dAdditivePipeline);
144 destroyPipe(lit2dPremultipliedPipeline);
145 destroyPipe(lit2dMultiplyPipeline);
146 destroyPipe(lit2dOpaquePipeline);
147 destroyPipe(gpuParticleAlphaPipeline_);
148 destroyPipe(gpuParticleAdditivePipeline_);
149 destroyPipe(gpuParticlePremultipliedPipeline_);
150 destroyPipe(gpuParticleMultiplyPipeline_);
151 destroyPipe(gpuParticleOpaquePipeline_);
152 for (auto &g : ownedGpuShaders) {
153 if (!g || g->isMesh3D) continue;
154 destroyPipe(g->swapchainPipeline);
155 destroyPipe(g->swapchainOpaquePipeline);
156 }
157 if (renderpass) {
158 device->destroyRenderPass(renderpass);
159 renderpass = {};
160 }
161 presentAttachmentFormat_ = vk::Format::eUndefined;
162 // Mesh/scene-pass pipelines may still target the destroyed present RP.
163 scenePassPipelineTarget = vk::RenderPass{};
164}
165
166void Graphics::createSwapchainAndPipeline() {
168 // Framebuffers / command buffers alias the current swapchain images; tear
169 // them down before replacing the swapchain (destroy() waitIdles first).
170 presentRecording = {};
171 swapchainPass = {};
172 presentModel.destroy();
173 presentModel = vkb::Present{};
174
175 vkb::SwapchainBuilder swapchainBuilder = device.createSwapchain();
176 if (pixelWidth > 0 && pixelHeight > 0)
177 swapchainBuilder.set_desired_extent(uint32_t(pixelWidth), uint32_t(pixelHeight));
178 // Prefer HDR present formats when requested; always keep SDR UNORM fallbacks.
179 applyPreferredSwapchainFormats(swapchainBuilder);
180 if (vsyncEnabled) {
181 swapchainBuilder.set_desired_present_mode(vk::PresentModeKHR::eMailbox);
182 swapchainBuilder.add_fallback_present_mode(vk::PresentModeKHR::eFifo);
183 } else {
184 swapchainBuilder.set_desired_present_mode(vk::PresentModeKHR::eImmediate);
185 swapchainBuilder.add_fallback_present_mode(vk::PresentModeKHR::eMailbox);
186 swapchainBuilder.add_fallback_present_mode(vk::PresentModeKHR::eFifo);
187 }
188 // Allow screen getPixel / newImageData readback after present.
189 swapchainBuilder.add_image_usage_flags(vk::ImageUsageFlagBits::eTransferSrc);
190 auto swapRet = swapchainBuilder.set_old_swapchain(swapchain).build();
191 swapchain.destroy();
192 swapchain = swapRet;
193 if (swapchain.image_format != selectedSurfaceFormat_.format) {
194 // Builder fell through to an unlisted surface format; treat as SDR.
196 selectedSurfaceFormat_ = {swapchain.image_format, vk::ColorSpaceKHR::eSrgbNonlinear};
197 }
198
199 {
200 StartupStage stage(" vulkan: depth image");
201 depthImage = vkb::DepthStencilImage{device, swapchain.extent.width, swapchain.extent.height, depthFormat};
202 }
203
204 // HDR/SDR mode switches change the swapchain image format; cached present
205 // render-pass + pipelines must be rebuilt for the new attachment format.
206 if (renderpass && presentAttachmentFormat_ == vk::Format::eUndefined)
207 presentAttachmentFormat_ = swapchain.image_format;
208 if (renderpass && presentAttachmentFormat_ != swapchain.image_format)
209 destroyPresentGraphicsPipelines();
210
211 if (!renderpass) {
212 vkb::RenderPassBuilder rpBuilder{device};
213 renderpass =
214 rpBuilder.addPresentAttachment(swapchain.image_format, vk::AttachmentLoadOp::eClear)
215 .addDepthAttachment(depthFormat, vk::AttachmentLoadOp::eClear,
216 vk::AttachmentStoreOp::eDontCare)
217 .addSubpass(vkb::SubpassBuilder()
218 .addAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal)
219 .setDepthStencilAttachment(
220 1, vk::ImageLayout::eDepthStencilAttachmentOptimal))
221 .addDependency(VK_SUBPASS_EXTERNAL, 0,
222 vk::PipelineStageFlagBits::eColorAttachmentOutput |
223 vk::PipelineStageFlagBits::eEarlyFragmentTests,
224 vk::PipelineStageFlagBits::eColorAttachmentOutput |
225 vk::PipelineStageFlagBits::eEarlyFragmentTests,
226 {},
227 vk::AccessFlagBits::eColorAttachmentRead |
228 vk::AccessFlagBits::eColorAttachmentWrite |
229 vk::AccessFlagBits::eDepthStencilAttachmentWrite)
230 .build();
231 presentAttachmentFormat_ = swapchain.image_format;
232 }
233
234 if (!pipeline) {
235 if (!pipelineLayout) pipelineLayout = createPipelineLayout(device);
236 pipeline = createSolidColorPipeline(device, renderpass, pipelineLayout);
237 solidAlphaPipeline = createSolidColorPipeline(device, renderpass, pipelineLayout,
239 additiveSolidPipeline = createSolidColorPipeline(device, renderpass, pipelineLayout,
241 premultipliedSolidPipeline = createSolidColorPipeline(
242 device, renderpass, pipelineLayout, BlendMode::Premultiplied);
243 multiplySolidPipeline = createSolidColorPipeline(device, renderpass, pipelineLayout,
245 }
246
247 // Recreate textured / tonemap / lit / particle draw pipelines when they were
248 // torn down with the present render pass (format change).
249 if (!texPipeline && texSetLayout) {
250 auto vert = embeddedSpirv(textured_vert_spv);
251 auto frag = embeddedSpirv(textured_frag_spv);
252 texPipeline = createTexturedStylePipeline(vert, frag, renderpass, texPipelineLayout);
253 additiveTexPipeline = createTexturedStylePipeline(vert, frag, renderpass, texPipelineLayout,
255 premultipliedTexPipeline = createTexturedStylePipeline(
256 vert, frag, renderpass, texPipelineLayout, BlendMode::Premultiplied);
257 multiplyTexPipeline = createTexturedStylePipeline(vert, frag, renderpass, texPipelineLayout,
259 opaqueTexPipeline = createTexturedStylePipeline(vert, frag, renderpass, texPipelineLayout,
261 particleDistortionPipeline = createTexturedStylePipeline(
262 vert, embeddedSpirv(particle_distortion_frag_spv), renderpass, texPipelineLayout,
264 sceneTonemapPipeline = createTexturedStylePipeline(
265 vert, embeddedSpirv(scene_tonemap_frag_spv), renderpass, texPipelineLayout,
267 if (lit2dPipelineLayout) {
268 auto lvert = embeddedSpirv(lit2d_vert_spv);
269 auto lfrag = embeddedSpirv(lit2d_frag_spv);
270 lit2dPipeline = createTexturedStylePipeline(lvert, lfrag, renderpass, lit2dPipelineLayout);
271 lit2dAdditivePipeline =
272 createTexturedStylePipeline(lvert, lfrag, renderpass, lit2dPipelineLayout, BlendMode::Additive);
273 lit2dPremultipliedPipeline =
274 createTexturedStylePipeline(lvert, lfrag, renderpass, lit2dPipelineLayout, BlendMode::Premultiplied);
275 lit2dMultiplyPipeline =
276 createTexturedStylePipeline(lvert, lfrag, renderpass, lit2dPipelineLayout, BlendMode::Multiply);
277 lit2dOpaquePipeline =
278 createTexturedStylePipeline(lvert, lfrag, renderpass, lit2dPipelineLayout, BlendMode::Opaque);
279 }
280 rebuildGpuParticleDrawPipelines(renderpass);
281 for (auto &shader : ownedShaders) {
282 if (!shader || !shader->gpuHandle) continue;
283 auto *gpu = static_cast<GpuShader *>(shader->gpuHandle);
284 if (gpu->isMesh3D || gpu->swapchainPipeline) continue;
285 gpu->swapchainPipeline = createTexturedStylePipeline(
286 shader->vertexSpirv(), shader->fragmentSpirv(), renderpass, shaderPipelineLayout);
287 gpu->swapchainOpaquePipeline = createTexturedStylePipeline(
288 shader->vertexSpirv(), shader->fragmentSpirv(), renderpass, shaderPipelineLayout,
290 }
291 }
292
293 // After a present format change scenePassPipelineTarget was cleared so the
294 // next ensureScenePassPipelines rebuilds against the live scene/present pass.
295 // On first create (no prior mesh pipelines) seed the target so the initial
296 // createMesh3DPipeline build is treated as matching the present pass.
297 if (!scenePassPipelineTarget && !mesh3dPipeline) {
298 scenePassPipelineTarget = vk::RenderPass(renderpass);
299 scenePassPipelineSamples = vk::SampleCountFlagBits::e1;
300 }
301
302 presentModel = device.createPresent(swapchain).build(renderpass, depthImage.imageView());
303 // Multi-frame overlap: submit + present without waiting on this frame's
304 // fence, so the CPU can build frame N+1 while the GPU still renders frame N.
305 // Present caps frames_in_flight at 2 (independent of swapchain image count)
306 // so present-wait semaphores are not reused while WSI still holds them.
307 // Per-frame mutable GPU resources must be multi-buffered (see *FrameSlots).
308 presentModel.synchronous_frames = false;
309 ensurePresentCaptureHook();
310 swapchainDirty = false;
311}
312
313void Graphics::createTexturedPipeline() {
314 if (texPipeline) return;
315
316 if (!texSetLayout) {
317 vkb::DescriptorSetLayoutBuilder layoutBuilder;
318 texSetLayoutUnique = layoutBuilder
319 .image(0, vk::DescriptorType::eCombinedImageSampler,
320 vk::ShaderStageFlagBits::eFragment, 1)
321 .image(1, vk::DescriptorType::eCombinedImageSampler,
322 vk::ShaderStageFlagBits::eFragment, 1)
323 .image(2, vk::DescriptorType::eCombinedImageSampler,
324 vk::ShaderStageFlagBits::eFragment, 1)
325 .image(3, vk::DescriptorType::eCombinedImageSampler,
326 vk::ShaderStageFlagBits::eFragment, 1)
327 .image(4, vk::DescriptorType::eCombinedImageSampler,
328 vk::ShaderStageFlagBits::eFragment, 1)
329 .createUnique(device.instance);
330 texSetLayout = *texSetLayoutUnique;
331
332 vk::DescriptorPoolSize poolSizes[] = {
333 {vk::DescriptorType::eCombinedImageSampler, 8192},
334 {vk::DescriptorType::eUniformBuffer, 2048},
335 // Dynamic-offset UBOs (per-frame mesh3d ring) count against their own
336 // pool size type; without this entry the first dynamic set allocation
337 // fails with VK_ERROR_OUT_OF_POOL_MEMORY.
338 {vk::DescriptorType::eUniformBufferDynamic, 4096},
339 {vk::DescriptorType::eStorageBuffer, 16384},
340 };
341 vk::DescriptorPoolCreateInfo poolInfo{};
342 poolInfo.flags = vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet;
343 poolInfo.maxSets = 8192;
344 poolInfo.poolSizeCount = 4;
345 poolInfo.pPoolSizes = poolSizes;
346 descriptorPool = device->createDescriptorPool(poolInfo);
347
348 texPipelineLayout = createPipelineLayout(device, texSetLayout);
349 const auto pcr =
350 pushConstantRange(vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
352 shaderPipelineLayout = createPipelineLayout(device, texSetLayout, &pcr);
353 }
354
355 auto vert = embeddedSpirv(textured_vert_spv);
356 auto frag = embeddedSpirv(textured_frag_spv);
357 texPipeline = createTexturedStylePipeline(vert, frag, renderpass, texPipelineLayout);
358 additiveTexPipeline = createTexturedStylePipeline(vert, frag, renderpass, texPipelineLayout,
360 premultipliedTexPipeline = createTexturedStylePipeline(
361 vert, frag, renderpass, texPipelineLayout, BlendMode::Premultiplied);
362 multiplyTexPipeline = createTexturedStylePipeline(vert, frag, renderpass, texPipelineLayout,
364 opaqueTexPipeline = createTexturedStylePipeline(vert, frag, renderpass, texPipelineLayout,
366 particleDistortionPipeline = createTexturedStylePipeline(
367 vert, embeddedSpirv(particle_distortion_frag_spv), renderpass, texPipelineLayout,
369 sceneTonemapPipeline = createTexturedStylePipeline(
370 vert, embeddedSpirv(scene_tonemap_frag_spv), renderpass, texPipelineLayout,
372
373 createLit2DPipeline();
374 createGpuParticlePipelines();
375}
376
377vk::Pipeline Graphics::createTexturedStylePipeline(const std::vector<uint32_t> &vert,
378 const std::vector<uint32_t> &frag,
379 const vkb::BuiltRenderPass &rp,
380 vk::PipelineLayout layout, BlendMode mode,
381 vk::SampleCountFlagBits samples) {
382 ShaderModulePair modules(device, vert, frag);
383 if (mode == BlendMode::Additive || mode == BlendMode::Premultiplied ||
384 mode == BlendMode::Multiply) {
385 // cbs/attachments must outlive build(); keep the builder as a single
386 // expression (see createSolidColorPipeline).
387 std::vector<vk::PipelineColorBlendAttachmentState> attachments(1,
388 makeBlendAttachment(mode));
389 vk::PipelineColorBlendStateCreateInfo cbs{};
390 cbs.logicOpEnable = false;
391 cbs.attachmentCount = 1;
392 cbs.pAttachments = attachments.data();
393 return device.createPipeline()
394 .useClassicPipeline(modules.vert, modules.frag)
395 .setPipelineLayout(layout)
396 .setVertexInputState(vkb::VertexInputStateBuilder()
397 .addInputBinding<TexturedVertex>()
398 .addAttributeDescription<TexturedVertex>())
399 .setDynamicStatesViewportScissor()
400 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f,
401 vk::CullModeFlagBits::eNone, vk::FrontFace::eCounterClockwise)
402 .setDepthStencil(false, false)
403 .setMultisampler(false, samples)
404 .setColorBlending(cbs)
405 .build(rp);
406 }
407 if (mode == BlendMode::Opaque) {
408 return device.createPipeline()
409 .useClassicPipeline(modules.vert, modules.frag)
410 .setPipelineLayout(layout)
411 .setVertexInputState(vkb::VertexInputStateBuilder()
412 .addInputBinding<TexturedVertex>()
413 .addAttributeDescription<TexturedVertex>())
414 .setDynamicStatesViewportScissor()
415 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f,
416 vk::CullModeFlagBits::eNone, vk::FrontFace::eCounterClockwise)
417 .setDepthStencil(false, false)
418 .setMultisampler(false, samples)
419 .setColorBlending()
420 .build(rp);
421 }
422 // Alpha (original behavior).
423 return device.createPipeline()
424 .useClassicPipeline(modules.vert, modules.frag)
425 .setPipelineLayout(layout)
426 .setVertexInputState(vkb::VertexInputStateBuilder()
427 .addInputBinding<TexturedVertex>()
428 .addAttributeDescription<TexturedVertex>())
429 .setDynamicStatesViewportScissor()
430 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eNone,
431 vk::FrontFace::eCounterClockwise)
432 .setDepthStencil(false, false)
433 .setMultisampler(false, samples)
434 .setAlphaBlending(1)
435 .build(rp);
436}
437
438void Graphics::createLit2DPipeline() {
439 if (lit2dPipeline) return;
440
441 vkb::DescriptorSetLayoutBuilder layoutBuilder;
442 lit2dSetLayoutUnique =
443 layoutBuilder
444 .image(0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
445 .image(1, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
446 .buffer(2, vk::DescriptorType::eUniformBuffer, vk::ShaderStageFlagBits::eFragment, 1)
447 .createUnique(device.instance);
448 lit2dSetLayout = *lit2dSetLayoutUnique;
449
450 lit2dPipelineLayout = createPipelineLayout(device, lit2dSetLayout);
451
452 // Offscreen (synchronous) lit-2D path owns a dedicated UBO. The swapchain
453 // path's per-frame UBOs are allocated lazily in currentLighting2dUbo().
454 offscreenLighting2dUbo.allocate(frameToken(), device, vk::BufferUsageFlagBits::eUniformBuffer,
455 sizeof(Lighting2DUBO),
456 vk::MemoryPropertyFlagBits::eHostVisible |
457 vk::MemoryPropertyFlagBits::eHostCoherent);
458
459 auto vert = embeddedSpirv(lit2d_vert_spv);
460 auto frag = embeddedSpirv(lit2d_frag_spv);
461 lit2dPipeline = createTexturedStylePipeline(vert, frag, renderpass, lit2dPipelineLayout);
462 lit2dAdditivePipeline =
463 createTexturedStylePipeline(vert, frag, renderpass, lit2dPipelineLayout, BlendMode::Additive);
464 lit2dPremultipliedPipeline =
465 createTexturedStylePipeline(vert, frag, renderpass, lit2dPipelineLayout, BlendMode::Premultiplied);
466 lit2dMultiplyPipeline =
467 createTexturedStylePipeline(vert, frag, renderpass, lit2dPipelineLayout, BlendMode::Multiply);
468 lit2dOpaquePipeline = createTexturedStylePipeline(vert, frag, renderpass, lit2dPipelineLayout, BlendMode::Opaque);
469}
470
471vk::Pipeline Graphics::selectLit2DPipeline(BlendMode blend, bool offscreen, bool hdr) const {
472 auto pick = [](BlendMode mode, vk::Pipeline alpha, vk::Pipeline additive, vk::Pipeline premultiplied,
473 vk::Pipeline multiply, vk::Pipeline opaque) -> vk::Pipeline {
474 switch (mode) {
475 case BlendMode::Additive: return additive ? additive : alpha;
476 case BlendMode::Premultiplied: return premultiplied ? premultiplied : alpha;
477 case BlendMode::Multiply: return multiply ? multiply : alpha;
478 case BlendMode::Opaque: return opaque ? opaque : alpha;
479 case BlendMode::Alpha:
480 default: return alpha;
481 }
482 };
483 // HDR canvas flush and HDR present-compose both target hdrOffscreenRenderPass.
484 if ((offscreen && hdr) || (!offscreen && presentComposeActive_)) {
485 vk::Pipeline pipe =
486 pick(blend, hdrOffscreenLitPipeline, hdrOffscreenLitAdditivePipeline, hdrOffscreenLitPremultipliedPipeline,
487 hdrOffscreenLitMultiplyPipeline, hdrOffscreenLitOpaquePipeline);
488 if (pipe) return pipe;
489 }
490 if (offscreen) {
491 return pick(blend, offscreenLitPipeline, offscreenLitAdditivePipeline, offscreenLitPremultipliedPipeline,
492 offscreenLitMultiplyPipeline, offscreenLitOpaquePipeline);
493 }
494 return pick(blend, lit2dPipeline, lit2dAdditivePipeline, lit2dPremultipliedPipeline, lit2dMultiplyPipeline,
495 lit2dOpaquePipeline);
496}
497
498void Graphics::createMesh3DPipeline() {
499 if (mesh3dPipeline) return;
500
501 // Right-handed view, Y-up world. Projection uses perspectiveVulkanRH_ZO (Y flip for
502 // Vulkan NDC), so frontFace is Clockwise while mesh winding stays CCW in object space.
503 vkb::DescriptorSetLayoutBuilder layoutBuilder;
504 mesh3dSetLayoutUnique =
505 layoutBuilder.buffer(21, vk::DescriptorType::eStorageBuffer, vk::ShaderStageFlagBits::eVertex, 1)
506 .buffer(0, vk::DescriptorType::eUniformBufferDynamic,
507 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 1)
508 .image(1, vk::DescriptorType::eCombinedImageSampler,
509 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 1)
510 .image(2, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
511 .image(3, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
512 .buffer(4, vk::DescriptorType::eUniformBufferDynamic, vk::ShaderStageFlagBits::eFragment, 1)
513 .image(5, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
514 .image(6, vk::DescriptorType::eCombinedImageSampler,
515 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 1)
516 .image(7, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
517 .image(8, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
518 .image(9, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
519 .image(10, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
520 .image(16, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
521 .image(17, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
522 .image(18, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
523 .image(20, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
524 .createUnique(device.instance);
525 mesh3dSetLayout = *mesh3dSetLayoutUnique;
526
527 vkb::DescriptorSetLayoutBuilder skinLayoutBuilder;
528 skinPassSetLayoutUnique =
529 skinLayoutBuilder.buffer(2, vk::DescriptorType::eStorageBuffer, vk::ShaderStageFlagBits::eVertex, 1)
530 .buffer(0, vk::DescriptorType::eUniformBufferDynamic,
531 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 1)
532 .image(1, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
533 .createUnique(device.instance);
534 skinPassSetLayout = *skinPassSetLayoutUnique;
535 skinPassPipelineLayout = createPipelineLayout(device, skinPassSetLayout);
536
537 mesh3dPipelineLayout = createPipelineLayout(device, mesh3dSetLayout);
538 const auto pcr =
539 pushConstantRange(vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment,
541 mesh3dShaderPipelineLayout = createPipelineLayout(device, mesh3dSetLayout, &pcr);
542
543 // Per-draw UBOs live in a per-frame-slot ring; descriptor sets are cached
544 // per texture combination (see mesh3dSetFor / ensureMesh3dRing).
545 mesh3dFrameSlots.clear();
546
547 auto vert = embeddedSpirv(mesh3d_vert_spv);
548 auto frag = embeddedSpirv(mesh3d_frag_spv);
549 mesh3dPipeline =
550 createMesh3DStylePipeline(vert, frag, mesh3dPipelineLayout, renderpass,
551 vk::SampleCountFlagBits::e1);
552 mesh3dTransparentPipeline =
553 createMesh3DHairPipeline(vert, frag, mesh3dPipelineLayout, renderpass,
554 vk::SampleCountFlagBits::e1);
555 for (int blendValue = 0; blendValue < 5; ++blendValue) {
556 const auto blend = BlendMode(blendValue);
557 for (int depthValue = 0; depthValue < 2; ++depthValue) {
558 for (int doubleValue = 0; doubleValue < 2; ++doubleValue) {
559 const size_t index = mesh3dPipelineIndex(blend, depthValue != 0, doubleValue != 0);
560 mesh3dSurfacePipelines[index] = createMesh3DStylePipeline(
561 vert, frag, mesh3dPipelineLayout, renderpass, vk::SampleCountFlagBits::e1,
562 blend, depthValue != 0, doubleValue != 0);
563 }
564 }
565 }
566}
567
568void Graphics::createMesh3DClusteredPipeline() {
569 if (mesh3dClusteredPipeline) return;
570
571 vkb::DescriptorSetLayoutBuilder layoutBuilder;
572 mesh3dClusteredSetLayoutUnique =
573 layoutBuilder
574 .buffer(0, vk::DescriptorType::eUniformBufferDynamic,
575 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 1)
576 .image(1, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
577 .image(2, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
578 .image(3, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
579 .buffer(4, vk::DescriptorType::eStorageBuffer, vk::ShaderStageFlagBits::eFragment, 1)
580 .buffer(5, vk::DescriptorType::eStorageBuffer, vk::ShaderStageFlagBits::eFragment, 1)
581 .buffer(6, vk::DescriptorType::eStorageBuffer, vk::ShaderStageFlagBits::eFragment, 1)
582 .buffer(7, vk::DescriptorType::eUniformBufferDynamic, vk::ShaderStageFlagBits::eFragment, 1)
583 .image(8, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
584 .image(9, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
585 .image(10, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
586 .image(11, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
587 .image(12, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
588 .image(18, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
589 .image(19, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
590 .image(20, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment, 1)
591 .createUnique(device.instance);
592 mesh3dClusteredSetLayout = *mesh3dClusteredSetLayoutUnique;
593
594 mesh3dClusteredPipelineLayout = createPipelineLayout(device, mesh3dClusteredSetLayout);
595
596 mesh3dClusteredFrameSlots.clear();
597
598 auto vert = embeddedSpirv(mesh3d_clustered_vert_spv);
599 auto frag = embeddedSpirv(mesh3d_clustered_frag_spv);
600 mesh3dClusteredPipeline =
601 createMesh3DStylePipeline(vert, frag, mesh3dClusteredPipelineLayout, renderpass,
602 vk::SampleCountFlagBits::e1);
603}
604
605void Graphics::destroyShadowResources() {
606 resetDeferredFrameGraphs();
607 destroyPipeline(device, shadowPipeline);
608 destroyPipeline(device, shadowSingleSidedPipeline);
609 destroyPipelineLayout(device, shadowPipelineLayout);
610 destroyPipeline(device, shadowAlphaPipeline);
611 destroyPipeline(device, shadowAlphaSingleSidedPipeline);
612 destroyPipeline(device, shadowSkinPipeline);
613 destroyPipeline(device, shadowSkinSingleSidedPipeline);
614 destroyPipeline(device, shadowSkinAlphaPipeline);
615 destroyPipeline(device, shadowSkinAlphaSingleSidedPipeline);
616 destroyPipelineLayout(device, shadowAlphaPipelineLayout);
617 for (auto &slot : shadowMaps) {
618 for (int i = 0; i < ShadowConfig::kTotalLayers; ++i) {
619 if (slot.framebuffers[i]) {
620 device->destroyFramebuffer(slot.framebuffers[i]);
621 slot.framebuffers[i] = vk::Framebuffer{};
622 }
623 }
624 }
625 shadowMaps.clear();
626 if (shadowSampler) {
627 device->destroySampler(shadowSampler);
628 shadowSampler = vkb::DepthSampler{};
629 }
630 if (shadowRawSampler) {
631 device->destroySampler(shadowRawSampler);
632 shadowRawSampler = nullptr;
633 }
634 if (shadowRenderPass) {
635 device->destroyRenderPass(shadowRenderPass);
636 shadowRenderPass = {};
637 }
638 shadowPassCascade = -1;
639 shadowPassDraws.clear();
640 shadowPendingMask = 0;
641 for (auto &d : shadowCascadeDraws) d.clear();
642}
643
644void Graphics::destroyGBufferResources() {
645 resetDeferredFrameGraphs();
646 gbufferPassActive = false;
647 gbufferPending = false;
648 gbufferPassDraws.clear();
649 for (auto &slot : gbufferSlots) {
650 slot.normalTex.gpuHandle = nullptr;
651 slot.depthColorTex.gpuHandle = nullptr;
652 slot.albedoTex.gpuHandle = nullptr;
653 slot.pbrParamsTex.gpuHandle = nullptr;
654 slot.emissiveTex.gpuHandle = nullptr;
655 slot.visIDTex.gpuHandle = nullptr;
656 slot.visBaryTex.gpuHandle = nullptr;
657 slot.depthTex.gpuHandle = nullptr;
658 if (slot.framebuffer) {
659 device->destroyFramebuffer(slot.framebuffer);
660 slot.framebuffer = vk::Framebuffer{};
661 }
662 if (slot.visFramebuffer) {
663 device->destroyFramebuffer(slot.visFramebuffer);
664 slot.visFramebuffer = vk::Framebuffer{};
665 }
666 destroySampler(device, slot.normalGpu.sampler);
667 destroySampler(device, slot.depthColorGpu.sampler);
668 destroySampler(device, slot.albedoGpu.sampler);
669 destroySampler(device, slot.pbrParamsGpu.sampler);
670 destroySampler(device, slot.emissiveGpu.sampler);
671 destroySampler(device, slot.visIDGpu.sampler);
672 destroySampler(device, slot.visBaryGpu.sampler);
673 destroySampler(device, slot.depthGpu.sampler);
674 }
675 gbufferSlots.clear();
676 post2Sets.clear();
677 destroyPipeline(device, gbufferPipeline);
678 destroyPipeline(device, gbufferAlphaPipeline);
679 destroyPipeline(device, gbufferSkinPipeline);
680 destroyPipeline(device, gbufferSkinAlphaPipeline);
681 destroyPipeline(device, gbufferVisPipeline);
682 destroyPipeline(device, gbufferVgVisPipeline);
683 destroyPipelineLayout(device, gbufferPipelineLayout);
684 if (gbufferRenderPass) {
685 device->destroyRenderPass(gbufferRenderPass);
686 gbufferRenderPass = {};
687 }
688 if (gbufferVisRenderPass) {
689 device->destroyRenderPass(gbufferVisRenderPass);
690 gbufferVisRenderPass = {};
691 }
692 gbufferWidth = 0;
693 gbufferHeight = 0;
694 if (renderControl_) renderControl_->getGBuffer()->clear();
695}
696
697void Graphics::destroySceneColorResources() {
698 sceneColorPassOpen = false;
699 sceneColorHistoryValid = false;
700 if (device.instance) device->waitIdle();
701 destroyDeferredLightingResources();
702 for (auto &slot : sceneColorSlots) {
703 slot.colorTex.gpuHandle = nullptr;
704 if (slot.framebuffer) {
705 device->destroyFramebuffer(slot.framebuffer);
706 slot.framebuffer = vk::Framebuffer{};
707 }
708 destroySampler(device, slot.colorGpu.sampler);
709 }
710 sceneColorSlots.clear();
711 post2Sets.clear();
712 if (sceneColorRenderPass) {
713 device->destroyRenderPass(sceneColorRenderPass);
714 sceneColorRenderPass = {};
715 }
716 if (sceneColorResumeRenderPass) {
717 device->destroyRenderPass(sceneColorResumeRenderPass);
718 sceneColorResumeRenderPass = vk::RenderPass{};
719 }
720 sceneColorWidth = 0;
721 sceneColorHeight = 0;
722 sceneColorFormat = vk::Format::eUndefined;
723 sceneColorSamples = vk::SampleCountFlagBits::e1;
724}
725
726void Graphics::createUiColorResources(int uiW, int uiH) {
727 if (uiW <= 0 || uiH <= 0) return;
728 // Keep scene lighting in linear HDR. The swapchain remains display
729 // encoded and receives a separate final composite; regular script Canvas
730 // targets intentionally stay RGBA8.
731 const vk::Format colorFmt = vk::Format::eR16G16B16A16Sfloat;
732
733 const vk::SampleCountFlags supported =
734 device.physical_device.properties.limits.framebufferColorSampleCounts;
735 const vk::SampleCountFlagBits samples =
736 (supported & vk::SampleCountFlagBits::e4) ? vk::SampleCountFlagBits::e4
737 : vk::SampleCountFlagBits::e1;
738
739 // The render pass is size-independent and must remain stable for ImGui's
740 // lifetime: ImGui_ImplVulkan_Init builds its pipeline against it. Create it
741 // once, before any early-returns below.
742 if (!uiRenderPass) {
743 if (samples != vk::SampleCountFlagBits::e1) {
744 uiRenderPass =
745 device.createRenderPass()
746 .addColorAttachment(colorFmt, vk::AttachmentLoadOp::eClear,
747 vk::AttachmentStoreOp::eDontCare, samples)
748 .addResolveColorAttachment(colorFmt, vk::AttachmentLoadOp::eDontCare)
749 .addSubpass(vkb::SubpassBuilder()
750 .addAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal)
751 .addResolveAttachment(1, vk::ImageLayout::eColorAttachmentOptimal))
752 .addExternalShaderReadDependencies()
753 .build();
754 } else {
755 uiRenderPass =
756 device.createRenderPass()
757 .addSampledColorAttachment(colorFmt)
758 .addSubpass(vkb::SubpassBuilder()
759 .addAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal))
760 .addExternalShaderReadDependencies()
761 .build();
762 }
763 }
764 // Keep sample count in sync with the render pass even if target creation
765 // returns early (ImGui builds its pipeline from getUiMsaaSamples()).
766 uiColorSamples = samples;
767
768 if (!texSetLayout || !descriptorPool) return;
769 if (!uiTexturePipeline) {
770 const auto vert = embeddedSpirv(textured_vert_spv);
771 const auto frag = embeddedSpirv(textured_frag_spv);
772 const auto opaqueFrag = embeddedSpirv(textured_opaque_frag_spv);
773 uiTexturePipeline = createTexturedStylePipeline(vert, frag, uiRenderPass,
774 texPipelineLayout, BlendMode::Alpha,
775 uiColorSamples);
776 uiTextureOpaquePipeline = createTexturedStylePipeline(
777 vert, opaqueFrag, uiRenderPass, texPipelineLayout, BlendMode::Opaque,
778 uiColorSamples);
779 }
780
781 if (!uiColorSlots.empty() && uiColorWidth == uiW && uiColorHeight == uiH &&
782 uiColorFormat == colorFmt && uiColorSamples == samples)
783 return;
784 destroyUiColorTargets();
785
786 uiColorWidth = uiW;
787 uiColorHeight = uiH;
788 uiColorFormat = colorFmt;
789 uiColorSamples = samples;
790 const uint32_t w = uint32_t(uiW);
791 const uint32_t h = uint32_t(uiH);
792 const bool msaa = samples != vk::SampleCountFlagBits::e1;
793
794 uiColorSlots.resize(kAsyncResourceCopies);
795 for (auto &slot : uiColorSlots) {
796 slot.color = device.createColorTarget(w, h, colorFmt);
797 if (msaa) slot.msaaColor = device.createColorTarget(w, h, colorFmt, samples);
798 }
799 auto uiPass = uiRenderPass;
800
801 for (auto &slot : uiColorSlots) {
802 if (msaa) {
803 slot.framebuffer = uiPass.createFramebuffer(
804 device, w, h, {slot.msaaColor.asAttachment(), slot.color.asAttachment()});
805 } else {
806 slot.framebuffer = uiPass.createFramebuffer(device, w, h, {slot.color.asAttachment()});
807 }
808 }
809
810 auto makeSampleTex = [&](GpuTexture &gpu, Texture &tex, vk::ImageView view) {
811 vkb::SamplerBuilder sb;
812 gpu.sampler = sb.magFilter(vk::Filter::eLinear)
813 .minFilter(vk::Filter::eLinear)
814 .addressModeU(vk::SamplerAddressMode::eClampToEdge)
815 .addressModeV(vk::SamplerAddressMode::eClampToEdge)
816 .build(device);
817 auto sets = vkb::DescriptorSetBuilder()
818 .layout(texSetLayout)
819 .build(device.instance, descriptorPool);
820 gpu.descriptorSet = vkb::BoundSet{sets[0]};
821 gpu.width = width;
822 gpu.height = height;
823 gpu.viewOverride = view;
824 writeCombinedImageDescriptor(&gpu);
825 tex.width = width;
826 tex.height = height;
827 tex.pixelWidth = width;
828 tex.pixelHeight = height;
829 tex.gpuHandle = &gpu;
830 };
831 for (auto &slot : uiColorSlots)
832 makeSampleTex(slot.colorGpu, slot.colorTex, slot.color.imageView());
833}
834
835void Graphics::destroyUiColorTargets() {
836 if (device.instance) device->waitIdle();
837 for (auto &slot : uiColorSlots) {
838 slot.colorTex.gpuHandle = nullptr;
839 if (slot.framebuffer) {
840 device->destroyFramebuffer(slot.framebuffer);
841 slot.framebuffer = vk::Framebuffer{};
842 }
843 destroySampler(device, slot.colorGpu.sampler);
844 }
845 uiColorSlots.clear();
846 uiColorWidth = 0;
847 uiColorHeight = 0;
848 uiColorFormat = vk::Format::eUndefined;
849 // Keep uiColorSamples matching uiRenderPass; ImGui's pipeline is bound to it.
850}
851
852void Graphics::destroyUiColorResources() {
853 destroyUiColorTargets();
854 if (uiTexturePipeline) {
855 device->destroyPipeline(uiTexturePipeline);
856 uiTexturePipeline = nullptr;
857 }
858 if (uiTextureOpaquePipeline) {
859 device->destroyPipeline(uiTextureOpaquePipeline);
860 uiTextureOpaquePipeline = nullptr;
861 }
862 if (uiRenderPass) {
863 device->destroyRenderPass(uiRenderPass);
864 uiRenderPass = {};
865 }
866}
867
868void Graphics::queueUiResolve() {
869 auto *slot = currentUiColorSlot();
870 if (!slot || !slot->colorTex.gpuHandle) return;
871 // HDR present always composites UI in paper-white-relative linear space on
872 // the compose target (or, on the rare already-open swapchain path, draws
873 // ImGui directly). Never bind the opaque DisplayEncode path here — it would
874 // replace transparent texels with the UI clear color.
875 TexturedBatch resolve{&slot->colorTex, nullptr, nullptr, BlendMode::Alpha, Batcher{}};
876 resolve.batch.addTexturedRect(0.f, 0.f, float(uiColorWidth), float(uiColorHeight),
877 Color(1.f, 1.f, 1.f, 1.f), 0.f, 0.f, 1.f, 1.f);
878 pendingUiResolve = std::move(resolve);
879}
880
881bool Graphics::renderUiOverlayPass() {
882 if (!presentOverlayFn_) return false;
883 if (!presentOverlayFn_(presentOverlayUser_, nullptr)) return false;
884 createUiColorResources(int(swapchain.extent.width), int(swapchain.extent.height));
885 auto *slot = currentUiColorSlot();
886 if (!slot || !uiRenderPass || !slot->framebuffer) return false;
887
888 auto &cb = currentPresentCb();
889 const bool msaa = uiColorSamples != vk::SampleCountFlagBits::e1;
890 std::array<vk::ClearValue, 2> clears{};
891 clears[0].color = vk::ClearColorValue(std::array<float, 4>{0.f, 0.f, 0.f, 0.f});
892 clears[1].color = vk::ClearColorValue(std::array<float, 4>{0.f, 0.f, 0.f, 0.f});
893 vk::RenderPassBeginInfo rpBegin{};
894 rpBegin.renderPass = uiRenderPass;
895 rpBegin.framebuffer = slot->framebuffer;
896 rpBegin.renderArea = vk::Rect2D{{0, 0}, {uint32_t(uiColorWidth), uint32_t(uiColorHeight)}};
897 rpBegin.clearValueCount = msaa ? 2 : 1;
898 rpBegin.pClearValues = clears.data();
899
900 slot->color.beginColorAttachment();
901 if (msaa) slot->msaaColor.beginColorAttachment();
902 cb.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
903
904 VkCommandBuffer raw = static_cast<VkCommandBuffer>(cb);
906
907 cb.endRenderPass();
908 slot->color.endSampledLayout();
909 queueUiResolve();
910 return true;
911}
912
913
914void Graphics::createGBufferResources(int gbufW, int gbufH) {
915 if (gbufW <= 0 || gbufH <= 0) return;
916 if (!gbufferSlots.empty() && gbufferWidth == gbufW && gbufferHeight == gbufH && gbufferPipeline)
917 return;
918 destroyGBufferResources();
919
920 gbufferWidth = gbufW;
921 gbufferHeight = gbufH;
922 const uint32_t w = uint32_t(gbufW);
923 const uint32_t h = uint32_t(gbufH);
924 const vk::Format colorFmt = pickGBufferColorFormat(device);
925 const vk::Format depthFmt = vk::Format::eD32Sfloat;
926 const vk::Format visIDFmt = vk::Format::eR32G32Uint;
927 const vk::Format visBaryFmt = vk::Format::eR16G16Sfloat;
928
929 gbufferSlots.resize(kAsyncResourceCopies);
930 for (auto &slot : gbufferSlots) {
931 slot.normal = device.createColorTarget(w, h, colorFmt);
932 slot.depthColor = device.createColorTarget(w, h, colorFmt);
933 slot.albedo = device.createColorTarget(w, h, colorFmt);
934 slot.pbrParams = device.createColorTarget(w, h, colorFmt);
935 slot.emissive = device.createColorTarget(w, h, colorFmt);
936 slot.visID = device.createColorTarget(w, h, visIDFmt);
937 slot.visBary = device.createColorTarget(w, h, visBaryFmt);
938 slot.depth = device.createDepthTarget(w, h, depthFmt, true);
939 }
940
941 auto gbufferPass =
942 device.createRenderPass()
943 .addSampledColorAttachment(colorFmt)
944 .addSampledColorAttachment(colorFmt)
945 .addSampledColorAttachment(colorFmt)
946 .addSampledColorAttachment(colorFmt)
947 .addSampledColorAttachment(colorFmt)
948 .addSampledDepthAttachment(depthFmt)
949 .addSubpass(vkb::SubpassBuilder()
950 .addAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal)
951 .addAttachmentRef(1, vk::ImageLayout::eColorAttachmentOptimal)
952 .addAttachmentRef(2, vk::ImageLayout::eColorAttachmentOptimal)
953 .addAttachmentRef(3, vk::ImageLayout::eColorAttachmentOptimal)
954 .addAttachmentRef(4, vk::ImageLayout::eColorAttachmentOptimal)
955 .setDepthStencilAttachment(5, vk::ImageLayout::eDepthStencilAttachmentOptimal))
956 // Match the FrameGraph render pass used to record this pipeline:
957 // imported G-buffer targets begin undefined and leave sampled, so
958 // only the subpass-to-reader dependency is materialized.
959 .addDependency(0, VK_SUBPASS_EXTERNAL,
960 vk::PipelineStageFlagBits::eColorAttachmentOutput |
961 vk::PipelineStageFlagBits::eEarlyFragmentTests |
962 vk::PipelineStageFlagBits::eLateFragmentTests,
963 vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader,
964 vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
965 vk::AccessFlagBits::eShaderRead)
966 .build();
967 gbufferRenderPass = gbufferPass;
968
969 for (auto &slot : gbufferSlots) {
970 slot.framebuffer = gbufferPass.createFramebuffer(
971 device, w, h,
972 {slot.normal.asAttachment(), slot.depthColor.asAttachment(), slot.albedo.asAttachment(),
973 slot.pbrParams.asAttachment(), slot.emissive.asAttachment(), slot.depth.asAttachment()});
974 }
975
976 auto layoutBuilder = device.createPipelineLayout();
977 if (texSetLayout) layoutBuilder.set(texSetLayout);
978 gbufferPipelineLayout =
979 layoutBuilder
980 .push<GBufferPush>(vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment)
981 .build();
982
983 std::vector<uint32_t> vert(mesh3d_gbuffer_vert_spv,
984 mesh3d_gbuffer_vert_spv + mesh3d_gbuffer_vert_spv_count);
985 std::vector<uint32_t> frag(mesh3d_gbuffer_frag_spv,
986 mesh3d_gbuffer_frag_spv + mesh3d_gbuffer_frag_spv_count);
987 vk::ShaderModule vertModule = vkb::PipelineBuilder::createShaderModule(device.instance, vert);
988 vk::ShaderModule fragModule = vkb::PipelineBuilder::createShaderModule(device.instance, frag);
989 gbufferPipeline =
990 device.createPipeline()
991 .useClassicPipeline(vertModule, fragModule)
992 .setPipelineLayout(gbufferPipelineLayout)
993 .setVertexInputState(
994 vkb::VertexInputStateBuilder().addInputBinding<MeshVertex>().addAttributeDescription<MeshVertex>())
995 .setDynamicStatesViewportScissor()
996 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eNone,
997 vk::FrontFace::eClockwise)
998 .setColorAttachmentCount(5)
999 .build(gbufferPass);
1000 device->destroyShaderModule(vertModule);
1001 device->destroyShaderModule(fragModule);
1002
1003 // Alpha-cutout variant for billboard/card geometry (sprite-stack slices):
1004 // same layout/push constants, fragment discards transparent texels.
1005 vk::ShaderModule alphaVertModule =
1006 vkb::PipelineBuilder::createShaderModule(device.instance, vert);
1007 std::vector<uint32_t> alphaFrag(mesh3d_gbuffer_alpha_frag_spv,
1008 mesh3d_gbuffer_alpha_frag_spv +
1009 mesh3d_gbuffer_alpha_frag_spv_count);
1010 vk::ShaderModule alphaFragModule =
1011 vkb::PipelineBuilder::createShaderModule(device.instance, alphaFrag);
1012 gbufferAlphaPipeline =
1013 device.createPipeline()
1014 .useClassicPipeline(alphaVertModule, alphaFragModule)
1015 .setPipelineLayout(gbufferPipelineLayout)
1016 .setVertexInputState(
1017 vkb::VertexInputStateBuilder().addInputBinding<MeshVertex>().addAttributeDescription<MeshVertex>())
1018 .setDynamicStatesViewportScissor()
1019 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eNone,
1020 vk::FrontFace::eClockwise)
1021 .setColorAttachmentCount(5)
1022 .build(gbufferPass);
1023 device->destroyShaderModule(alphaVertModule);
1024 device->destroyShaderModule(alphaFragModule);
1025
1026 auto skinVert = embeddedSpirv(mesh3d_gbuffer_skin_vert_spv);
1027 auto skinFrag = embeddedSpirv(mesh3d_gbuffer_skin_frag_spv);
1028 gbufferSkinPipeline =
1029 device.createPipeline()
1030 .useClassicPipeline(skinVert, skinFrag)
1031 .setPipelineLayout(skinPassPipelineLayout)
1032 .setVertexInputState(
1033 vkb::VertexInputStateBuilder().addInputBinding<MeshVertex>().addAttributeDescription<MeshVertex>())
1034 .setDynamicStatesViewportScissor()
1035 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eNone,
1036 vk::FrontFace::eClockwise)
1037 .setColorAttachmentCount(5)
1038 .build(gbufferPass);
1039 auto skinAlphaFrag = embeddedSpirv(mesh3d_gbuffer_skin_alpha_frag_spv);
1040 gbufferSkinAlphaPipeline =
1041 device.createPipeline()
1042 .useClassicPipeline(skinVert, skinAlphaFrag)
1043 .setPipelineLayout(skinPassPipelineLayout)
1044 .setVertexInputState(
1045 vkb::VertexInputStateBuilder().addInputBinding<MeshVertex>().addAttributeDescription<MeshVertex>())
1046 .setDynamicStatesViewportScissor()
1047 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eNone,
1048 vk::FrontFace::eClockwise)
1049 .setColorAttachmentCount(5)
1050 .build(gbufferPass);
1051
1052 auto makeSampleTex = [&](GpuTexture &gpu, Texture &tex, vk::ImageView view) {
1053 vkb::SamplerBuilder sb;
1054 gpu.sampler = sb.nearestClamp().build(device);
1055 auto sets = vkb::DescriptorSetBuilder()
1056 .layout(texSetLayout)
1057 .build(device.instance, descriptorPool);
1058 gpu.descriptorSet = vkb::BoundSet{sets[0]};
1059 gpu.width = gbufW;
1060 gpu.height = gbufH;
1061 gpu.viewOverride = view;
1062 writeCombinedImageDescriptor(&gpu);
1063 tex.width = gbufW;
1064 tex.height = gbufH;
1065 tex.pixelWidth = gbufW;
1066 tex.pixelHeight = gbufH;
1067 tex.gpuHandle = &gpu;
1068 };
1069 for (auto &slot : gbufferSlots) {
1070 makeSampleTex(slot.normalGpu, slot.normalTex, slot.normal.imageView());
1071 makeSampleTex(slot.depthColorGpu, slot.depthColorTex, slot.depthColor.imageView());
1072 makeSampleTex(slot.albedoGpu, slot.albedoTex, slot.albedo.imageView());
1073 makeSampleTex(slot.pbrParamsGpu, slot.pbrParamsTex, slot.pbrParams.imageView());
1074 makeSampleTex(slot.emissiveGpu, slot.emissiveTex, slot.emissive.imageView());
1075 makeSampleTex(slot.visIDGpu, slot.visIDTex, slot.visID.imageView());
1076 makeSampleTex(slot.visBaryGpu, slot.visBaryTex, slot.visBary.imageView());
1077 makeSampleTex(slot.depthGpu, slot.depthTex, slot.depth.imageView());
1078 }
1079 createGpuDrivenVisResources(gbufW, gbufH);
1080}
1081
1082void Graphics::ensureDecalUnitBox() {
1083 if (decalUnitBox) return;
1084 // Unit cube [-0.5, 0.5]^3, one 4-vertex face per side with its own
1085 // normal + UVs so the decal projection samples a clean square per face.
1086 const float h = 0.5f;
1087 const std::vector<glm::vec3> pos = {
1088 // +Z
1089 {-h, -h, h}, {h, -h, h}, {h, h, h}, {-h, h, h},
1090 // -Z
1091 {h, -h, -h}, {-h, -h, -h}, {-h, h, -h}, {h, h, -h},
1092 // +X
1093 {h, -h, h}, {h, -h, -h}, {h, h, -h}, {h, h, h},
1094 // -X
1095 {-h, -h, -h}, {-h, -h, h}, {-h, h, h}, {-h, h, -h},
1096 // +Y
1097 {-h, h, h}, {h, h, h}, {h, h, -h}, {-h, h, -h},
1098 // -Y
1099 {-h, -h, -h}, {h, -h, -h}, {h, -h, h}, {-h, -h, h},
1100 };
1101 const std::vector<glm::vec3> nrm = {
1102 {0, 0, 1}, {0, 0, 1}, {0, 0, 1}, {0, 0, 1},
1103 {0, 0, -1}, {0, 0, -1}, {0, 0, -1}, {0, 0, -1},
1104 {1, 0, 0}, {1, 0, 0}, {1, 0, 0}, {1, 0, 0},
1105 {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0}, {-1, 0, 0},
1106 {0, 1, 0}, {0, 1, 0}, {0, 1, 0}, {0, 1, 0},
1107 {0, -1, 0}, {0, -1, 0}, {0, -1, 0}, {0, -1, 0},
1108 };
1109 const std::vector<glm::vec2> uv = {
1110 {0, 0}, {1, 0}, {1, 1}, {0, 1},
1111 {0, 0}, {1, 0}, {1, 1}, {0, 1},
1112 {0, 0}, {1, 0}, {1, 1}, {0, 1},
1113 {0, 0}, {1, 0}, {1, 1}, {0, 1},
1114 {0, 0}, {1, 0}, {1, 1}, {0, 1},
1115 {0, 0}, {1, 0}, {1, 1}, {0, 1},
1116 };
1117 const std::vector<uint32_t> idx = {
1118 0, 1, 2, 0, 2, 3, 4, 5, 6, 4, 6, 7,
1119 8, 9, 10, 8, 10, 11, 12, 13, 14, 12, 14, 15,
1120 16, 17, 18, 16, 18, 19, 20, 21, 22, 20, 22, 23,
1121 };
1122 std::vector<float> posF, nrmF, uvF;
1123 posF.reserve(pos.size() * 3);
1124 nrmF.reserve(nrm.size() * 3);
1125 uvF.reserve(uv.size() * 2);
1126 for (const auto &p : pos) posF.insert(posF.end(), {p.x, p.y, p.z});
1127 for (const auto &n : nrm) nrmF.insert(nrmF.end(), {n.x, n.y, n.z});
1128 for (const auto &t : uv) uvF.insert(uvF.end(), {t.x, t.y});
1129 decalUnitBox =
1130 newMeshFromArrays(posF.data(), nrmF.data(), uvF.data(), int(pos.size()), idx.data(),
1131 int(idx.size()));
1132}
1133
1134void Graphics::createDecalResources(int decalW, int decalH) {
1135 if (decalW <= 0 || decalH <= 0) return;
1136 if (!decalSlots.empty() && decalWidth == decalW && decalHeight == decalH && decalPipeline)
1137 return;
1138 destroyDecalResources();
1139
1140 decalWidth = decalW;
1141 decalHeight = decalH;
1142 const uint32_t w = uint32_t(decalW);
1143 const uint32_t h = uint32_t(decalH);
1144 const vk::Format colorFmt = pickGBufferColorFormat(device); // RGBA8
1145
1146 decalSlots.resize(kAsyncResourceCopies);
1147 for (auto &slot : decalSlots) {
1148 slot.albedo = device.createColorTarget(w, h, colorFmt);
1149 slot.normal = device.createColorTarget(w, h, colorFmt);
1150 slot.params = device.createColorTarget(w, h, colorFmt);
1151 slot.cameraUbo.allocate(frameToken(), device, vk::BufferUsageFlagBits::eUniformBuffer,
1152 sizeof(DecalCameraUBO), kHostVisibleCoherent);
1153 }
1154
1155 auto decalPass =
1156 device.createRenderPass()
1157 .addSampledColorAttachment(colorFmt)
1158 .addSampledColorAttachment(colorFmt)
1159 .addSampledColorAttachment(colorFmt)
1160 .addSubpass(vkb::SubpassBuilder()
1161 .addAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal)
1162 .addAttachmentRef(1, vk::ImageLayout::eColorAttachmentOptimal)
1163 .addAttachmentRef(2, vk::ImageLayout::eColorAttachmentOptimal))
1164 .addExternalShaderReadDependencies()
1165 .build();
1166 decalRenderPass = decalPass;
1167
1168 for (auto &slot : decalSlots) {
1169 slot.framebuffer = decalPass.createFramebuffer(
1170 device, w, h,
1171 {slot.albedo.asAttachment(), slot.normal.asAttachment(), slot.params.asAttachment()});
1172 }
1173
1174 vkb::DescriptorSetLayoutBuilder layoutBuilder;
1175 decalSetLayoutUnique =
1176 layoutBuilder
1177 .image(0, vk::DescriptorType::eCombinedImageSampler,
1178 vk::ShaderStageFlagBits::eFragment, 1)
1179 .image(1, vk::DescriptorType::eCombinedImageSampler,
1180 vk::ShaderStageFlagBits::eFragment, 1)
1181 .image(2, vk::DescriptorType::eCombinedImageSampler,
1182 vk::ShaderStageFlagBits::eFragment, 1)
1183 .image(3, vk::DescriptorType::eCombinedImageSampler,
1184 vk::ShaderStageFlagBits::eFragment, 1)
1185 .image(4, vk::DescriptorType::eCombinedImageSampler,
1186 vk::ShaderStageFlagBits::eFragment, 1)
1187 .buffer(5, vk::DescriptorType::eUniformBuffer,
1188 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 1)
1189 .createUnique(device.instance);
1190 decalSetLayout = *decalSetLayoutUnique;
1191 const vk::PushConstantRange decalPushRange{
1192 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
1193 uint32_t(sizeof(DecalInstanceData))};
1194 decalPipelineLayout = device.createPipelineLayout()
1195 .set(decalSetLayout)
1196 .push(vk::ShaderStageFlagBits::eVertex |
1197 vk::ShaderStageFlagBits::eFragment,
1198 decalPushRange.size, decalPushRange.offset)
1199 .build();
1200
1201 std::vector<uint32_t> vert(decal_box_vert_spv, decal_box_vert_spv + decal_box_vert_spv_count);
1202 std::vector<uint32_t> frag(decal_box_frag_spv, decal_box_frag_spv + decal_box_frag_spv_count);
1203 vk::ShaderModule vertModule = vkb::PipelineBuilder::createShaderModule(device.instance, vert);
1204 vk::ShaderModule fragModule = vkb::PipelineBuilder::createShaderModule(device.instance, frag);
1205
1206 // Alpha-over compositing for the three layer targets (non-premultiplied
1207 // decal output; the shader writes rgb + coverage in alpha).
1208 decalPipeline = device.createPipeline()
1209 .useClassicPipeline(vertModule, fragModule)
1210 .setPipelineLayout(decalPipelineLayout)
1211 .setDynamicStatesViewportScissor()
1212 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f,
1213 vk::CullModeFlagBits::eNone, vk::FrontFace::eClockwise)
1214 .setMultisampler(false, vk::SampleCountFlagBits::e1)
1215 .setDepthStencil(false, false, vk::CompareOp::eAlways)
1216 .setAlphaBlending(3)
1217 .build(decalPass);
1218 device->destroyShaderModule(vertModule);
1219 device->destroyShaderModule(fragModule);
1220
1221 auto makeSampleTex = [&](GpuTexture &gpu, Texture &tex, vk::ImageView view) {
1222 vkb::SamplerBuilder sb;
1223 gpu.sampler = sb.nearestClamp().build(device);
1224 auto sets = vkb::DescriptorSetBuilder()
1225 .layout(texSetLayout)
1226 .build(device.instance, descriptorPool);
1227 gpu.descriptorSet = vkb::BoundSet{sets[0]};
1228 gpu.width = decalW;
1229 gpu.height = decalH;
1230 gpu.viewOverride = view;
1231 writeCombinedImageDescriptor(&gpu);
1232 tex.width = decalW;
1233 tex.height = decalH;
1234 tex.pixelWidth = decalW;
1235 tex.pixelHeight = decalH;
1236 tex.gpuHandle = &gpu;
1237 };
1238 for (auto &slot : decalSlots) {
1239 makeSampleTex(slot.albedoGpu, slot.albedoTex, slot.albedo.imageView());
1240 makeSampleTex(slot.normalGpu, slot.normalTex, slot.normal.imageView());
1241 makeSampleTex(slot.paramsGpu, slot.paramsTex, slot.params.imageView());
1242 }
1243}
1244
1245void Graphics::destroyDecalResources() {
1246 decalPassActive = false;
1247 decalPending = false;
1248 decalPassDraws.clear();
1249 for (auto &slot : decalSlots) {
1250 slot.albedoTex.gpuHandle = nullptr;
1251 slot.normalTex.gpuHandle = nullptr;
1252 slot.paramsTex.gpuHandle = nullptr;
1253 if (slot.framebuffer) {
1254 device->destroyFramebuffer(slot.framebuffer);
1255 slot.framebuffer = vk::Framebuffer{};
1256 }
1257 destroySampler(device, slot.albedoGpu.sampler);
1258 destroySampler(device, slot.normalGpu.sampler);
1259 destroySampler(device, slot.paramsGpu.sampler);
1260 slot.cameraUbo.release();
1261 slot.sets.clear();
1262 }
1263 decalSlots.clear();
1264 destroyPipeline(device, decalPipeline);
1265 destroyPipelineLayout(device, decalPipelineLayout);
1266 if (decalSetLayoutUnique) decalSetLayoutUnique.reset();
1267 if (decalRenderPass) {
1268 device->destroyRenderPass(decalRenderPass);
1269 decalRenderPass = {};
1270 }
1271 decalWidth = 0;
1272 decalHeight = 0;
1273}
1274
1275vkb::BoundSet Graphics::decalSetFor(DecalSlot &slot, GpuTexture *albedo, GpuTexture *normal,
1276 GpuTexture *params, GpuTexture *depth,
1277 GpuTexture *gbNormal) {
1278 ASSERT(albedo != nullptr);
1279 ASSERT(normal != nullptr);
1280 ASSERT(params != nullptr);
1281 ASSERT(depth != nullptr);
1282 ASSERT(gbNormal != nullptr);
1283 DecalSetKey key{albedo, normal, params, depth, gbNormal};
1284 auto it = slot.sets.find(key);
1285 if (it != slot.sets.end()) return it->second;
1286
1287 auto sets = vkb::DescriptorSetBuilder()
1288 .layout(decalSetLayout)
1289 .build(device.instance, descriptorPool);
1290 vkb::UnboundSet unbound{sets.front()};
1291 vkb::DescriptorSetUpdater updater(1, 5, 0);
1292 updater.beginDescriptorSet(unbound)
1293 .beginImages(0, 0, vk::DescriptorType::eCombinedImageSampler)
1294 .image(vkb::SampledImage::forLaterSample(albedo->sampler, albedo->imageView()))
1295 .beginImages(1, 0, vk::DescriptorType::eCombinedImageSampler)
1296 .image(vkb::SampledImage::forLaterSample(normal->sampler, normal->imageView()))
1297 .beginImages(2, 0, vk::DescriptorType::eCombinedImageSampler)
1298 .image(vkb::SampledImage::forLaterSample(params->sampler, params->imageView()))
1299 .beginImages(3, 0, vk::DescriptorType::eCombinedImageSampler)
1300 .image(vkb::SampledImage::forLaterSample(depth->sampler, depth->imageView()))
1301 .beginImages(4, 0, vk::DescriptorType::eCombinedImageSampler)
1302 .image(vkb::SampledImage::forLaterSample(gbNormal->sampler, gbNormal->imageView()))
1303 .beginBuffers(5, 0, vk::DescriptorType::eUniformBuffer)
1304 .buffer(slot.cameraUbo.buffer, 0, slot.cameraUbo.size)
1305 .update(device.instance);
1306
1307 vkb::BoundSet bound = std::move(unbound).publish();
1308 slot.sets.emplace(key, bound);
1309 return bound;
1310}
1311
1312void Graphics::createSceneColorResources(int sceneW, int sceneH) {
1313 if (sceneW <= 0 || sceneH <= 0) return;
1314 if (!texSetLayout || !descriptorPool) return;
1315 // Lighting and post effects stay linear HDR until the final swapchain resolve.
1316 const vk::Format colorFmt = vk::Format::eR16G16B16A16Sfloat;
1317
1318 const bool featureMsaa = !renderControl_ || renderControl_->isEnabled("msaa");
1319 const int desired = featureMsaa ? msaaSamples : 0;
1320 if (desired != appliedMsaa) appliedMsaa = desired;
1321 const vk::SampleCountFlagBits samples = sampleCountFlagFor(clampMsaaSamples(appliedMsaa));
1322
1323 if (!sceneColorSlots.empty() && sceneColorWidth == sceneW && sceneColorHeight == sceneH &&
1324 sceneColorFormat == colorFmt && sceneColorSamples == samples && sceneColorRenderPass)
1325 return;
1326 destroySceneColorResources();
1327
1328 sceneColorWidth = sceneW;
1329 sceneColorHeight = sceneH;
1330 sceneColorFormat = colorFmt;
1331 sceneColorSamples = samples;
1332 const uint32_t w = uint32_t(sceneW);
1333 const uint32_t h = uint32_t(sceneH);
1334 const vk::Format depthFmt = depthFormat;
1335 const bool msaa = samples != vk::SampleCountFlagBits::e1;
1336
1337 sceneColorSlots.resize(kAsyncResourceCopies);
1338 for (auto &slot : sceneColorSlots) {
1339 slot.color = device.createColorTarget(w, h, colorFmt);
1340 if (msaa) {
1341 slot.msaaColor = device.createColorTarget(w, h, colorFmt, samples);
1342 slot.depth = device.createDepthTarget(w, h, depthFmt, false, samples);
1343 } else {
1344 slot.depth = device.createDepthTarget(w, h, depthFmt, false);
1345 }
1346 }
1347
1348 if (msaa) {
1349 // MSAA color is transient (DONT_CARE store). Sampling the 1x resolve.
1350 // addSampledColorAttachment on the MSAA target makes MoltenVK treat it
1351 // as a shader-readable texture2d and segfaults on macOS.
1352 sceneColorRenderPass =
1353 device.createRenderPass()
1354 .addColorAttachment(colorFmt, vk::AttachmentLoadOp::eClear,
1355 vk::AttachmentStoreOp::eDontCare, samples)
1356 .addResolveColorAttachment(colorFmt, vk::AttachmentLoadOp::eDontCare)
1357 .addDepthAttachment(depthFmt, vk::AttachmentLoadOp::eClear,
1358 vk::AttachmentStoreOp::eDontCare, samples)
1359 .addSubpass(vkb::SubpassBuilder()
1360 .addAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal)
1361 .addResolveAttachment(1, vk::ImageLayout::eColorAttachmentOptimal)
1362 .setDepthStencilAttachment(
1363 2, vk::ImageLayout::eDepthStencilAttachmentOptimal))
1364 .addExternalShaderReadDependencies()
1365 .build();
1366 } else {
1367 sceneColorRenderPass =
1368 device.createRenderPass()
1369 .addSampledColorAttachment(colorFmt)
1370 .addDepthAttachment(depthFmt, vk::AttachmentLoadOp::eClear,
1371 vk::AttachmentStoreOp::eStore)
1372 .addSubpass(vkb::SubpassBuilder()
1373 .addAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal)
1374 .setDepthStencilAttachment(
1375 1, vk::ImageLayout::eDepthStencilAttachmentOptimal))
1376 .addExternalShaderReadDependencies()
1377 .build();
1378
1379 std::array<vk::AttachmentDescription, 2> attachments{};
1380 attachments[0] = vk::AttachmentDescription()
1381 .setFormat(colorFmt)
1382 .setSamples(vk::SampleCountFlagBits::e1)
1383 .setLoadOp(vk::AttachmentLoadOp::eLoad)
1384 .setStoreOp(vk::AttachmentStoreOp::eStore)
1385 .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
1386 .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
1387 .setInitialLayout(vk::ImageLayout::eColorAttachmentOptimal)
1388 .setFinalLayout(vk::ImageLayout::eShaderReadOnlyOptimal);
1389 attachments[1] = vk::AttachmentDescription()
1390 .setFormat(depthFmt)
1391 .setSamples(vk::SampleCountFlagBits::e1)
1392 .setLoadOp(vk::AttachmentLoadOp::eLoad)
1393 .setStoreOp(vk::AttachmentStoreOp::eStore)
1394 .setStencilLoadOp(vk::AttachmentLoadOp::eDontCare)
1395 .setStencilStoreOp(vk::AttachmentStoreOp::eDontCare)
1396 .setInitialLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal)
1397 .setFinalLayout(vk::ImageLayout::eDepthStencilAttachmentOptimal);
1398 const vk::AttachmentReference colorRef{0, vk::ImageLayout::eColorAttachmentOptimal};
1399 const vk::AttachmentReference depthRef{1, vk::ImageLayout::eDepthStencilAttachmentOptimal};
1400 const vk::SubpassDescription subpass =
1401 vk::SubpassDescription()
1402 .setPipelineBindPoint(vk::PipelineBindPoint::eGraphics)
1403 .setColorAttachmentCount(1)
1404 .setPColorAttachments(&colorRef)
1405 .setPDepthStencilAttachment(&depthRef);
1406 std::array<vk::SubpassDependency, 2> dependencies{};
1407 dependencies[0] = vk::SubpassDependency()
1408 .setSrcSubpass(VK_SUBPASS_EXTERNAL)
1409 .setDstSubpass(0)
1410 .setSrcStageMask(vk::PipelineStageFlagBits::eTransfer)
1411 .setDstStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput |
1412 vk::PipelineStageFlagBits::eEarlyFragmentTests)
1413 .setSrcAccessMask(vk::AccessFlagBits::eTransferRead)
1414 .setDstAccessMask(vk::AccessFlagBits::eColorAttachmentWrite |
1415 vk::AccessFlagBits::eDepthStencilAttachmentRead);
1416 dependencies[1] = vk::SubpassDependency()
1417 .setSrcSubpass(0)
1418 .setDstSubpass(VK_SUBPASS_EXTERNAL)
1419 .setSrcStageMask(vk::PipelineStageFlagBits::eColorAttachmentOutput)
1420 .setDstStageMask(vk::PipelineStageFlagBits::eFragmentShader)
1421 .setSrcAccessMask(vk::AccessFlagBits::eColorAttachmentWrite)
1422 .setDstAccessMask(vk::AccessFlagBits::eShaderRead);
1423 const vk::RenderPassCreateInfo resumeInfo =
1424 vk::RenderPassCreateInfo()
1425 .setAttachmentCount(static_cast<uint32_t>(attachments.size()))
1426 .setPAttachments(attachments.data())
1427 .setSubpassCount(1)
1428 .setPSubpasses(&subpass)
1429 .setDependencyCount(static_cast<uint32_t>(dependencies.size()))
1430 .setPDependencies(dependencies.data());
1431 sceneColorResumeRenderPass = device->createRenderPass(resumeInfo);
1432 }
1433 auto scenePass = sceneColorRenderPass;
1434
1435 for (auto &slot : sceneColorSlots) {
1436 if (msaa) {
1437 slot.framebuffer = scenePass.createFramebuffer(
1438 device, w, h,
1439 {slot.msaaColor.asAttachment(), slot.color.asAttachment(), slot.depth.asAttachment()});
1440 } else {
1441 slot.framebuffer = scenePass.createFramebuffer(
1442 device, w, h, {slot.color.asAttachment(), slot.depth.asAttachment()});
1443 }
1444 }
1445
1446 auto makeSampleTex = [&](GpuTexture &gpu, Texture &tex, vk::ImageView view) {
1447 vkb::SamplerBuilder sb;
1448 gpu.sampler = sb.magFilter(vk::Filter::eLinear)
1449 .minFilter(vk::Filter::eLinear)
1450 .addressModeU(vk::SamplerAddressMode::eClampToEdge)
1451 .addressModeV(vk::SamplerAddressMode::eClampToEdge)
1452 .build(device);
1453 auto sets = vkb::DescriptorSetBuilder()
1454 .layout(texSetLayout)
1455 .build(device.instance, descriptorPool);
1456 gpu.descriptorSet = vkb::BoundSet{sets[0]};
1457 gpu.width = width;
1458 gpu.height = height;
1459 gpu.viewOverride = view;
1460 writeCombinedImageDescriptor(&gpu);
1461 tex.width = width;
1462 tex.height = height;
1463 tex.pixelWidth = width;
1464 tex.pixelHeight = height;
1465 tex.gpuHandle = &gpu;
1466 };
1467 for (auto &slot : sceneColorSlots) makeSampleTex(slot.colorGpu, slot.colorTex, slot.color.imageView());
1468
1469 // Hybrid deferred lighting targets the scene-color render pass; rebuild when
1470 // the pass (format/samples/extent) changes.
1471 createDeferredLightingPipeline();
1472}
1473
1474bool Graphics::beginSceneColorRenderPass() {
1475 auto *slot = currentSceneColorSlot();
1476 if (!slot || !sceneColorRenderPass || !slot->framebuffer) return false;
1477 auto &cb = currentPresentCb();
1478 const bool msaa = sceneColorSamples != vk::SampleCountFlagBits::e1;
1479 // A=1 marks sky / far plane so SSGI skips uncleared pixels.
1480 if (msaa) {
1481 std::array<vk::ClearValue, 3> clears{};
1482 clears[0].color = vk::ClearColorValue(
1483 std::array<float, 4>{clearColor.r, clearColor.g, clearColor.b, 1.f});
1484 clears[1].color = vk::ClearColorValue(
1485 std::array<float, 4>{clearColor.r, clearColor.g, clearColor.b, 1.f});
1486 clears[2].depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
1487 vk::RenderPassBeginInfo rpBegin{};
1488 rpBegin.renderPass = sceneColorRenderPass;
1489 rpBegin.framebuffer = slot->framebuffer;
1490 rpBegin.renderArea =
1491 vk::Rect2D{{0, 0}, {uint32_t(sceneColorWidth), uint32_t(sceneColorHeight)}};
1492 rpBegin.clearValueCount = uint32_t(clears.size());
1493 rpBegin.pClearValues = clears.data();
1494 slot->msaaColor.beginColorAttachment();
1495 slot->color.beginColorAttachment();
1496 slot->depth.beginDepthAttachment();
1497 cb.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
1498 } else {
1499 std::array<vk::ClearValue, 2> clears{};
1500 clears[0].color = vk::ClearColorValue(
1501 std::array<float, 4>{clearColor.r, clearColor.g, clearColor.b, 1.f});
1502 clears[1].depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
1503 vk::RenderPassBeginInfo rpBegin{};
1504 rpBegin.renderPass = sceneColorRenderPass;
1505 rpBegin.framebuffer = slot->framebuffer;
1506 rpBegin.renderArea =
1507 vk::Rect2D{{0, 0}, {uint32_t(sceneColorWidth), uint32_t(sceneColorHeight)}};
1508 rpBegin.clearValueCount = uint32_t(clears.size());
1509 rpBegin.pClearValues = clears.data();
1510 slot->color.beginColorAttachment();
1511 slot->depth.beginDepthAttachment();
1512 cb.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
1513 }
1514 sceneColorPassOpen = true;
1515 return true;
1516}
1517
1518void Graphics::endSceneColorRenderPass() {
1519 if (!sceneColorPassOpen) return;
1520 auto &cb = currentPresentCb();
1521 cb.endRenderPass();
1522 sceneColorPassOpen = false;
1523 if (auto *slot = currentSceneColorSlot()) {
1524 slot->color.endSampledLayout();
1525 completedSceneColorSlot = currentFrameSlot() % sceneColorSlots.size();
1526 sceneColorHistoryValid = true;
1527 }
1528}
1529
1530int Graphics::clampMsaaSamples(int requested) const {
1531 if (requested <= 1) return 0;
1532 vk::SampleCountFlags supported =
1533 device.physical_device.properties.limits.framebufferColorSampleCounts &
1534 device.physical_device.properties.limits.framebufferDepthSampleCounts;
1535 const int candidates[3] = {8, 4, 2};
1536 for (int c : candidates) {
1537 if (requested >= c && (supported & sampleCountFlagFor(c))) return c;
1538 }
1539 for (int c : candidates) {
1540 if (supported & sampleCountFlagFor(c)) return c;
1541 }
1542 return 0;
1543}
1544
1545void Graphics::ensureScenePassPipelines(const vkb::BuiltRenderPass &target,
1546 vk::SampleCountFlagBits samples) {
1547 createMesh3DPipeline();
1548 const vk::RenderPass targetHandle = vk::RenderPass(target);
1549 if (targetHandle == scenePassPipelineTarget && samples == scenePassPipelineSamples) return;
1550 device->waitIdle();
1551 destroyPbrResources();
1552
1553 destroyPipeline(device, mesh3dPipeline);
1554 destroyPipeline(device, mesh3dTransparentPipeline);
1555 for (auto &pipeline : mesh3dSurfacePipelines) destroyPipeline(device, pipeline);
1556 destroyPipeline(device, mesh3dClusteredPipeline);
1557 destroyPipeline(device, mesh3dGpuDrivenPipeline);
1558 destroyPipeline(device, resolveVisPipeline);
1559 destroyPipeline(device, voxelRectPipeline);
1560 for (auto &pipeline : primitive3DPipelines) destroyPipeline(device, pipeline);
1561
1562 mesh3dPipeline = createMesh3DStylePipeline(embeddedSpirv(mesh3d_vert_spv),
1563 embeddedSpirv(mesh3d_frag_spv),
1564 mesh3dPipelineLayout, target, samples);
1565 mesh3dTransparentPipeline =
1566 createMesh3DHairPipeline(embeddedSpirv(mesh3d_vert_spv),
1567 embeddedSpirv(mesh3d_frag_spv), mesh3dPipelineLayout,
1568 target, samples);
1569 for (int blendValue = 0; blendValue < 5; ++blendValue) {
1570 const auto blend = BlendMode(blendValue);
1571 for (int depthValue = 0; depthValue < 2; ++depthValue) {
1572 for (int doubleValue = 0; doubleValue < 2; ++doubleValue) {
1573 const size_t index = mesh3dPipelineIndex(blend, depthValue != 0, doubleValue != 0);
1574 mesh3dSurfacePipelines[index] = createMesh3DStylePipeline(
1575 embeddedSpirv(mesh3d_vert_spv), embeddedSpirv(mesh3d_frag_spv),
1576 mesh3dPipelineLayout, target, samples, blend, depthValue != 0,
1577 doubleValue != 0);
1578 }
1579 }
1580 }
1581 if (mesh3dGpuDrivenPipelineLayout) {
1582 mesh3dGpuDrivenPipeline =
1583 createMesh3DStylePipeline(embeddedSpirv(mesh3d_gpudriven_vert_spv),
1584 embeddedSpirv(mesh3d_gpudriven_frag_spv),
1585 mesh3dGpuDrivenPipelineLayout, target, samples);
1586 createResolveVisPipeline(target, samples);
1587 }
1588 if (mesh3dClusteredPipelineLayout) {
1589 mesh3dClusteredPipeline =
1590 createMesh3DStylePipeline(embeddedSpirv(mesh3d_clustered_vert_spv),
1591 embeddedSpirv(mesh3d_clustered_frag_spv),
1592 mesh3dClusteredPipelineLayout, target, samples);
1593 }
1594 if (voxelRectPipelineLayout)
1595 voxelRectPipeline = buildVoxelRectPipeline(target, samples);
1596 rebuildPrimitive3DPipelines(target, samples);
1597
1598 for (auto &g : ownedGpuShaders) {
1599 if (!g->isMesh3D) continue;
1600 destroyPipeline(device, g->mesh3dPipeline);
1601 destroyPipeline(device, g->mesh3dXrayPipeline);
1602 g->mesh3dXrayPipeline = nullptr;
1603 if (!g->owner) continue;
1604 if (g->isHair3D) {
1605 g->mesh3dPipeline =
1606 createMesh3DHairPipeline(g->owner->vertexSpirv(), g->owner->fragmentSpirv(),
1607 g->pipelineLayout, target, samples);
1608 } else {
1609 g->mesh3dPipeline = createMesh3DStylePipeline(
1610 g->owner->vertexSpirv(), g->owner->fragmentSpirv(), g->pipelineLayout, target, samples,
1611 g->owner->meshBlend, g->owner->meshDepthWrite, g->owner->meshDoubleSided, g->owner->meshRasterState());
1612 g->mesh3dXrayPipeline =
1613 createMesh3DXrayPipeline(g->owner->vertexSpirv(), g->owner->fragmentSpirv(),
1614 g->pipelineLayout, target, samples);
1615 }
1616 }
1617
1618 scenePassPipelineTarget = targetHandle;
1619 scenePassPipelineSamples = samples;
1620}
1621
1622size_t Graphics::primitive3DPipelineIndex(PrimitiveDepthMode depth, BlendMode blend, PrimitiveCullMode cull) {
1623 return static_cast<size_t>(depth) * 15u + static_cast<size_t>(blend) * 3u + static_cast<size_t>(cull);
1624}
1625
1626void Graphics::rebuildPrimitive3DPipelines(const vkb::BuiltRenderPass &target, vk::SampleCountFlagBits samples) {
1627 buildPrimitive3DPipelines(target, samples, primitive3DPipelines);
1628}
1629
1630void Graphics::buildPrimitive3DPipelines(const vkb::BuiltRenderPass &target, vk::SampleCountFlagBits samples,
1631 std::array<vk::Pipeline, kPrimitive3DPipelineVariants> &pipelines) {
1632 for (auto &pipeline : pipelines) destroyPipeline(device, pipeline);
1633 for (int depthValue = 0; depthValue < 3; ++depthValue) {
1634 for (int blendValue = 0; blendValue < 5; ++blendValue) {
1635 for (int cullValue = 0; cullValue < 3; ++cullValue) {
1636 const auto depth = static_cast<PrimitiveDepthMode>(depthValue);
1637 const auto blend = static_cast<BlendMode>(blendValue);
1638 const auto cull = static_cast<PrimitiveCullMode>(cullValue);
1639 pipelines[primitive3DPipelineIndex(depth, blend, cull)] =
1640 createPrimitive3DPipeline(device, target, pipelineLayout, depth, blend, cull, samples);
1641 }
1642 }
1643 }
1644}
1645
1646void Graphics::queueSceneColorResolve() {
1648 if (!src) src = getSceneColorTexture();
1649 if (!src || !src->gpuHandle) return;
1650 // Still build the AA/Bloom/Exposure resolve when a script called
1651 // drawScene3D (sceneColorComposited). autoScene skips the implicit
1652 // fullscreen blit; present replaces the script's scene-color span.
1653 const bool reflectionPasses =
1654 renderControl_ && (renderControl_->isEnabled("rtgi") || renderControl_->isEnabled("ssr") ||
1655 renderControl_->isEnabled("rtx") || renderControl_->isEnabled("reflectionChain"));
1656 if (reflectionPasses) {
1657 auto *slot = currentSceneColorSlot();
1658 auto *snapshot = dynamic_cast<OffscreenCanvas *>(
1659 pipelineReflectionComposite(src->getWidth(), src->getHeight()));
1660 if (slot && snapshot) {
1661 auto &cb = currentPresentCb();
1662 slot->color.setLayout(cb, vk::ImageLayout::eTransferSrcOptimal);
1663 snapshot->colorImage().setLayout(cb, vk::ImageLayout::eTransferDstOptimal);
1664 vk::ImageCopy region{};
1665 region.srcSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1};
1666 region.dstSubresource = {vk::ImageAspectFlagBits::eColor, 0, 0, 1};
1667 region.extent = vk::Extent3D{uint32_t(src->getWidth()),
1668 uint32_t(src->getHeight()), 1};
1669 cb.copyImage(slot->color.image(), vk::ImageLayout::eTransferSrcOptimal,
1670 snapshot->colorImage().image(), vk::ImageLayout::eTransferDstOptimal,
1671 region);
1672 slot->color.setLayout(cb, vk::ImageLayout::eShaderReadOnlyOptimal);
1673 snapshot->colorImage().setLayout(cb, vk::ImageLayout::eShaderReadOnlyOptimal);
1674 src = snapshot->getTexture();
1675 }
1676 }
1677 pendingSceneResolveSource = src;
1678}
1679
1680void Graphics::materializeSceneColorResolve() {
1681 Texture *src = pendingSceneResolveSource;
1682 pendingSceneResolveSource = nullptr;
1683 if (!src || !src->gpuHandle) return;
1684 auto savedSolid = std::move(solidBatches);
1685 auto savedTextured = std::move(texturedBatches);
1686 auto savedLit = std::move(litBatches);
1687 auto savedSpans = std::move(overlaySpans);
1688 auto savedEngineSpans = std::move(engine3DSpans);
1689 auto savedUiResolve = std::move(pendingUiResolve);
1690 solidBatches.clear();
1691 texturedBatches.clear();
1692 litBatches.clear();
1693 overlaySpans.clear();
1694 engine3DSpans.clear();
1695 Texture *motion = nullptr;
1696 if (renderControl_ && renderControl_->getGBuffer()->isValid())
1697 motion = renderControl_->getGBuffer()->getVelocityTexture();
1698 Texture *postProcessed = prepareFinalSceneTexture(src, motion);
1699 if (postProcessed) {
1700 // AA and exposure are already applied. Use the final tone-map pipeline;
1701 // UNORM SDR swapchains also require explicit linear-to-sRGB encoding.
1702 // HDR compose keeps paper-white-relative linear; the final encode pass
1703 // applies PQ/scRGB. Direct HDR present (rare already-open swapchain path)
1704 // encodes here.
1705 const bool composeLinear = isDisplayHdrActive();
1706 const bool attachmentEncodesSrgb =
1707 !composeLinear && (swapchain.image_format == vk::Format::eB8G8R8A8Srgb ||
1708 swapchain.image_format == vk::Format::eR8G8B8A8Srgb);
1709 TexturedBatch resolve{postProcessed, nullptr, nullptr, BlendMode::Opaque, Batcher{}};
1710 Color tint = composeLinear
1715 ? display::ActiveColorSpace::Hdr10
1717 ? display::ActiveColorSpace::ScRgb
1718 : display::ActiveColorSpace::Sdr),
1719 attachmentEncodesSrgb, displayPaperWhiteNits_, displayPeakNits_);
1720 tint.r = static_cast<float>(getSceneToneMapping());
1721 tint.g += getScenePhotographicVignette() * .25f;
1722 resolve.batch.addTexturedRect(0.f, 0.f, float(width), float(height), tint, 0.f, 0.f, 1.f, 1.f);
1723 pendingSceneResolve = std::move(resolve);
1724 }
1725 solidBatches = std::move(savedSolid);
1726 texturedBatches = std::move(savedTextured);
1727 litBatches = std::move(savedLit);
1728 overlaySpans = std::move(savedSpans);
1729 engine3DSpans = std::move(savedEngineSpans);
1730 pendingUiResolve = std::move(savedUiResolve);
1731}
1732
1733void Graphics::createShadowResources() {
1734 if (!shadowMaps.empty()) return;
1735
1736 const uint32_t size = uint32_t(ShadowConfig::kMapSize);
1737 const uint32_t layers = uint32_t(ShadowConfig::kTotalLayers);
1738
1739 shadowMaps.resize(kAsyncResourceCopies);
1740 for (auto &slot : shadowMaps)
1741 slot.image = device.createDepthArray(size, size, layers, vk::Format::eD32Sfloat);
1742
1743 vkb::SamplerBuilder sb;
1744 // Hardware PCF: linear filtering + depth compare on the D32 shadow map.
1745 // The compare result of each filtered depth sample is blended by the driver,
1746 // giving a soft penumbra instead of the hard 1-texel boundary.
1747 shadowSampler = sb.linearClamp().compareEnable(VK_TRUE).compareOp(vk::CompareOp::eLess).buildDepthPcf(device);
1748 vkb::SamplerBuilder rawBuilder;
1749 shadowRawSampler = rawBuilder.magFilter(vk::Filter::eNearest)
1750 .minFilter(vk::Filter::eNearest)
1751 .addressModeU(vk::SamplerAddressMode::eClampToEdge)
1752 .addressModeV(vk::SamplerAddressMode::eClampToEdge)
1753 .build(device);
1754
1755 auto shadowPass =
1756 device.createRenderPass()
1757 .addSampledDepthAttachment(vk::Format::eD32Sfloat)
1758 .addSubpass(
1759 vkb::SubpassBuilder().setDepthStencilAttachment(0, vk::ImageLayout::eDepthStencilAttachmentOptimal))
1760 // Imported cascades start undefined and finish sampled. Match the
1761 // actual FrameGraph pass, including its single outgoing dependency.
1762 .addDependency(
1763 0, VK_SUBPASS_EXTERNAL,
1764 vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests |
1765 vk::PipelineStageFlagBits::eLateFragmentTests,
1766 vk::PipelineStageFlagBits::eVertexShader | vk::PipelineStageFlagBits::eFragmentShader,
1767 vk::AccessFlagBits::eColorAttachmentWrite | vk::AccessFlagBits::eDepthStencilAttachmentWrite,
1768 vk::AccessFlagBits::eShaderRead)
1769 .build();
1770 shadowRenderPass = shadowPass;
1771
1772 for (auto& slot : shadowMaps) {
1773 for (uint32_t i = 0; i < layers; ++i) {
1774 slot.framebuffers[i] =
1775 shadowPass.createFramebuffer(device, size, size, {slot.image.layerAttachment(i)});
1776 }
1777 }
1778
1779 shadowPipelineLayout =
1780 device.createPipelineLayout()
1781 .push<glm::mat4>(vk::ShaderStageFlagBits::eVertex)
1782 .build();
1783
1784 std::vector<uint32_t> vert(mesh3d_shadow_vert_spv,
1785 mesh3d_shadow_vert_spv + mesh3d_shadow_vert_spv_count);
1786 std::vector<uint32_t> frag(mesh3d_shadow_frag_spv,
1787 mesh3d_shadow_frag_spv + mesh3d_shadow_frag_spv_count);
1788 vk::ShaderModule vertModule = vkb::PipelineBuilder::createShaderModule(device.instance, vert);
1789 vk::ShaderModule fragModule = vkb::PipelineBuilder::createShaderModule(device.instance, frag);
1790 shadowPipeline =
1791 device.createPipeline()
1792 .useClassicPipeline(vertModule, fragModule)
1793 .setPipelineLayout(shadowPipelineLayout)
1794 .setVertexInputState(vkb::VertexInputStateBuilder()
1795 .addInputBinding<MeshVertex>()
1796 .addAttributeDescription<MeshVertex>())
1797 .setDynamicStatesViewportScissor()
1798 // No cull: Cornell-style one-sided interiors keep writing when the
1799 // ceiling/walls are back-facing the sun. Closest depth still wins
1800 // on closed meshes, so floors are not punched through.
1801 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eNone,
1802 vk::FrontFace::eClockwise)
1803 .setDepthBias(0.0f, 0.5f)
1804 .build(shadowPass);
1805 shadowSingleSidedPipeline =
1806 device.createPipeline()
1807 .useClassicPipeline(vertModule, fragModule)
1808 .setPipelineLayout(shadowPipelineLayout)
1809 .setVertexInputState(vkb::VertexInputStateBuilder()
1810 .addInputBinding<MeshVertex>()
1811 .addAttributeDescription<MeshVertex>())
1812 .setDynamicStatesViewportScissor()
1813 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eBack,
1814 vk::FrontFace::eClockwise)
1815 .setDepthBias(0.0f, 0.5f)
1816 .build(shadowPass);
1817 device->destroyShaderModule(vertModule);
1818 device->destroyShaderModule(fragModule);
1819
1820 // Alpha-cutout variant for billboard/card shadow casters (sprite-stack
1821 // slices): same transform push constant, fragment samples the albedo and
1822 // discards transparent texels so shadows follow the silhouette.
1823 shadowAlphaPipelineLayout = device.createPipelineLayout()
1824 .set(texSetLayout)
1825 .push<ShadowAlphaPush>(vk::ShaderStageFlagBits::eVertex |
1826 vk::ShaderStageFlagBits::eFragment)
1827 .build();
1828 std::vector<uint32_t> alphaVert(mesh3d_shadow_alpha_vert_spv,
1829 mesh3d_shadow_alpha_vert_spv +
1830 mesh3d_shadow_alpha_vert_spv_count);
1831 std::vector<uint32_t> alphaFrag(mesh3d_shadow_alpha_frag_spv,
1832 mesh3d_shadow_alpha_frag_spv +
1833 mesh3d_shadow_alpha_frag_spv_count);
1834 vk::ShaderModule alphaVertModule =
1835 vkb::PipelineBuilder::createShaderModule(device.instance, alphaVert);
1836 vk::ShaderModule alphaFragModule =
1837 vkb::PipelineBuilder::createShaderModule(device.instance, alphaFrag);
1838 shadowAlphaPipeline =
1839 device.createPipeline()
1840 .useClassicPipeline(alphaVertModule, alphaFragModule)
1841 .setPipelineLayout(shadowAlphaPipelineLayout)
1842 .setVertexInputState(vkb::VertexInputStateBuilder()
1843 .addInputBinding<MeshVertex>()
1844 .addAttributeDescription<MeshVertex>())
1845 .setDynamicStatesViewportScissor()
1846 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eNone,
1847 vk::FrontFace::eClockwise)
1848 .setDepthBias(0.0f, 0.5f)
1849 .build(shadowPass);
1850 shadowAlphaSingleSidedPipeline =
1851 device.createPipeline()
1852 .useClassicPipeline(alphaVertModule, alphaFragModule)
1853 .setPipelineLayout(shadowAlphaPipelineLayout)
1854 .setVertexInputState(vkb::VertexInputStateBuilder()
1855 .addInputBinding<MeshVertex>()
1856 .addAttributeDescription<MeshVertex>())
1857 .setDynamicStatesViewportScissor()
1858 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eBack,
1859 vk::FrontFace::eClockwise)
1860 .setDepthBias(0.0f, 0.5f)
1861 .build(shadowPass);
1862 device->destroyShaderModule(alphaVertModule);
1863 device->destroyShaderModule(alphaFragModule);
1864
1865 auto skinVert = embeddedSpirv(mesh3d_shadow_skin_vert_spv);
1866 shadowSkinPipeline = device.createPipeline()
1867 .useClassicPipeline(skinVert, frag)
1868 .setPipelineLayout(skinPassPipelineLayout)
1869 .setVertexInputState(vkb::VertexInputStateBuilder()
1870 .addInputBinding<MeshVertex>()
1871 .addAttributeDescription<MeshVertex>())
1872 .setDynamicStatesViewportScissor()
1873 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f,
1874 vk::CullModeFlagBits::eNone,
1875 vk::FrontFace::eClockwise)
1876 .setDepthBias(0.0f, 0.5f)
1877 .build(shadowPass);
1878 shadowSkinSingleSidedPipeline = device.createPipeline()
1879 .useClassicPipeline(skinVert, frag)
1880 .setPipelineLayout(skinPassPipelineLayout)
1881 .setVertexInputState(vkb::VertexInputStateBuilder()
1882 .addInputBinding<MeshVertex>()
1883 .addAttributeDescription<MeshVertex>())
1884 .setDynamicStatesViewportScissor()
1885 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f,
1886 vk::CullModeFlagBits::eBack,
1887 vk::FrontFace::eClockwise)
1888 .setDepthBias(0.0f, 0.5f)
1889 .build(shadowPass);
1890 auto skinAlphaFrag = embeddedSpirv(mesh3d_shadow_skin_alpha_frag_spv);
1891 shadowSkinAlphaPipeline = device.createPipeline()
1892 .useClassicPipeline(skinVert, skinAlphaFrag)
1893 .setPipelineLayout(skinPassPipelineLayout)
1894 .setVertexInputState(vkb::VertexInputStateBuilder()
1895 .addInputBinding<MeshVertex>()
1896 .addAttributeDescription<MeshVertex>())
1897 .setDynamicStatesViewportScissor()
1898 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f,
1899 vk::CullModeFlagBits::eNone,
1900 vk::FrontFace::eClockwise)
1901 .setDepthBias(0.0f, 0.5f)
1902 .build(shadowPass);
1903 shadowSkinAlphaSingleSidedPipeline = device.createPipeline()
1904 .useClassicPipeline(skinVert, skinAlphaFrag)
1905 .setPipelineLayout(skinPassPipelineLayout)
1906 .setVertexInputState(vkb::VertexInputStateBuilder()
1907 .addInputBinding<MeshVertex>()
1908 .addAttributeDescription<MeshVertex>())
1909 .setDynamicStatesViewportScissor()
1910 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f,
1911 vk::CullModeFlagBits::eBack,
1912 vk::FrontFace::eClockwise)
1913 .setDepthBias(0.0f, 0.5f)
1914 .build(shadowPass);
1915
1916 // Clear every ping-pong copy so sampling before the first real shadow pass
1917 // sees SHADER_READ_ONLY rather than UNDEFINED.
1918 vkb::executeImmediately(device.instance, uploadPool, device.getQueue(vkb::QueueType::graphics),
1919 [&](vk::CommandBuffer cb) {
1920 vk::ClearValue clear{};
1921 clear.depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
1922 for (auto &slot : shadowMaps) {
1923 slot.image.beginDepthAttachment();
1924 for (int c = 0; c < ShadowConfig::kTotalLayers; ++c) {
1925 vk::RenderPassBeginInfo rpBegin{};
1926 rpBegin.renderPass = shadowRenderPass;
1927 rpBegin.framebuffer = slot.framebuffers[c];
1928 rpBegin.renderArea = vk::Rect2D{{0, 0}, {size, size}};
1929 rpBegin.clearValueCount = 1;
1930 rpBegin.pClearValues = &clear;
1931 cb.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
1932 cb.endRenderPass();
1933 }
1934 slot.image.endSampledLayout();
1935 }
1936 });
1937}
1938
1939void Graphics::ensureClusteredBuffers(size_t lightsBytes, size_t tableBytes, size_t indicesBytes) {
1940 auto &st = currentClusteredStorage();
1941 auto ensure = [&](vkb::GenericBuffer &buf, size_t &cap, size_t need) {
1942 if (need == 0) need = 4;
1943 if (cap >= need && buf.buffer) return;
1944 buf.release();
1945 // Grow with some slack.
1946 size_t alloc = std::max(need, cap ? cap * 2 : need);
1947 buf.allocate(frameToken(), device, vk::BufferUsageFlagBits::eStorageBuffer, vk::DeviceSize(alloc),
1948 vk::MemoryPropertyFlagBits::eHostVisible |
1949 vk::MemoryPropertyFlagBits::eHostCoherent);
1950 cap = alloc;
1951 };
1952 ensure(st.lightsBuf, st.lightsCap, lightsBytes);
1953 ensure(st.tableBuf, st.tableCap, tableBytes);
1954 ensure(st.indicesBuf, st.indicesCap, indicesBytes);
1955 // Reallocated handles invalidate this frame's cached descriptor sets; drop
1956 // them so mesh3dClusteredSetFor rebinds against the new buffers.
1957 currentMesh3dClusteredFrameSlots().sets.clear();
1958}
1959
1960void Graphics::uploadClusteredLighting(const ClusteredLightingUpload &upload) {
1961 const size_t lightsBytes =
1962 std::max(size_t(1), upload.lights.size()) * sizeof(ClusteredLightGpu);
1963 const size_t tableBytes =
1964 std::max(size_t(1), upload.clusterTable.size()) * sizeof(ClusterTableEntry);
1965 const size_t indicesBytes =
1966 std::max(size_t(1), upload.lightIndices.size()) * sizeof(uint32_t);
1967 ensureClusteredBuffers(lightsBytes, tableBytes, indicesBytes);
1968
1969 auto &st = currentClusteredStorage();
1970 if (!upload.lights.empty())
1971 st.lightsBuf.updateLocal(frameToken(), upload.lights.data(),
1972 upload.lights.size() * sizeof(ClusteredLightGpu));
1973 else {
1974 ClusteredLightGpu zero{};
1975 st.lightsBuf.updateLocal(frameToken(), &zero, sizeof(zero));
1976 }
1977 st.tableBuf.updateLocal(frameToken(), upload.clusterTable.data(),
1978 upload.clusterTable.size() * sizeof(ClusterTableEntry));
1979 st.indicesBuf.updateLocal(frameToken(), upload.lightIndices.data(),
1980 upload.lightIndices.size() * sizeof(uint32_t));
1981}
1982
1984 mesh3dClusteredActive = upload.active;
1985 mesh3dClustered = upload;
1986 if (upload.active) {
1987 createMesh3DClusteredPipeline();
1988 uploadClusteredLighting(upload);
1989 }
1990}
1991
1993 mesh3dClusteredActive = active;
1994 if (active) createMesh3DClusteredPipeline();
1995}
1996
1997void Graphics::ensureMesh3dStrides() {
1998 if (mesh3dUboStride != 0) return;
1999 const uint32_t align = std::max(
2000 1u, uint32_t(device.physical_device.properties.limits.minUniformBufferOffsetAlignment));
2001 mesh3dUboStride = alignUpValue(uint32_t(sizeof(Mesh3DUBO)), align);
2002 shadowUboStride = alignUpValue(uint32_t(sizeof(ShadowUBO)), align);
2003 mesh3dClusteredUboStride = alignUpValue(uint32_t(sizeof(Mesh3DClusteredUBO)), align);
2004}
2005
2006void Graphics::ensureMesh3dRing(Mesh3dFrameSlots &fslots) {
2007 ensureMesh3dStrides();
2008 const size_t want = std::max<size_t>(2048, fslots.lastDrawCount + 512);
2009 if (fslots.uboRing.buffer && fslots.capacity >= want) return;
2010 const size_t cap = std::max(want, fslots.capacity * 2);
2011 // Only called at frame start (before any draw of this frame is recorded),
2012 // so releasing the old ring is safe: no in-flight reader of this slot.
2013 fslots.uboRing.release();
2014 fslots.uboRing.allocate(frameToken(), device, vk::BufferUsageFlagBits::eUniformBuffer,
2015 vk::DeviceSize(cap) * mesh3dUboStride, kHostVisibleCoherent);
2016 fslots.shadowRing.release();
2017 fslots.shadowRing.allocate(frameToken(), device, vk::BufferUsageFlagBits::eUniformBuffer,
2018 vk::DeviceSize(cap) * shadowUboStride, kHostVisibleCoherent);
2019 fslots.capacity = cap;
2020 fslots.sets.clear(); // cached sets reference the old rings
2021 fslots.skinSets.clear();
2022}
2023
2024void Graphics::ensureMesh3dClusteredRing(Mesh3dClusteredFrameSlots &fslots) {
2025 ensureMesh3dStrides();
2026 const size_t want = std::max<size_t>(2048, fslots.lastDrawCount + 512);
2027 if (fslots.uboRing.buffer && fslots.capacity >= want) return;
2028 const size_t cap = std::max(want, fslots.capacity * 2);
2029 fslots.uboRing.release();
2030 fslots.uboRing.allocate(frameToken(), device, vk::BufferUsageFlagBits::eUniformBuffer,
2031 vk::DeviceSize(cap) * mesh3dClusteredUboStride, kHostVisibleCoherent);
2032 fslots.shadowRing.release();
2033 fslots.shadowRing.allocate(frameToken(), device, vk::BufferUsageFlagBits::eUniformBuffer,
2034 vk::DeviceSize(cap) * shadowUboStride, kHostVisibleCoherent);
2035 fslots.capacity = cap;
2036 fslots.sets.clear();
2037}
2038
2039vkb::BoundSet Graphics::mesh3dClusteredSetFor(GpuTexture *gpuTex, GpuTexture *normalTex,
2040 GpuTexture *envTex, GpuTexture *heightTex,
2041 GpuTexture *decalAlbedo, GpuTexture *decalNormal,
2042 GpuTexture *decalParams,
2043 Mesh3dClusteredFrameSlots &fslots) {
2044 ASSERT(gpuTex != nullptr);
2045 ASSERT(normalTex != nullptr);
2046 ASSERT(envTex != nullptr);
2047 ASSERT(heightTex != nullptr);
2048 ASSERT(decalAlbedo != nullptr);
2049 ASSERT(decalNormal != nullptr);
2050 ASSERT(decalParams != nullptr);
2051 ASSERT(currentShadowArrayView());
2052 ASSERT(fslots.uboRing.buffer);
2053 ASSERT(fslots.shadowRing.buffer);
2054
2055 auto probeTexture = [&](int index) -> GpuTexture * {
2056 if (index < mesh3dReflectionProbes.count) {
2057 Texture *texture = mesh3dReflectionProbes.probes[index].cubemap;
2058 if (texture && texture->gpuHandle) {
2059 auto *gpu = static_cast<GpuTexture *>(texture->gpuHandle);
2060 if (gpu->isCube) return gpu;
2061 }
2062 }
2063 return static_cast<GpuTexture *>(defaultEnvCubemap->gpuHandle);
2064 };
2065 GpuTexture *probe0 = probeTexture(0);
2066 GpuTexture *probe1 = probeTexture(1);
2067 Mesh3dSetKey key{gpuTex, normalTex, envTex, probe0, probe1, heightTex, nullptr, nullptr,
2068 decalAlbedo, decalNormal, decalParams};
2069 auto it = fslots.sets.find(key);
2070 if (it != fslots.sets.end()) return it->second;
2071
2072 vk::DescriptorSetAllocateInfo alloc{};
2073 alloc.descriptorPool = descriptorPool;
2074 alloc.descriptorSetCount = 1;
2075 alloc.pSetLayouts = &mesh3dClusteredSetLayout;
2076 vkb::UnboundSet unbound{device->allocateDescriptorSets(alloc).front()};
2077
2078 auto &st = currentClusteredStorage();
2079 vkb::DescriptorSetUpdater updater(17, 17, 0);
2080 updater.beginDescriptorSet(unbound)
2081 .beginBuffers(0, 0, vk::DescriptorType::eUniformBufferDynamic)
2082 .buffer(fslots.uboRing.buffer, 0, sizeof(Mesh3DClusteredUBO))
2083 .beginImages(1, 0, vk::DescriptorType::eCombinedImageSampler)
2084 .image(vkb::SampledImage::forLaterSample(gpuTex->sampler, gpuTex->imageView()))
2085 .beginImages(2, 0, vk::DescriptorType::eCombinedImageSampler)
2086 .image(vkb::SampledImage::forLaterSample(normalTex->sampler, normalTex->imageView()))
2087 .beginImages(3, 0, vk::DescriptorType::eCombinedImageSampler)
2088 .image(vkb::SampledImage::forLaterSample(envTex->sampler, envTex->imageView()))
2089 .beginBuffers(4, 0, vk::DescriptorType::eStorageBuffer)
2090 .buffer(st.lightsBuf.buffer, 0, vk::DeviceSize(st.lightsCap))
2091 .beginBuffers(5, 0, vk::DescriptorType::eStorageBuffer)
2092 .buffer(st.tableBuf.buffer, 0, vk::DeviceSize(st.tableCap))
2093 .beginBuffers(6, 0, vk::DescriptorType::eStorageBuffer)
2094 .buffer(st.indicesBuf.buffer, 0, vk::DeviceSize(st.indicesCap))
2095 .beginBuffers(7, 0, vk::DescriptorType::eUniformBufferDynamic)
2096 .buffer(fslots.shadowRing.buffer, 0, sizeof(ShadowUBO))
2097 .beginImages(8, 0, vk::DescriptorType::eCombinedImageSampler)
2098 .image(vkb::SampledImage::forLaterSample(shadowSampler, currentShadowArrayView()))
2099 .beginImages(9, 0, vk::DescriptorType::eCombinedImageSampler)
2100 .image(vkb::SampledImage::forLaterSample(heightTex->sampler, heightTex->imageView()))
2101 .beginImages(10, 0, vk::DescriptorType::eCombinedImageSampler)
2102 .image(vkb::SampledImage::forLaterSample(decalAlbedo->sampler, decalAlbedo->imageView()))
2103 .beginImages(11, 0, vk::DescriptorType::eCombinedImageSampler)
2104 .image(vkb::SampledImage::forLaterSample(decalNormal->sampler, decalNormal->imageView()))
2105 .beginImages(12, 0, vk::DescriptorType::eCombinedImageSampler)
2106 .image(vkb::SampledImage::forLaterSample(decalParams->sampler, decalParams->imageView()))
2107 .beginImages(18, 0, vk::DescriptorType::eCombinedImageSampler)
2108 .image(vkb::SampledImage::forLaterSample(probe0->sampler, probe0->imageView()))
2109 .beginImages(19, 0, vk::DescriptorType::eCombinedImageSampler)
2110 .image(vkb::SampledImage::forLaterSample(probe1->sampler, probe1->imageView()))
2111 .beginImages(20, 0, vk::DescriptorType::eCombinedImageSampler)
2112 .image(vkb::SampledImage::forLaterSample(shadowRawSampler, currentShadowArrayView()))
2113 .update(device.instance);
2114
2115 vkb::BoundSet bound = std::move(unbound).publish();
2116 fslots.sets.emplace(key, bound);
2117 return bound;
2118}
2119
2120size_t Graphics::mesh3dPipelineIndex(BlendMode blend, bool depthWrite, bool doubleSided) {
2121 return size_t(blend) * 4u + (depthWrite ? 2u : 0u) + (doubleSided ? 1u : 0u);
2122}
2123
2124vk::Pipeline Graphics::createMesh3DHairPipeline(const std::vector<uint32_t> &vert,
2125 const std::vector<uint32_t> &frag,
2126 vk::PipelineLayout layout,
2127 const vkb::BuiltRenderPass &rp,
2128 vk::SampleCountFlagBits samples) {
2129 vk::ShaderModule vertModule = vkb::PipelineBuilder::createShaderModule(device.instance, vert);
2130 vk::ShaderModule fragModule = vkb::PipelineBuilder::createShaderModule(device.instance, frag);
2131 vk::Pipeline pipe =
2132 device.createPipeline()
2133 .useClassicPipeline(vertModule, fragModule)
2134 .setPipelineLayout(layout)
2135 .setVertexInputState(vkb::VertexInputStateBuilder()
2136 .addInputBinding<MeshVertex>()
2137 .addAttributeDescription<MeshVertex>())
2138 .setDynamicStatesViewportScissor()
2139 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eNone,
2140 vk::FrontFace::eClockwise)
2141 .setDepthBias(-1.5f, -1.0f)
2142 .setMultisampler(false, samples)
2143 .setDepthStencil(true, false, vk::CompareOp::eLess)
2144 .setAlphaBlending(1)
2145 .build(rp);
2146 device->destroyShaderModule(vertModule);
2147 device->destroyShaderModule(fragModule);
2148 return pipe;
2149}
2150
2151vk::Pipeline Graphics::createMesh3DXrayPipeline(const std::vector<uint32_t> &vert,
2152 const std::vector<uint32_t> &frag,
2153 vk::PipelineLayout layout,
2154 const vkb::BuiltRenderPass &rp,
2155 vk::SampleCountFlagBits samples) {
2156 vk::ShaderModule vertModule = vkb::PipelineBuilder::createShaderModule(device.instance, vert);
2157 vk::ShaderModule fragModule = vkb::PipelineBuilder::createShaderModule(device.instance, frag);
2158 vk::Pipeline pipe =
2159 device.createPipeline()
2160 .useClassicPipeline(vertModule, fragModule)
2161 .setPipelineLayout(layout)
2162 .setVertexInputState(vkb::VertexInputStateBuilder()
2163 .addInputBinding<MeshVertex>()
2164 .addAttributeDescription<MeshVertex>())
2165 .setDynamicStatesViewportScissor()
2166 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eNone,
2167 vk::FrontFace::eClockwise)
2168 .setMultisampler(false, samples)
2169 // Depth test/write off: the shader discards visible fragments itself
2170 // (sampling G-buffer scene depth), so occluded ones can paint over walls.
2171 .setDepthStencil(false, false, vk::CompareOp::eLess)
2172 .setAlphaBlending(1)
2173 .setColorAttachmentCount(1)
2174 .build(rp);
2175 device->destroyShaderModule(vertModule);
2176 device->destroyShaderModule(fragModule);
2177 return pipe;
2178}
2179
2180Texture *Graphics::getTexture() { return nullptr; }
2181
2182
2183} // namespace eve::graphics::vulkan
LogicalId target
bool & active
float w
Definition AnimClip.cpp:738
float uv
graphics::Texture * albedo
glm::vec4 p[6]
std::string layout
tensor::Graph g
Definition GpuGraph.cpp:7
vkb::Device & device
vk::ShaderModule vert
vk::ShaderModule frag
std::uint32_t key
vk::UniqueImage image
glm::vec4 tint
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::int32_t c
float blend
int h
bool doubleSided
Texture * normal
int idx
float d
float t
Shader * shader
glm::mat4 view
float size
Definition TreeMesh.cpp:156
uint32_t index
std::uint32_t depth
virtual Texture * getTexture()=0
Sampleable color buffer; screen Canvas returns nullptr.
Texture * prepareFinalSceneTexture(Texture *scene, Texture *motion=nullptr)
Build the shared linear-HDR AA, DOF, bloom, and exposure result for final ACES. @lifetime Returned te...
virtual void setMesh3DClusteredActive(bool active)=0
Cheap per-draw toggle for the already-uploaded clustered light table. Unlike setMesh3DClusteredLighti...
virtual bool isDisplayHdrActive() const
True when the live swapchain is scRGB or HDR10.
virtual void setMesh3DClusteredLighting(const ClusteredLightingUpload &upload)=0
Enable clustered forward path for subsequent default mesh draws (SSBO light lists)....
SceneToneMapping
Final display mapping; None preserves linear color, Aces uses the fitted curve, and Filmic uses Rec....
Definition Graphics.h:1325
std::unique_ptr< RenderControl > renderControl_
Definition Graphics.h:2264
DisplayColorSpace activeDisplayColorSpace_
Definition Graphics.h:2254
PresentOverlayFn presentOverlayFn_
Definition Graphics.h:2259
DisplayColorSpace
Color space actually selected for the live swapchain.
Definition Graphics.h:1329
Texture * takeFinalSceneTexture()
Consume and clear the per-frame HDR scene resolve override. @lifetime Returned texture is borrowed an...
Definition Graphics.h:2073
Canvas * pipelineReflectionComposite(int width, int height)
Return a reusable HDR target for composing screen-space lighting. @lifetime Returned canvas is Graphi...
static constexpr uint32_t kPushConstantBytes
Definition Shader.h:43
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:18
float getScenePhotographicVignette() const override
Read the current render-thread photographic vignette.
Definition Graphics.h:858
Texture * getSceneColorTexture() override
Returns the scene color texture.
Definition Graphics.cpp:961
Mesh * newMeshFromArrays(const float *posXYZ, const float *nrmXYZ, const float *uvST, int vertexCount, const uint32_t *indices, int indexCount) override
Creates a mesh from arrays. @ownership Caller deletes unless documented otherwise.
SceneToneMapping getSceneToneMapping() const override
Returns the scene tone mapping.
Definition Graphics.h:848
void initGpuTiming()
Creates the GPU timestamp query pool; call after device init.
void clear(std::optional< Color > color, std::optional< int > stencil, std::optional< double > depth) override
Clears .
std::vector< ParamSpec > params
Color sceneResolveTint(bool aces, ActiveColorSpace space, bool attachmentEncodesSrgb, float paperWhiteNits, float peakNits) noexcept
Build the Color tint used by the final scene present resolve.
ActiveColorSpace
Active present color encoding after swapchain selection.
Color sceneComposeTint(bool aces, float paperWhiteNits, float peakNits) noexcept
Build the Color tint for tonemapping into a linear HDR compose target.
vk::Pipeline createSolidColorPipeline(vkb::Device &device, const vkb::BuiltRenderPass &renderPass, vk::PipelineLayout layout, BlendMode mode)
Internal synchronous pipeline factory; caller destroys the returned Vulkan pipeline.
PrimitiveCullMode
Face culling used by filled 3D primitives.
eve::Color Color
RGBA color used by every graphics draw call. Lives inside eve::graphics so including a graphics heade...
Definition Color.h:13
Light3DGpu ClusteredLightGpu
eve::BlendMode BlendMode
Compatibility alias for the shared 2D blend mode.
Definition BlendMode.h:8
PrimitiveDepthMode
Depth behavior of one 3D primitive.
CompareOp
CompareOp public API.
Definition NpcAi.h:27
CPU-built clustered lighting upload for one frame/camera. Point lights are clustered; directional lig...
static constexpr int kMapSize
Definition Shadow.h:28
static constexpr int kTotalLayers
Definition Shadow.h:27
Per-frame / per-draw CSM + local-spot constants (std140).
Definition Shadow.h:36
Mesh3DClusteredUBO public API.
Definition Graphics.h:235
Mesh3DUBO public API.
Definition Graphics.h:174
glm::vec4 color