16#include <SDL2/SDL_vulkan.h>
34#include "common/config.h"
38#include "zeroerr/assert.h"
43#include <assimp/mesh.h>
44#include <assimp/matrix3x3.h>
45#include <assimp/matrix4x4.h>
46#include <assimp/vector3.h>
47#include <glm/gtc/matrix_transform.hpp>
49#include "graphics/shaders/mesh3d_vert_spv.inc"
50#include "graphics/shaders/mesh3d_frag_spv.inc"
51#include "graphics/shaders/voxel_rect_vert_spv.inc"
52#include "graphics/shaders/voxel_rect_frag_spv.inc"
61 if (!initialized)
throw Exception(
"begin3DFrame: graphics not initialized");
63 if (swapchainPassOpen) {
73 if (!beginPresentCommandBuffer())
75 recordDeferredFrameGraph();
79 createSceneColorResources(
int(swapchain.extent.width),
int(swapchain.extent.height));
80 if (beginSceneColorRenderPass()) {
85 ensureScenePassPipelines(activeScenePass(), activeSceneSamples());
89 ensureScenePassPipelines(renderpass, vk::SampleCountFlagBits::e1);
90 beginSwapchainColorPass();
93 auto &cb = currentPresentCb();
94 setViewportAndScissor(cb, swapchain.extent.width, swapchain.extent.height);
97 auto &fslots = currentMesh3dFrameSlots();
98 fslots.lastDrawCount = fslots.drawIndex;
100 ensureMesh3dRing(fslots);
101 auto &cslots = currentMesh3dClusteredFrameSlots();
102 cslots.lastDrawCount = cslots.drawIndex;
103 cslots.drawIndex = 0;
104 ensureMesh3dClusteredRing(cslots);
106 lastMesh3dPipeline =
nullptr;
107 lastMesh3dClusteredPipeline =
nullptr;
108 currentVoxelInstanceFrame().drawIndex = 0;
109 swapchainPassOpen =
true;
111 hasPendingClear =
false;
114void Graphics::ensureOffscreen3DResources() {
116 if (!offscreen3DRenderPass) {
117 offscreen3DRenderPass =
118 device.createRenderPass()
119 .addSampledColorAttachment(vk::Format::eR8G8B8A8Unorm)
120 .addDepthAttachment(depthFormat, vk::AttachmentLoadOp::eClear,
121 vk::AttachmentStoreOp::eDontCare)
122 .addSubpass(vkb::SubpassBuilder()
123 .addAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal)
124 .setDepthStencilAttachment(
125 1, vk::ImageLayout::eDepthStencilAttachmentOptimal))
126 .addExternalShaderReadDependencies()
128 offscreen3DMeshPipeline = createMesh3DStylePipeline(
129 embeddedSpirv(mesh3d_vert_spv), embeddedSpirv(mesh3d_frag_spv), mesh3dPipelineLayout,
130 offscreen3DRenderPass, vk::SampleCountFlagBits::e1);
132 if (!offscreen3DPool) {
133 vk::CommandPoolCreateInfo poolInfo{};
134 poolInfo.flags = vk::CommandPoolCreateFlagBits::eTransient;
135 offscreen3DPool = device->createCommandPool(poolInfo);
136 vk::CommandBufferAllocateInfo allocInfo{};
137 allocInfo.commandPool = offscreen3DPool;
138 allocInfo.level = vk::CommandBufferLevel::ePrimary;
139 allocInfo.commandBufferCount = 1;
140 auto bufs = device->allocateCommandBuffers(allocInfo);
141 offscreen3DCB = bufs[0];
142 vk::FenceCreateInfo fenceInfo{};
143 fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled;
144 offscreen3DFence = device->createFence(fenceInfo);
148void Graphics::destroyOffscreen3DResources() {
150 if (offscreen3DFence) {
151 device->destroyFence(offscreen3DFence);
152 offscreen3DFence =
nullptr;
154 if (offscreen3DPool) {
155 device->destroyCommandPool(offscreen3DPool);
156 offscreen3DPool =
nullptr;
158 offscreen3DCB =
nullptr;
159 if (offscreen3DMeshPipeline) {
160 device->destroyPipeline(offscreen3DMeshPipeline);
161 offscreen3DMeshPipeline =
nullptr;
163 if (offscreen3DRenderPass) {
164 device->destroyRenderPass(offscreen3DRenderPass);
165 offscreen3DRenderPass = {};
171 if (!initialized)
throw Exception(
"begin3DFrameToCanvas: graphics not initialized");
172 if (!canvas)
throw Exception(
"begin3DFrameToCanvas: null canvas");
174 if (!oc)
throw Exception(
"begin3DFrameToCanvas: not an offscreen canvas");
175 if (offscreen3DPassOpen)
throw Exception(
"begin3DFrameToCanvas: already open");
176 if (swapchainPassOpen) {
184 ensureOffscreen3DResources();
186 offscreen3DCanvas = oc;
188 if (offscreen3DFence) (void)device->waitForFences(1, &offscreen3DFence, VK_TRUE, UINT64_MAX);
189 vk::CommandBufferBeginInfo beginInfo{};
190 offscreen3DCB.begin(beginInfo);
191 std::array<vk::ClearValue, 2> clears{};
193 vk::ClearColorValue(std::array<float, 4>{clearColor.r, clearColor.g, clearColor.b, 1.f});
194 clears[1].depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
195 vk::RenderPassBeginInfo rpBegin{};
196 rpBegin.renderPass = offscreen3DRenderPass;
197 rpBegin.framebuffer = oc->framebuffer3D();
199 vk::Rect2D{{0, 0}, {uint32_t(oc->getWidth()), uint32_t(oc->getHeight())}};
200 rpBegin.clearValueCount = uint32_t(clears.size());
201 rpBegin.pClearValues = clears.data();
202 oc->colorImage().beginColorAttachment();
203 oc->depthImage().beginDepthAttachment();
204 offscreen3DCB.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
205 setViewportAndScissor(offscreen3DCB, oc->getWidth(), oc->getHeight());
208 auto &fslots = currentMesh3dFrameSlots();
209 fslots.lastDrawCount = fslots.drawIndex;
210 fslots.drawIndex = 0;
211 ensureMesh3dRing(fslots);
212 auto &cslots = currentMesh3dClusteredFrameSlots();
213 cslots.lastDrawCount = cslots.drawIndex;
214 cslots.drawIndex = 0;
215 ensureMesh3dClusteredRing(cslots);
217 lastMesh3dPipeline =
nullptr;
218 lastMesh3dClusteredPipeline =
nullptr;
219 offscreen3DPassOpen =
true;
221 hasPendingClear =
false;
225 if (!offscreen3DPassOpen || !offscreen3DCB)
return;
226 offscreen3DCB.endRenderPass();
227 if (offscreen3DCanvas) {
228 offscreen3DCanvas->
colorImage().setLayout(offscreen3DCB,
229 vk::ImageLayout::eShaderReadOnlyOptimal);
236 vk::SubmitInfo submitInfo{};
237 submitInfo.commandBufferCount = 1;
238 submitInfo.pCommandBuffers = &offscreen3DCB;
239 if (offscreen3DFence) (void)device->resetFences(1, &offscreen3DFence);
240 (void)device.getQueue(vkb::QueueType::graphics).submit(1, &submitInfo, offscreen3DFence);
241 if (offscreen3DFence)
242 (void)device->waitForFences(1, &offscreen3DFence, VK_TRUE, UINT64_MAX);
244 offscreen3DPassOpen =
false;
245 offscreen3DCanvas =
nullptr;
249 glm::vec3
d = glm::normalize(
dir);
250 if (glm::length(
d) < 1e-6f)
d = glm::vec3(0.f, 1.f, 0.f);
260void Graphics::destroyVoxelRectResources() {
261 for (
auto &frame : voxelInstanceFrames) {
262 for (
auto &slot : frame.slots) {
263 slot.buffer.release();
264 slot.capacityBytes = 0;
269 voxelInstanceFrames.clear();
270 voxelRectSets.clear();
271 auto destroyBuf = [&](vkb::GenericBuffer &
b) {
b.release(); };
272 if (voxelUnitQuadReady) {
273 destroyBuf(voxelUnitQuadVerts);
274 destroyBuf(voxelUnitQuadIndices);
275 voxelUnitQuadReady =
false;
277 if (voxelRectPipeline) {
278 device->destroyPipeline(voxelRectPipeline);
279 voxelRectPipeline = vk::Pipeline{};
281 if (voxelRectPipelineLayout) {
282 device->destroyPipelineLayout(voxelRectPipelineLayout);
283 voxelRectPipelineLayout = vk::PipelineLayout{};
285 voxelRectSetLayoutUnique.reset();
286 voxelRectSetLayout = vk::DescriptorSetLayout{};
289void Graphics::createVoxelRectPipeline() {
290 if (voxelRectPipeline)
return;
292 if (!voxelRectPipelineLayout) {
293 vkb::DescriptorSetLayoutBuilder layoutBuilder;
294 voxelRectSetLayoutUnique =
296 .image(0, vk::DescriptorType::eCombinedImageSampler,
297 vk::ShaderStageFlagBits::eFragment, 1)
298 .createUnique(device.instance);
299 voxelRectSetLayout = *voxelRectSetLayoutUnique;
301 const auto pcr = pushConstantRange(vk::ShaderStageFlagBits::eVertex,
sizeof(VoxelRectPC));
302 voxelRectPipelineLayout = createPipelineLayout(device, voxelRectSetLayout, &pcr);
305 voxelRectPipeline = buildVoxelRectPipeline(renderpass, vk::SampleCountFlagBits::e1);
306 ensureVoxelUnitQuad();
309vk::Pipeline Graphics::buildVoxelRectPipeline(
const vkb::BuiltRenderPass &rp,
310 vk::SampleCountFlagBits samples) {
311 auto vert = embeddedSpirv(voxel_rect_vert_spv);
312 auto frag = embeddedSpirv(voxel_rect_frag_spv);
313 ShaderModulePair modules(device,
vert,
frag);
315 vk::VertexInputBindingDescription bindings[3]{};
316 bindings[0].binding = 0;
317 bindings[0].stride =
sizeof(glm::vec2);
318 bindings[0].inputRate = vk::VertexInputRate::eVertex;
319 bindings[1].binding = 1;
320 bindings[1].stride =
sizeof(uint32_t);
321 bindings[1].inputRate = vk::VertexInputRate::eInstance;
322 bindings[2].binding = 2;
323 bindings[2].stride =
sizeof(uint32_t);
324 bindings[2].inputRate = vk::VertexInputRate::eInstance;
326 vk::VertexInputAttributeDescription attrs[3]{};
327 attrs[0].location = 0;
328 attrs[0].binding = 0;
329 attrs[0].format = vk::Format::eR32G32Sfloat;
331 attrs[1].location = 1;
332 attrs[1].binding = 1;
333 attrs[1].format = vk::Format::eR32Uint;
335 attrs[2].location = 2;
336 attrs[2].binding = 2;
337 attrs[2].format = vk::Format::eR32Uint;
340 vk::PipelineVertexInputStateCreateInfo vi{};
341 vi.vertexBindingDescriptionCount = 3;
342 vi.pVertexBindingDescriptions = bindings;
343 vi.vertexAttributeDescriptionCount = 3;
344 vi.pVertexAttributeDescriptions = attrs;
352 return device.createPipeline()
353 .useClassicPipeline(modules.vert, modules.frag)
354 .setPipelineLayout(voxelRectPipelineLayout)
355 .setVertexInputState(vi)
356 .setDynamicStatesViewportScissor()
357 .setRasterizer(vk::PolygonMode::eFill,
false,
false, 1.0f, vk::CullModeFlagBits::eBack,
358 vk::FrontFace::eCounterClockwise)
359 .setMultisampler(
false, samples)
360 .setDepthStencil(
true,
true, vk::CompareOp::eLess)
361 .setColorAttachmentCount(1)
365void Graphics::ensureVoxelUnitQuad() {
366 if (voxelUnitQuadReady)
return;
367 const float corners[8] = {0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f};
368 voxelUnitQuadVerts.allocate(frameToken(), device, vk::BufferUsageFlagBits::eVertexBuffer,
sizeof(corners),
369 vk::MemoryPropertyFlagBits::eHostVisible |
370 vk::MemoryPropertyFlagBits::eHostCoherent);
371 voxelUnitQuadVerts.updateLocal(frameToken(), corners,
sizeof(corners));
372 const uint32_t indices[6] = {0, 2, 1, 0, 3, 2};
373 voxelUnitQuadIndices.allocate(frameToken(), device, vk::BufferUsageFlagBits::eIndexBuffer,
sizeof(indices),
374 vk::MemoryPropertyFlagBits::eHostVisible |
375 vk::MemoryPropertyFlagBits::eHostCoherent);
376 voxelUnitQuadIndices.updateLocal(frameToken(), indices,
sizeof(indices));
377 voxelUnitQuadReady =
true;
380vkb::BoundSet Graphics::voxelRectSetFor(GpuTexture *gpuTex) {
381 ASSERT(gpuTex !=
nullptr);
382 auto it = voxelRectSets.find(gpuTex);
383 if (it != voxelRectSets.end())
return it->second;
385 vk::DescriptorSetAllocateInfo alloc{};
386 alloc.descriptorPool = descriptorPool;
387 alloc.descriptorSetCount = 1;
388 alloc.pSetLayouts = &voxelRectSetLayout;
389 vkb::UnboundSet unbound{device->allocateDescriptorSets(alloc).front()};
391 vkb::DescriptorSetUpdater updater(4, 4, 0);
392 updater.beginDescriptorSet(unbound)
393 .beginImages(0, 0, vk::DescriptorType::eCombinedImageSampler)
394 .image(vkb::SampledImage::forLaterSample(gpuTex->sampler, gpuTex->imageView()))
395 .update(device.instance);
397 vkb::BoundSet bound = std::move(unbound).publish();
398 voxelRectSets.emplace(gpuTex, bound);
403 float originY,
float originZ,
const std::string &faceDir,
404 Texture *atlas,
int tilesPerRow,
const uint32_t *ao) {
406 if (!initialized)
throw Exception(
"drawVoxelFaceInstances: graphics not initialized");
407 if (!swapchainPassOpen)
throw Exception(
"drawVoxelFaceInstances: call begin3DFrame first");
408 if (!voxelRectPipeline) createVoxelRectPipeline();
409 if (!packed || count <= 0)
return;
412 if (faceDir ==
"posX" || faceDir ==
"+x")
414 else if (faceDir ==
"negX" || faceDir ==
"-x")
416 else if (faceDir ==
"posY" || faceDir ==
"+y")
418 else if (faceDir ==
"negY" || faceDir ==
"-y")
420 else if (faceDir ==
"posZ" || faceDir ==
"+z")
422 else if (faceDir ==
"negZ" || faceDir ==
"-z")
425 throw Exception(
"drawVoxelFaceInstances: unknown faceDir '%s'", faceDir.c_str());
427 Texture *tex = atlas ? atlas : whiteTexture;
428 if (!tex || !tex->
gpuHandle)
throw Exception(
"drawVoxelFaceInstances: missing texture");
431 ensureVoxelUnitQuad();
433 const vk::DeviceSize bytes = vk::DeviceSize(count) *
sizeof(uint32_t);
434 auto &vframe = currentVoxelInstanceFrame();
435 const size_t slotIndex = vframe.drawIndex++;
436 while (vframe.slots.size() <= slotIndex) vframe.slots.emplace_back();
437 auto &slot = vframe.slots[slotIndex];
438 if (slot.capacityBytes <
size_t(bytes) || !slot.buffer.buffer) {
439 const size_t alloc = std::max(
size_t(bytes), slot.capacityBytes ? slot.capacityBytes * 2 : size_t(bytes));
440 slot.buffer.allocate(frameToken(), device, vk::BufferUsageFlagBits::eVertexBuffer, vk::DeviceSize(alloc),
441 vk::MemoryPropertyFlagBits::eHostVisible |
442 vk::MemoryPropertyFlagBits::eHostCoherent);
443 slot.capacityBytes = alloc;
445 slot.buffer.updateLocal(frameToken(), packed, size_t(bytes));
448 const uint32_t defaultAO = 0xFFu;
449 std::vector<uint32_t> aoDefaults;
451 aoDefaults.assign(
size_t(count), defaultAO);
452 ao = aoDefaults.data();
454 const vk::DeviceSize aoBytes = vk::DeviceSize(count) *
sizeof(uint32_t);
455 if (slot.aoCapacityBytes <
size_t(aoBytes) || !slot.aoBuffer.buffer) {
456 const size_t alloc = std::max(
size_t(aoBytes),
457 slot.aoCapacityBytes ? slot.aoCapacityBytes * 2
459 slot.aoBuffer.allocate(frameToken(), device, vk::BufferUsageFlagBits::eVertexBuffer,
460 vk::DeviceSize(alloc),
461 vk::MemoryPropertyFlagBits::eHostVisible |
462 vk::MemoryPropertyFlagBits::eHostCoherent);
463 slot.aoCapacityBytes = alloc;
465 slot.aoBuffer.updateLocal(frameToken(), ao, size_t(aoBytes));
468 pc.viewProj = mesh3dFrameUbo.
mvp;
469 pc.chunkOrigin = glm::vec4(originX, originY, originZ,
float(face));
470 pc.atlasInfo = glm::vec4(
float(std::max(1, tilesPerRow)), 0.f, 0.f, 0.f);
471 pc.tint = mesh3dFrameUbo.
tint;
473 auto &cb = currentPresentCb();
474 vk::DescriptorSet set = voxelRectSetFor(gpuTex);
475 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, voxelRectPipeline);
476 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, voxelRectPipelineLayout, 0, 1, &set, 0,
478 cb.pushConstants(voxelRectPipelineLayout, vk::ShaderStageFlagBits::eVertex, 0,
sizeof(VoxelRectPC),
481 vk::Buffer vbufs[3] = {voxelUnitQuadVerts.buffer, slot.buffer.buffer, slot.aoBuffer.buffer};
482 vk::DeviceSize offsets[3] = {0, 0, 0};
483 cb.bindVertexBuffers(0, 3, vbufs, offsets);
484 cb.bindIndexBuffer(voxelUnitQuadIndices.buffer, 0, vk::IndexType::eUint32);
485 cb.drawIndexed(6, uint32_t(count), 0, 0, 0);
495 mesh3dEnvTexture = cube;
496 mesh3dEnvIntensity = intensity < 0.f ? 0.f : intensity;
497 if (!cube) mesh3dEnvIntensity = 0.f;
506 if (!shadowPipeline) createShadowResources();
508 throw Exception(
"beginShadowPass: cascadeIndex out of range");
510 shadowPassCascade = cascadeIndex;
511 shadowPassDraws.clear();
515 if (shadowPassCascade < 0)
throw Exception(
"drawMeshShadow: call beginShadowPass first");
517 shadowPassDraws.push_back(ShadowDraw{
mesh, lightMVP});
521 if (shadowPassCascade < 0)
throw Exception(
"drawMeshShadowAlpha: call beginShadowPass first");
522 if (!
mesh || !
mesh->gpuHandle)
throw Exception(
"drawMeshShadowAlpha: null mesh");
528 shadowPassDraws.push_back(
d);
532 if (shadowPassCascade < 0)
throw Exception(
"endShadowPass: no active shadow pass");
533 if (!shadowPipeline || !shadowRenderPass) {
534 shadowPassCascade = -1;
535 shadowPassDraws.clear();
538 const int cascade = shadowPassCascade;
539 shadowCascadeDraws[cascade] = std::move(shadowPassDraws);
540 shadowPassDraws.clear();
541 shadowPassCascade = -1;
542 shadowPendingMask |= (1u << cascade);
550 if (!initialized)
throw Exception(
"beginGBufferPass: graphics not initialized");
552 if (!texSetLayout || !descriptorPool)
553 throw Exception(
"beginGBufferPass: textured descriptor layout not ready");
555 gbufferPassActive =
true;
556 gbufferPassDraws.clear();
560 float farZ,
Texture *
albedo,
float tintR,
float tintG,
float tintB) {
561 if (!gbufferPassActive)
throw Exception(
"drawMeshGBuffer: call beginGBufferPass first");
570 auto u8 = [](
float x) -> uint32_t {
571 return uint32_t(std::lround(std::clamp(
x, 0.f, 1.f) * 255.f));
573 const uint32_t packedTint = u8(tintR) | (u8(tintG) << 8) | (u8(tintB) << 16) | (255u << 24);
574 d.push.clip = glm::vec4(nearZ, farZ, glm::uintBitsToFloat(packedTint), 0.f);
575 gbufferPassDraws.push_back(
d);
580 float tintG,
float tintB) {
581 if (!gbufferPassActive)
throw Exception(
"drawMeshGBufferAlpha: call beginGBufferPass first");
582 if (!
mesh || !
mesh->gpuHandle)
throw Exception(
"drawMeshGBufferAlpha: null mesh");
591 auto u8 = [](
float x) -> uint32_t {
592 return uint32_t(std::lround(std::clamp(
x, 0.f, 1.f) * 255.f));
594 const uint32_t packedTint = u8(tintR) | (u8(tintG) << 8) | (u8(tintB) << 16) | (255u << 24);
595 d.push.clip = glm::vec4(nearZ, farZ, glm::uintBitsToFloat(packedTint), 0.f);
596 gbufferPassDraws.push_back(
d);
600 if (!gbufferPassActive)
throw Exception(
"endGBufferPass: no active gbuffer pass");
601 gbufferPassActive =
false;
603 auto *slot = currentGBufferSlot();
604 if (!gbufferPipeline || !gbufferRenderPass || !slot || !slot->framebuffer) {
605 gbufferPassDraws.clear();
606 gbufferPending =
false;
611 gbufferPending =
true;
614 renderControl_->getGBuffer()->setTargets(gbufferWidth, gbufferHeight, &slot->depthColorTex,
615 &slot->normalTex,
albedo, &slot->depthTex);
619void Graphics::ensureDefaultEnvCubemap() {
620 if (defaultEnvCubemap)
return;
621 const uint8_t black[4] = {0, 0, 0, 255};
622 std::vector<uint8_t> faces(6u * 4u);
623 for (
int i = 0; i < 6; ++i) {
624 faces[size_t(i) * 4u + 0] = black[0];
625 faces[size_t(i) * 4u + 1] = black[1];
626 faces[size_t(i) * 4u + 2] = black[2];
627 faces[size_t(i) * 4u + 3] = black[3];
629 defaultEnvCubemap =
newCubemap(1, faces.data());
640 mesh3dTexBombScale = cellScale > 1e-3f ? cellScale : 1e-3f;
641 mesh3dTexBombStrength = strength < 0.f ? 0.f : (strength > 1.f ? 1.f : strength);
642 mesh3dTexBombRot = rotAmount < 0.f ? 0.f : (rotAmount > 1.f ? 1.f : rotAmount);
644 glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot, 0.f);
648 mesh3dParallaxScale =
scale < 0.f ? 0.f : (
scale > 0.25f ? 0.25f :
scale);
649 float minL = minLayers < 1.f ? 1.f : minLayers;
650 float maxL = maxLayers < minL ? minL : maxLayers;
651 if (maxL > 64.f) maxL = 64.f;
652 mesh3dParallaxMinLayers = minL;
653 mesh3dParallaxMaxLayers = maxL;
655 glm::vec4(mesh3dParallaxScale, mesh3dParallaxMinLayers, mesh3dParallaxMaxLayers, 0.f);
659 mesh3dLighting = pack;
663 for (
int i = 0; i <
n; ++i) {
671 if (glm::length(
d) < 1e-6f)
d = glm::vec3(0.f, 1.f, 0.f);
672 else d = glm::normalize(
d);
673 mesh3dFrameUbo.
lightDir = glm::vec4(
d,
float(
n));
678 mesh3dFrameUbo.
lightDir = glm::vec4(0.f, 1.f, 0.f,
float(
n));
680 glm::vec4(0.f, 0.f, 0.f, mesh3dFrameUbo.
lightColor.w);
685 float windAngle,
float coverage,
float detail) {
686 mesh3dFrameUbo.
cloud = glm::vec4(std::clamp(strength, 0.f, 1.f), std::max(worldCell, 1e-4f),
688 mesh3dFrameUbo.
cloudWind = glm::vec4(std::cos(windAngle) * windSpeed,
689 std::sin(windAngle) * windSpeed,
690 std::clamp(coverage, 0.f, 1.f), std::clamp(detail, 0.f, 1.f));
693void Graphics::ensureFlatNormalTexture3D() {
694 if (flatNormalTexture3D)
return;
695 const uint8_t
px[4] = {128, 128, 255, 255};
699void Graphics::ensureFlatHeightTexture3D() {
700 if (flatHeightTexture3D)
return;
702 const uint8_t
px[4] = {128, 128, 128, 255};
706vkb::BoundSet Graphics::mesh3dSetFor(GpuTexture *gpuTex, GpuTexture *normalTex, GpuTexture *envTex,
707 GpuTexture *heightTex, GpuTexture *depthTex,
708 Mesh3dFrameSlots &fslots) {
709 ASSERT(gpuTex !=
nullptr);
710 ASSERT(normalTex !=
nullptr);
711 ASSERT(envTex !=
nullptr);
712 ASSERT(heightTex !=
nullptr);
713 ASSERT(currentShadowArrayView());
714 ASSERT(fslots.uboRing.buffer);
715 ASSERT(fslots.shadowRing.buffer);
717 Mesh3dSetKey key{gpuTex, normalTex, envTex, heightTex, depthTex};
718 auto it = fslots.sets.find(key);
719 if (it != fslots.sets.end())
return it->second;
721 vk::DescriptorSetAllocateInfo alloc{};
722 alloc.descriptorPool = descriptorPool;
723 alloc.descriptorSetCount = 1;
724 alloc.pSetLayouts = &mesh3dSetLayout;
725 vkb::UnboundSet unbound{device->allocateDescriptorSets(alloc).front()};
727 vkb::DescriptorSetUpdater updater(12, 12, 0);
728 updater.beginDescriptorSet(unbound)
729 .beginBuffers(0, 0, vk::DescriptorType::eUniformBufferDynamic)
730 .buffer(fslots.uboRing.buffer, 0, fslots.uboRing.size)
731 .beginImages(1, 0, vk::DescriptorType::eCombinedImageSampler)
732 .image(vkb::SampledImage::forLaterSample(gpuTex->sampler, gpuTex->imageView()))
733 .beginImages(2, 0, vk::DescriptorType::eCombinedImageSampler)
734 .image(vkb::SampledImage::forLaterSample(normalTex->sampler, normalTex->imageView()))
735 .beginImages(3, 0, vk::DescriptorType::eCombinedImageSampler)
736 .image(vkb::SampledImage::forLaterSample(envTex->sampler, envTex->imageView()))
737 .beginBuffers(4, 0, vk::DescriptorType::eUniformBufferDynamic)
738 .buffer(fslots.shadowRing.buffer, 0, fslots.shadowRing.size)
739 .beginImages(5, 0, vk::DescriptorType::eCombinedImageSampler)
740 .image(vkb::SampledImage::forLaterSample(shadowSampler, currentShadowArrayView()))
741 .beginImages(6, 0, vk::DescriptorType::eCombinedImageSampler)
742 .image(vkb::SampledImage::forLaterSample(heightTex->sampler, heightTex->imageView()))
743 .beginImages(7, 0, vk::DescriptorType::eCombinedImageSampler)
744 .image(vkb::SampledImage::forLaterSample(depthTex->sampler, depthTex->imageView()))
745 .update(device.instance);
747 vkb::BoundSet bound = std::move(unbound).publish();
748 fslots.sets.emplace(key, bound);
761 const float n = nearZ > 1e-4f ? nearZ : 0.1f;
762 const float f = farZ >
n ? farZ :
n + 1.f;
768vkb::FrameGraph *Graphics::currentDeferredFrameGraph() {
769 if (deferredFrameGraphs_[0] ==
nullptr)
return nullptr;
770 return deferredFrameGraphs_[currentFrameSlot() % deferredFrameGraphs_.size()].get();
773void Graphics::buildDeferredFrameGraphs() {
784 const vk::Format depthFmt = vk::Format::eD32Sfloat;
785 const vk::Format colorFmt = pickGBufferColorFormat(device);
788 const uint32_t
w = gbufferWidth > 0 ? uint32_t(gbufferWidth) : 1u;
789 const uint32_t
h = gbufferHeight > 0 ? uint32_t(gbufferHeight) : 1u;
791 for (
size_t i = 0; i < deferredFrameGraphs_.size(); ++i) {
792 auto graph = std::make_unique<vkb::FrameGraph>(&device, 1);
794 vkb::TextureDesc shadowDesc;
795 shadowDesc.format = depthFmt;
796 shadowDesc.extent = vk::Extent3D{mapSize, mapSize, 1};
797 shadowDesc.arrayLayers = shadowLayers;
798 shadowDesc.aspect = vk::ImageAspectFlagBits::eDepth;
799 shadowDesc.usage = vk::ImageUsageFlagBits::eSampled |
800 vk::ImageUsageFlagBits::eDepthStencilAttachment;
801 shadowDesc.afterLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal;
802 vk::ClearValue shadowClear{};
803 shadowClear.depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
804 const bool haveShadowSlot =
805 i < shadowMaps.size() && shadowMaps[i].image.layerCount() >= shadowLayers;
806 if (haveShadowSlot) {
807 const vk::Image shadowImage = shadowMaps[i].image.image();
808 for (uint32_t
c = 0;
c < shadowLayers; ++
c) {
809 auto shadowH =
graph->importTexture(
"shadowCascade" + std::to_string(
c),
810 shadowImage, shadowMaps[i].
image.layerView(
c),
812 graph->addPass(
"shadow" + std::to_string(
c))
813 .depthAttachment(shadowH, vkb::AttachmentOp::clear(shadowClear))
814 .record([
this,
c](vkb::FrameGraphPassContext &ctx) {
815 recordShadowCascadePass(ctx,
int(
c));
820 if (i < gbufferSlots.size() && gbufferWidth > 0 && gbufferHeight > 0) {
821 auto &slot = gbufferSlots[i];
822 vkb::TextureDesc colorDesc;
823 colorDesc.format = colorFmt;
824 colorDesc.extent = vk::Extent3D{
w,
h, 1};
825 colorDesc.usage = vk::ImageUsageFlagBits::eSampled |
826 vk::ImageUsageFlagBits::eColorAttachment |
827 vk::ImageUsageFlagBits::eTransferSrc;
828 colorDesc.afterLayout = vk::ImageLayout::eShaderReadOnlyOptimal;
829 auto normalH =
graph->importTexture(
"gbNormal", slot.normal.image(),
830 slot.normal.imageView(), colorDesc);
831 auto depthColorH =
graph->importTexture(
"gbDepthColor", slot.depthColor.image(),
832 slot.depthColor.imageView(), colorDesc);
833 auto albedoH =
graph->importTexture(
"gbAlbedo", slot.albedo.image(),
834 slot.albedo.imageView(), colorDesc);
836 vkb::TextureDesc depthDesc;
837 depthDesc.format = depthFmt;
838 depthDesc.extent = vk::Extent3D{
w,
h, 1};
839 depthDesc.aspect = vk::ImageAspectFlagBits::eDepth;
840 depthDesc.usage = vk::ImageUsageFlagBits::eSampled |
841 vk::ImageUsageFlagBits::eDepthStencilAttachment;
842 depthDesc.afterLayout = vk::ImageLayout::eDepthStencilReadOnlyOptimal;
843 auto depthH =
graph->importTexture(
"gbHwDepth", slot.depth.image(),
844 slot.depth.imageView(), depthDesc);
846 std::array<vk::ClearValue, 4> clears{};
847 clears[0].color = vk::ClearColorValue(std::array<float, 4>{0, 0, 0, 0});
848 clears[1].color = vk::ClearColorValue(std::array<float, 4>{1, 1, 1, 1});
849 clears[2].color = vk::ClearColorValue(std::array<float, 4>{0, 0, 0, 0});
850 clears[3].depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
851 graph->addPass(
"gbuffer")
852 .colorAttachment(normalH, vkb::AttachmentOp::clear(clears[0]))
853 .colorAttachment(depthColorH, vkb::AttachmentOp::clear(clears[1]))
854 .colorAttachment(albedoH, vkb::AttachmentOp::clear(clears[2]))
855 .depthAttachment(depthH, vkb::AttachmentOp::clear(clears[3]))
856 .record([
this](vkb::FrameGraphPassContext &ctx) { recordGBufferPassDraws(ctx); });
859 deferredFrameGraphs_[i] = std::move(
graph);
863void Graphics::recordShadowCascadePass(vkb::FrameGraphPassContext &ctx,
int cascade) {
869 auto &cb = ctx.commandBuffer();
870 const vk::Extent2D extent = ctx.extent();
872 setViewportAndScissor(cb, size, size);
873 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, shadowPipeline);
874 bool alphaBound =
false;
875 for (
const auto &
d : shadowCascadeDraws[cascade]) {
876 if (!
d.mesh || !
d.mesh->gpuHandle)
continue;
877 const bool wantAlpha =
d.alphaTest && shadowAlphaPipeline;
878 if (wantAlpha != alphaBound) {
879 cb.bindPipeline(vk::PipelineBindPoint::eGraphics,
880 wantAlpha ? shadowAlphaPipeline : shadowPipeline);
881 alphaBound = wantAlpha;
883 auto *gpuMesh =
static_cast<GpuMesh *
>(
d.mesh->gpuHandle);
885 Texture *alb =
d.albedo ?
d.albedo : whiteTexture;
886 if (alb && alb->gpuHandle && texSetLayout) {
887 auto *gpuTex =
static_cast<GpuTexture *
>(alb->gpuHandle);
888 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
889 shadowAlphaPipelineLayout, 0, 1,
890 gpuTex->descriptorSet.ptr(), 0,
nullptr);
893 cb.pushConstants(wantAlpha ? shadowAlphaPipelineLayout : shadowPipelineLayout,
894 vk::ShaderStageFlagBits::eVertex, 0, sizeof(glm::mat4), &
d.mvp);
895 drawIndexedMesh(cb, *gpuMesh);
899void Graphics::recordGBufferPassDraws(vkb::FrameGraphPassContext &ctx) {
904 auto &cb = ctx.commandBuffer();
905 const vk::Extent2D extent = ctx.extent();
906 const uint32_t
w = extent.width ? extent.width : uint32_t(gbufferWidth);
907 const uint32_t
h = extent.height ? extent.height : uint32_t(gbufferHeight);
908 setViewportAndScissor(cb,
w,
h);
909 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, gbufferPipeline);
910 bool alphaBound =
false;
911 for (
const auto &
d : gbufferPassDraws) {
912 if (!
d.mesh || !
d.mesh->gpuHandle)
continue;
913 const bool wantAlpha =
d.alphaTest && gbufferAlphaPipeline;
914 if (wantAlpha != alphaBound) {
915 cb.bindPipeline(vk::PipelineBindPoint::eGraphics,
916 wantAlpha ? gbufferAlphaPipeline : gbufferPipeline);
917 alphaBound = wantAlpha;
919 auto *gpuMesh =
static_cast<GpuMesh *
>(
d.mesh->gpuHandle);
920 Texture *alb =
d.albedo ?
d.albedo : whiteTexture;
921 if (alb && alb->gpuHandle && texSetLayout) {
922 auto *gpuTex =
static_cast<GpuTexture *
>(alb->gpuHandle);
923 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, gbufferPipelineLayout, 0, 1,
924 gpuTex->descriptorSet.ptr(), 0,
nullptr);
926 cb.pushConstants(gbufferPipelineLayout,
927 vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0,
928 sizeof(GBufferPush), &
d.push);
929 drawIndexedMesh(cb, *gpuMesh);
933void Graphics::recordDeferredFrameGraph() {
934 const size_t slot = currentFrameSlot();
935 if (deferredGraphRecorded_ && deferredGraphRecordedSlot_ == slot) {
943 auto *
graph = currentDeferredFrameGraph();
944 if (!
graph && (!shadowMaps.empty() || !gbufferSlots.empty())) {
947 buildDeferredFrameGraphs();
948 graph = currentDeferredFrameGraph();
950 if (!
graph || !gbufferPipeline || !gbufferRenderPass || !shadowPipeline) {
951 dropPendingOffscreenPasses();
961 auto *jobs = thread::Thread::create()->getJobSystem();
975 for (
auto &
d : shadowCascadeDraws)
d.
clear();
976 gbufferPassDraws.clear();
977 shadowPendingMask = 0;
978 gbufferPending =
false;
979 deferredGraphRecorded_ =
true;
980 deferredGraphRecordedSlot_ = slot;
image::ImageData::Colorf color
std::unique_ptr< RenderControl > renderControl_
GPU mesh handle (+ optional CPU morph targets).
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
void drawMeshGBuffer(Mesh *mesh, const glm::mat4 &mvp, const glm::mat4 &model, float nearZ, float farZ, Texture *albedo=nullptr, float tintR=1.f, float tintG=1.f, float tintB=1.f) override
void beginShadowPass(int cascadeIndex) override
Depth-only shadow pass for one cascade layer (0..2). Draws are recorded into the next begin3DFrame co...
void begin3DFrame() override
Begin a 3D frame: shadow/gbuffer (if pending) then a sampleable scene color pass (color+depth)....
void setMesh3DParallax(float scale, float minLayers=8.f, float maxLayers=32.f) override
Parallax occlusion mapping for the next default mesh draw. scale: UV displacement strength (0=off)....
void setCloudShadows(float strength, float worldCell, float time, float windSpeed, float windAngle, float coverage, float detail) override
Dynamic cloud shadows cast on the ground by the default PBR mesh path. strength 0 disables (no change...
void drawVoxelFaceInstances(const uint32_t *packed, int count, float originX, float originY, float originZ, const std::string &faceDir, Texture *atlas, int tilesPerRow=16, const uint32_t *ao=nullptr) override
Instanced voxel face rectangles (32-bit packed instances). ao: optional per-instance ambient-occlusio...
void end3DFrameToCanvas() override
void setMesh3DShadowReceive(bool receive) override
Per-draw: when false, shadow sampling is forced off for the next mesh draw.
void setMesh3DLighting(const Lighting3DPack &pack) override
Per-frame ambient + up to 8 lights packed into Mesh3DUBO.
void setMesh3DEnv(Texture *cube, float intensity) override
Specular IBL environment for subsequent default mesh draws. cube must be from newCubemap (or nullptr ...
bool isCanvasActive() const override
void endGBufferPass() override
void endShadowPass() override
void setMesh3DTexCellBomb(float cellScale, float strength, float rotAmount=1.f) override
Texture cell bombing for the next default mesh draw (breaks tiling). cellScale: cells per UV unit (ty...
void drawMeshShadow(Mesh *mesh, const glm::mat4 &lightMVP) override
void setMesh3DCameraPos(const glm::vec3 &eye) override
Camera eye used by mesh shaders that need view/rim (stored in Mesh3DUBO).
void begin3DFrameToCanvas(Canvas *canvas) override
Open a 3D render pass targeting an offscreen Canvas (color + depth) at the canvas size....
void setMesh3DViewProj(const glm::mat4 &viewProj) override
void setMesh3DShadows(const ShadowUpload &upload) override
Upload CSM constants for subsequent default mesh draws (active=false disables).
void beginGBufferPass(int width, int height) override
Depth/normal(/albedo) fill pass for mid/post effects. One-shot submit (like shadow); call before begi...
void setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) override
Directional light for subsequent drawMesh calls (world-space direction toward surface).
void setMesh3DHeightTexture(Texture *height) override
Optional height map for parallax (R channel; nullptr = flat / off).
void drawMeshGBufferAlpha(Mesh *mesh, const glm::mat4 &mvp, const glm::mat4 &model, float nearZ, float farZ, Texture *albedo=nullptr, float tintR=1.f, float tintG=1.f, float tintB=1.f) override
GBuffer fill with alpha-cutout discard (card/billboard geometry such as sprite-stack slices): same ou...
Texture * newCubemap(int faceSize, const uint8_t *rgbaFaces) override
Create an RGBA8 cubemap from 6 faces packed as +X,-X,+Y,-Y,+Z,-Z (each faceSize×faceSize,...
void setMesh3DView(const glm::mat4 &view) override
Camera view matrix for subsequent drawMesh (view-space depth / CSM select).
void drawMeshShadowAlpha(Mesh *mesh, const glm::mat4 &lightMVP, Texture *albedo=nullptr) override
Shadow pass draw with alpha-cutout discard (card/billboard geometry such as sprite-stack slices): tra...
vkb::Device & getDevice()
void setMesh3DNormalTexture(Texture *normal) override
Optional normal map for the next drawMesh / drawMeshShader (nullptr = flat).
void setMesh3DClip(float nearZ, float farZ) override
Near/far used to pack linear depth into scene color A (SSGI).
Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false) override
void setMesh3DSceneDepth(Texture *depth) override
Optional scene hardware depth (G-buffer hwDepth, Vulkan NDC z) bound to mesh3d shader binding 7....
void setMesh3DMaterial(float metallic, float roughness) override
Metallic (0..1) and roughness (0..1) for the next default mesh draw.
void clear(std::optional< Color > color, std::optional< int > stencil, std::optional< double > depth) override
vkb::ColorAttachmentImage & colorImage()
void recordFrameGraphWithJobSystem(vkb::FrameGraph &graph, eve::thread::JobSystem *jobs)
Record a vkb::FrameGraph with the engine JobSystem as the parallel recording executor.
static constexpr int kMaxLights
Light3DGpu lights[kMaxLights]
static constexpr int kMapSize
static constexpr int kCascades