载入中...
搜索中...
未找到
Graphics3D.cpp
浏览该文件的文档.
1// Vulkan backend implementation — 3D frame, shadow and G-buffer passes.
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
9#include "graphics/Light.h"
10#include "graphics/Outline.h"
16
17#include <SDL2/SDL.h>
18#include <SDL2/SDL_vulkan.h>
19
20#include <algorithm>
21#include <array>
22#include <bit>
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/mesh3d_vert_spv.inc"
53#include "graphics/shaders/mesh3d_frag_spv.inc"
54#include "graphics/shaders/voxel_rect_vert_spv.inc"
55#include "graphics/shaders/voxel_rect_frag_spv.inc"
58#include "thread/Thread.h"
59
60namespace eve::graphics::vulkan {
61
64 ASSERT(initialized);
65 if (!initialized) throw Exception("begin3DFrame: graphics not initialized");
66 if (isCanvasActive()) throw Exception("begin3DFrame: cannot start 3D while a Canvas is active");
67 createMesh3DPipeline();
68 if (swapchainPassOpen) {
69 // Previous eve_render opened 3D then threw before present().
70 try {
71 flushToSwapchain();
72 } catch (...) {
73 abortOpen3DFrame();
74 }
75 }
76 // Soft-fail like flushToSwapchain: on Android/iOS the surface may still be
77 // settling after orientation change; throwing would abort the whole script.
78 if (!beginPresentCommandBuffer())
79 return;
80 currentFrame2DBuffers().primitive3DDrawIndex = 0;
81 decalLayerFresh = false;
82 recordDeferredFrameGraph();
83 recordDecalPass();
84 vgAnyThisFrame_ = false;
86 ensureGpuDrivenCullResources(gbufferWidth > 0 ? gbufferWidth : int(swapchain.extent.width),
87 gbufferHeight > 0 ? gbufferHeight : int(swapchain.extent.height));
88
89 // 3D pass clears with backgroundColor (setBackgroundColor), not a stale 2D clear.
90 clearColor = backgroundColor;
91 createSceneColorResources(int(swapchain.extent.width), int(swapchain.extent.height));
92 // GPU-driven cull defers the scene color pass so the compute section
93 // (HZB build + cull + emit + VG cull) can run before the opaque draws;
94 // gpuDrivenOpenScenePass() opens it when the renderer reaches the forward
95 // section.
96 gpuDrivenScenePassPending_ = gpuDrivenEnabled_ && gpuDrivenCullReady_;
97 if (!gpuDrivenScenePassPending_) {
98 if (beginSceneColorRenderPass()) {
99 // Rebuild scene-pass pipelines to match the active scene pass (MSAA).
100 // Must happen AFTER the pass opens: if the MSAA scene pass is unavailable
101 // we fall back to the swapchain (e1) render pass below, and rasterizing
102 // with an Nx pipeline into an e1 pass is UB that can hang the GPU (TDR).
103 ensureScenePassPipelines(activeScenePass(), activeSceneSamples());
104 } else {
105 // Scene pass unavailable — draw 3D straight into the swapchain.
106 ensureScenePassPipelines(renderpass, vk::SampleCountFlagBits::e1);
107 beginSwapchainColorPass();
108 }
109 }
110
111 auto &cb = currentPresentCb();
112 setViewportAndScissor(cb, swapchain.extent.width, swapchain.extent.height);
113
114 {
115 auto &fslots = currentMesh3dFrameSlots();
116 fslots.lastDrawCount = fslots.drawIndex;
117 fslots.drawIndex = 0;
118 fslots.activePalette = 0;
119 ensureMesh3dRing(fslots);
120 auto &cslots = currentMesh3dClusteredFrameSlots();
121 cslots.lastDrawCount = cslots.drawIndex;
122 cslots.drawIndex = 0;
123 ensureMesh3dClusteredRing(cslots);
124 }
125 lastMesh3dPipeline = nullptr;
126 lastMesh3dClusteredPipeline = nullptr;
127 currentVoxelInstanceFrame().drawIndex = 0;
128 swapchainPassOpen = !gpuDrivenScenePassPending_;
129 frameHad3D = true;
130 hasPendingClear = false;
131}
132
133
134void Graphics::ensureOffscreen3DResources() {
135 auto &dev = getDevice();
136 auto createResources = [&](vk::Format colorFormat, vkb::BuiltRenderPass &renderPass, vk::Pipeline &meshPipeline,
137 std::array<vk::Pipeline, kMesh3DPipelineVariants> &surfacePipelines,
138 std::array<vk::Pipeline, kPrimitive3DPipelineVariants> &primitivePipelines) {
139 if (renderPass) return;
140 renderPass =
141 dev.createRenderPass()
142 .addSampledColorAttachment(colorFormat)
143 .addDepthAttachment(depthFormat, vk::AttachmentLoadOp::eClear,
144 vk::AttachmentStoreOp::eDontCare)
145 .addSubpass(vkb::SubpassBuilder()
146 .addAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal)
147 .setDepthStencilAttachment(
148 1, vk::ImageLayout::eDepthStencilAttachmentOptimal))
149 .addExternalShaderReadDependencies()
150 .build();
151 meshPipeline = createMesh3DStylePipeline(
152 embeddedSpirv(mesh3d_vert_spv), embeddedSpirv(mesh3d_frag_spv), mesh3dPipelineLayout,
153 renderPass, vk::SampleCountFlagBits::e1);
154 for (int blendValue = 0; blendValue < 5; ++blendValue) {
155 const auto blend = BlendMode(blendValue);
156 for (int depthValue = 0; depthValue < 2; ++depthValue) {
157 for (int doubleValue = 0; doubleValue < 2; ++doubleValue) {
158 const size_t index =
159 mesh3dPipelineIndex(blend, depthValue != 0, doubleValue != 0);
160 surfacePipelines[index] = createMesh3DStylePipeline(
161 embeddedSpirv(mesh3d_vert_spv), embeddedSpirv(mesh3d_frag_spv),
162 mesh3dPipelineLayout, renderPass, vk::SampleCountFlagBits::e1,
163 blend, depthValue != 0, doubleValue != 0);
164 }
165 }
166 }
167 buildPrimitive3DPipelines(renderPass, vk::SampleCountFlagBits::e1, primitivePipelines);
168 };
169 createResources(vk::Format::eR8G8B8A8Unorm, offscreen3DRenderPass, offscreen3DMeshPipeline,
170 offscreen3DSurfacePipelines, offscreenPrimitive3DPipelines);
171 createResources(vk::Format::eR16G16B16A16Sfloat, hdrOffscreen3DRenderPass, hdrOffscreen3DMeshPipeline,
172 hdrOffscreen3DSurfacePipelines, hdrOffscreenPrimitive3DPipelines);
173 if (!offscreen3DPool) {
174 vk::CommandPoolCreateInfo poolInfo{};
175 poolInfo.flags = vk::CommandPoolCreateFlagBits::eTransient | vk::CommandPoolCreateFlagBits::eResetCommandBuffer;
176 offscreen3DPool = dev->createCommandPool(poolInfo);
177 vk::CommandBufferAllocateInfo allocInfo{};
178 allocInfo.commandPool = offscreen3DPool;
179 allocInfo.level = vk::CommandBufferLevel::ePrimary;
180 allocInfo.commandBufferCount = 1;
181 auto bufs = dev->allocateCommandBuffers(allocInfo);
182 offscreen3DCB = bufs[0];
183 vk::FenceCreateInfo fenceInfo{};
184 fenceInfo.flags = vk::FenceCreateFlagBits::eSignaled; // first wait passes immediately
185 offscreen3DFence = dev->createFence(fenceInfo);
186 const auto &limits = dev.physical_device.properties.limits;
187 if (limits.timestampComputeAndGraphics && limits.timestampPeriod > 0.f) {
188 vk::QueryPoolCreateInfo queryInfo{};
189 queryInfo.queryType = vk::QueryType::eTimestamp;
190 queryInfo.queryCount = 2;
191 offscreen3DTimestampQueryPool = dev->createQueryPool(queryInfo);
192 offscreen3DTimestampPeriodNs = limits.timestampPeriod;
193 }
194 }
195}
196
197void Graphics::destroyOffscreen3DResources() {
198 auto &dev = getDevice();
199 if (offscreen3DFence) {
200 dev->destroyFence(offscreen3DFence);
201 offscreen3DFence = nullptr;
202 }
203 if (offscreen3DTimestampQueryPool) {
204 dev->destroyQueryPool(offscreen3DTimestampQueryPool);
205 offscreen3DTimestampQueryPool = nullptr;
206 }
207 offscreen3DTimestampPeriodNs = 0.f;
208 lastOffscreen3DGpuDurationMs = 0.f;
209 if (offscreen3DPool) {
210 dev->destroyCommandPool(offscreen3DPool);
211 offscreen3DPool = nullptr;
212 }
213 offscreen3DCB = nullptr;
214 if (offscreen3DMeshPipeline) {
215 device->destroyPipeline(offscreen3DMeshPipeline);
216 offscreen3DMeshPipeline = nullptr;
217 }
218 if (hdrOffscreen3DMeshPipeline) {
219 device->destroyPipeline(hdrOffscreen3DMeshPipeline);
220 hdrOffscreen3DMeshPipeline = nullptr;
221 }
222 for (auto &pipeline : offscreen3DSurfacePipelines) {
223 if (pipeline) device->destroyPipeline(pipeline);
224 pipeline = nullptr;
225 }
226 for (auto &pipeline : hdrOffscreen3DSurfacePipelines) {
227 if (pipeline) device->destroyPipeline(pipeline);
228 pipeline = nullptr;
229 }
230 for (auto &pipeline : offscreenPrimitive3DPipelines) {
231 if (pipeline) device->destroyPipeline(pipeline);
232 pipeline = nullptr;
233 }
234 for (auto &pipeline : hdrOffscreenPrimitive3DPipelines) {
235 if (pipeline) device->destroyPipeline(pipeline);
236 pipeline = nullptr;
237 }
238 offscreenPrimitive3DBufs.clear();
239 if (offscreen3DRenderPass) {
240 device->destroyRenderPass(offscreen3DRenderPass);
241 offscreen3DRenderPass = {};
242 }
243 if (hdrOffscreen3DRenderPass) {
244 device->destroyRenderPass(hdrOffscreen3DRenderPass);
245 hdrOffscreen3DRenderPass = {};
246 }
247}
248
251 ASSERT(initialized);
252 if (!initialized) throw Exception("begin3DFrameToCanvas: graphics not initialized");
253 if (!canvas) throw Exception("begin3DFrameToCanvas: null canvas");
254 createMesh3DPipeline();
255 auto *oc = dynamic_cast<OffscreenCanvas *>(canvas);
256 if (!oc) throw Exception("begin3DFrameToCanvas: not an offscreen canvas");
257 if (offscreen3DPassOpen) throw Exception("begin3DFrameToCanvas: already open");
258 if (swapchainPassOpen) {
259 try {
260 flushToSwapchain();
261 } catch (...) {
262 abortOpen3DFrame();
263 }
264 }
265 clearColor = backgroundColor;
266 ensureOffscreen3DResources();
267 oc->ensure3D();
268 offscreen3DCanvas = oc;
269 offscreen3DHDRActive = oc->isHDR();
270
271 if (offscreen3DFence) (void)device->waitForFences(1, &offscreen3DFence, VK_TRUE, UINT64_MAX);
272 offscreenPrimitive3DDrawIndex = 0;
273 vk::CommandBufferBeginInfo beginInfo{};
274 offscreen3DCB.begin(beginInfo);
275 lastOffscreen3DGpuDurationMs = 0.f;
276 if (offscreen3DTimestampQueryPool) {
277 offscreen3DCB.resetQueryPool(offscreen3DTimestampQueryPool, 0, 2);
278 offscreen3DCB.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe,
279 offscreen3DTimestampQueryPool, 0);
280 }
281 std::array<vk::ClearValue, 2> clears{};
282 clears[0].color =
283 vk::ClearColorValue(std::array<float, 4>{clearColor.r, clearColor.g, clearColor.b, 1.f});
284 clears[1].depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
285 vk::RenderPassBeginInfo rpBegin{};
286 rpBegin.renderPass = getOffscreen3DRenderPass(offscreen3DHDRActive);
287 rpBegin.framebuffer = oc->framebuffer3D();
288 rpBegin.renderArea =
289 vk::Rect2D{{0, 0}, {uint32_t(oc->getWidth()), uint32_t(oc->getHeight())}};
290 rpBegin.clearValueCount = uint32_t(clears.size());
291 rpBegin.pClearValues = clears.data();
292 oc->colorImage().beginColorAttachment();
293 oc->depthImage().beginDepthAttachment();
294 offscreen3DCB.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
295 setViewportAndScissor(offscreen3DCB, oc->getWidth(), oc->getHeight());
296
297 {
298 auto &fslots = currentMesh3dFrameSlots();
299 fslots.lastDrawCount = fslots.drawIndex;
300 fslots.drawIndex = 0;
301 fslots.activePalette = 0;
302 ensureMesh3dRing(fslots);
303 auto &cslots = currentMesh3dClusteredFrameSlots();
304 cslots.lastDrawCount = cslots.drawIndex;
305 cslots.drawIndex = 0;
306 ensureMesh3dClusteredRing(cslots);
307 }
308 lastMesh3dPipeline = nullptr;
309 lastMesh3dClusteredPipeline = nullptr;
310 offscreen3DPassOpen = true;
311 frameHad3D = true;
312 hasPendingClear = false;
313}
314
316 if (!offscreen3DPassOpen || !offscreen3DCB) return;
317 offscreen3DCB.endRenderPass();
318 if (offscreen3DCanvas) {
319 // The sampled render pass already performed its final-layout transition.
320 offscreen3DCanvas->colorImage().endSampledLayout();
321 }
322 if (offscreen3DTimestampQueryPool) {
323 offscreen3DCB.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe,
324 offscreen3DTimestampQueryPool, 1);
325 }
326 offscreen3DCB.end();
327
328 // Submit the offscreen render directly to the graphics queue (no present),
329 // then wait for it so it is fully drained before any subsequent render or
330 // teardown.
331 vk::SubmitInfo submitInfo{};
332 submitInfo.commandBufferCount = 1;
333 submitInfo.pCommandBuffers = &offscreen3DCB;
334 if (offscreen3DFence) (void)device->resetFences(1, &offscreen3DFence);
335 (void)device.getQueue(vkb::QueueType::graphics).submit(1, &submitInfo, offscreen3DFence);
336 if (offscreen3DFence)
337 (void)device->waitForFences(1, &offscreen3DFence, VK_TRUE, UINT64_MAX);
338 if (offscreen3DTimestampQueryPool && offscreen3DTimestampPeriodNs > 0.f) {
339 std::array<uint64_t, 2> ticks{};
340 const vk::Result queryResult = device->getQueryPoolResults(
341 offscreen3DTimestampQueryPool, 0, uint32_t(ticks.size()), sizeof(ticks), ticks.data(),
342 sizeof(uint64_t), vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
343 if (queryResult == vk::Result::eSuccess && ticks[1] >= ticks[0]) {
344 const double nanoseconds =
345 double(ticks[1] - ticks[0]) * double(offscreen3DTimestampPeriodNs);
346 lastOffscreen3DGpuDurationMs = float(nanoseconds * 1.0e-6);
347 }
348 }
349
350 offscreen3DPassOpen = false;
351 offscreen3DHDRActive = false;
352 offscreen3DCanvas = nullptr;
353}
354
355void Graphics::setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) {
356 glm::vec3 d = glm::normalize(dir);
357 if (glm::length(d) < 1e-6f) d = glm::vec3(0.f, 1.f, 0.f);
358 mesh3dFrameUbo.lightDir = glm::vec4(d, mesh3dFrameUbo.lightDir.w);
359 // Preserve .w (envIntensity) — filled per-draw in drawMeshShader.
360 mesh3dFrameUbo.lightColor = glm::vec4(color, mesh3dFrameUbo.lightColor.w);
361}
362
363void Graphics::setMesh3DCameraPos(const glm::vec3 &eye) {
364 mesh3dFrameUbo.cameraPos = glm::vec4(eye, mesh3dFrameUbo.cameraPos.w);
365}
366
367void Graphics::destroyVoxelRectResources() {
368 for (auto &frame : voxelInstanceFrames) {
369 for (auto &slot : frame.slots) {
370 slot.buffer.release();
371 slot.capacityBytes = 0;
372 }
373 frame.slots.clear();
374 frame.drawIndex = 0;
375 }
376 voxelInstanceFrames.clear();
377 voxelRectSets.clear();
378 auto destroyBuf = [&](vkb::GenericBuffer &b) { b.release(); };
379 if (voxelUnitQuadReady) {
380 destroyBuf(voxelUnitQuadVerts);
381 destroyBuf(voxelUnitQuadIndices);
382 voxelUnitQuadReady = false;
383 }
384 if (voxelRectPipeline) {
385 device->destroyPipeline(voxelRectPipeline);
386 voxelRectPipeline = vk::Pipeline{};
387 }
388 if (voxelRectPipelineLayout) {
389 device->destroyPipelineLayout(voxelRectPipelineLayout);
390 voxelRectPipelineLayout = vk::PipelineLayout{};
391 }
392 voxelRectSetLayoutUnique.reset();
393 voxelRectSetLayout = vk::DescriptorSetLayout{};
394}
395
396void Graphics::createVoxelRectPipeline() {
397 if (voxelRectPipeline) return;
398
399 if (!voxelRectPipelineLayout) {
400 vkb::DescriptorSetLayoutBuilder layoutBuilder;
401 voxelRectSetLayoutUnique =
402 layoutBuilder
403 .image(0, vk::DescriptorType::eCombinedImageSampler,
404 vk::ShaderStageFlagBits::eFragment, 1)
405 .createUnique(device.instance);
406 voxelRectSetLayout = *voxelRectSetLayoutUnique;
407
408 const auto pcr = pushConstantRange(vk::ShaderStageFlagBits::eVertex, sizeof(VoxelRectPC));
409 voxelRectPipelineLayout = createPipelineLayout(device, voxelRectSetLayout, &pcr);
410 }
411
412 voxelRectPipeline = buildVoxelRectPipeline(renderpass, vk::SampleCountFlagBits::e1);
413 ensureVoxelUnitQuad();
414}
415
416vk::Pipeline Graphics::buildVoxelRectPipeline(const vkb::BuiltRenderPass &rp,
417 vk::SampleCountFlagBits samples) {
418 auto vert = embeddedSpirv(voxel_rect_vert_spv);
419 auto frag = embeddedSpirv(voxel_rect_frag_spv);
420 ShaderModulePair modules(device, vert, frag);
421
422 vk::VertexInputBindingDescription bindings[3]{};
423 bindings[0].binding = 0;
424 bindings[0].stride = sizeof(glm::vec2);
425 bindings[0].inputRate = vk::VertexInputRate::eVertex;
426 bindings[1].binding = 1;
427 bindings[1].stride = sizeof(uint32_t);
428 bindings[1].inputRate = vk::VertexInputRate::eInstance;
429 bindings[2].binding = 2;
430 bindings[2].stride = sizeof(uint32_t);
431 bindings[2].inputRate = vk::VertexInputRate::eInstance;
432
433 vk::VertexInputAttributeDescription attrs[3]{};
434 attrs[0].location = 0;
435 attrs[0].binding = 0;
436 attrs[0].format = vk::Format::eR32G32Sfloat;
437 attrs[0].offset = 0;
438 attrs[1].location = 1;
439 attrs[1].binding = 1;
440 attrs[1].format = vk::Format::eR32Uint;
441 attrs[1].offset = 0;
442 attrs[2].location = 2;
443 attrs[2].binding = 2;
444 attrs[2].format = vk::Format::eR32Uint;
445 attrs[2].offset = 0;
446
447 vk::PipelineVertexInputStateCreateInfo vi{};
448 vi.vertexBindingDescriptionCount = 3;
449 vi.pVertexBindingDescriptions = bindings;
450 vi.vertexAttributeDescriptionCount = 3;
451 vi.pVertexAttributeDescriptions = attrs;
452
453 // Unit-quad indices 0-2-1 / 0-3-2 are object-space CCW for outward faces.
454 // perspectiveVulkanRH_ZO flips clip Y, so those faces are CCW in the
455 // framebuffer — CounterClockwise frontFace keeps them. Clockwise + Back
456 // (the mesh-pipeline convention) culls every submitted face after the Y
457 // flip; CPU 6-dir cull already dropped the true back faces, so the frame
458 // collapses to a 1px silhouette.
459 return device.createPipeline()
460 .useClassicPipeline(modules.vert, modules.frag)
461 .setPipelineLayout(voxelRectPipelineLayout)
462 .setVertexInputState(vi)
463 .setDynamicStatesViewportScissor()
464 .setRasterizer(vk::PolygonMode::eFill, false, false, 1.0f, vk::CullModeFlagBits::eBack,
465 vk::FrontFace::eCounterClockwise)
466 .setMultisampler(false, samples)
467 .setDepthStencil(true, true, vk::CompareOp::eLess)
468 .setColorAttachmentCount(1)
469 .build(rp);
470}
471
472void Graphics::ensureVoxelUnitQuad() {
473 if (voxelUnitQuadReady) return;
474 const float corners[8] = {0.f, 0.f, 1.f, 0.f, 1.f, 1.f, 0.f, 1.f};
475 voxelUnitQuadVerts.allocate(frameToken(), device, vk::BufferUsageFlagBits::eVertexBuffer, sizeof(corners),
476 vk::MemoryPropertyFlagBits::eHostVisible |
477 vk::MemoryPropertyFlagBits::eHostCoherent);
478 voxelUnitQuadVerts.updateLocal(frameToken(), corners, sizeof(corners));
479 const uint32_t indices[6] = {0, 2, 1, 0, 3, 2};
480 voxelUnitQuadIndices.allocate(frameToken(), device, vk::BufferUsageFlagBits::eIndexBuffer, sizeof(indices),
481 vk::MemoryPropertyFlagBits::eHostVisible |
482 vk::MemoryPropertyFlagBits::eHostCoherent);
483 voxelUnitQuadIndices.updateLocal(frameToken(), indices, sizeof(indices));
484 voxelUnitQuadReady = true;
485}
486
487vkb::BoundSet Graphics::voxelRectSetFor(GpuTexture *gpuTex) {
488 ASSERT(gpuTex != nullptr);
489 auto it = voxelRectSets.find(gpuTex);
490 if (it != voxelRectSets.end()) return it->second;
491
492 vk::DescriptorSetAllocateInfo alloc{};
493 alloc.descriptorPool = descriptorPool;
494 alloc.descriptorSetCount = 1;
495 alloc.pSetLayouts = &voxelRectSetLayout;
496 vkb::UnboundSet unbound{device->allocateDescriptorSets(alloc).front()};
497
498 vkb::DescriptorSetUpdater updater(4, 4, 0);
499 updater.beginDescriptorSet(unbound)
500 .beginImages(0, 0, vk::DescriptorType::eCombinedImageSampler)
501 .image(vkb::SampledImage::forLaterSample(gpuTex->sampler, gpuTex->imageView()))
502 .update(device.instance);
503
504 vkb::BoundSet bound = std::move(unbound).publish();
505 voxelRectSets.emplace(gpuTex, bound);
506 return bound;
507}
508
509void Graphics::drawVoxelFaceInstances(const uint32_t *packed, int count, float originX,
510 float originY, float originZ, const std::string &faceDir,
511 Texture *atlas, int tilesPerRow, const uint32_t *ao) {
512 ASSERT(initialized);
513 if (!initialized) throw Exception("drawVoxelFaceInstances: graphics not initialized");
514 if (!swapchainPassOpen) throw Exception("drawVoxelFaceInstances: call begin3DFrame first");
515 if (!voxelRectPipeline) createVoxelRectPipeline();
516 if (!packed || count <= 0) return;
517
518 int face = -1;
519 if (faceDir == "posX" || faceDir == "+x")
520 face = 0;
521 else if (faceDir == "negX" || faceDir == "-x")
522 face = 1;
523 else if (faceDir == "posY" || faceDir == "+y")
524 face = 2;
525 else if (faceDir == "negY" || faceDir == "-y")
526 face = 3;
527 else if (faceDir == "posZ" || faceDir == "+z")
528 face = 4;
529 else if (faceDir == "negZ" || faceDir == "-z")
530 face = 5;
531 else
532 throw Exception("drawVoxelFaceInstances: unknown faceDir '%s'", faceDir.c_str());
533
534 Texture *tex = atlas ? atlas : whiteTexture;
535 if (!tex || !tex->gpuHandle) throw Exception("drawVoxelFaceInstances: missing texture");
536 auto *gpuTex = static_cast<GpuTexture *>(tex->gpuHandle);
537
538 ensureVoxelUnitQuad();
539
540 const vk::DeviceSize bytes = vk::DeviceSize(count) * sizeof(uint32_t);
541 auto &vframe = currentVoxelInstanceFrame();
542 const size_t slotIndex = vframe.drawIndex++;
543 while (vframe.slots.size() <= slotIndex) vframe.slots.emplace_back();
544 auto &slot = vframe.slots[slotIndex];
545 if (slot.capacityBytes < size_t(bytes) || !slot.buffer.buffer) {
546 const size_t alloc = std::max(size_t(bytes), slot.capacityBytes ? slot.capacityBytes * 2 : size_t(bytes));
547 slot.buffer.allocate(frameToken(), device, vk::BufferUsageFlagBits::eVertexBuffer, vk::DeviceSize(alloc),
548 vk::MemoryPropertyFlagBits::eHostVisible |
549 vk::MemoryPropertyFlagBits::eHostCoherent);
550 slot.capacityBytes = alloc;
551 }
552 slot.buffer.updateLocal(frameToken(), packed, size_t(bytes));
553
554 // Ambient-occlusion words (null → full bright). Kept parallel to packed.
555 const uint32_t defaultAO = 0xFFu; // all four corners AO=3
556 std::vector<uint32_t> aoDefaults;
557 if (!ao) {
558 aoDefaults.assign(size_t(count), defaultAO);
559 ao = aoDefaults.data();
560 }
561 const vk::DeviceSize aoBytes = vk::DeviceSize(count) * sizeof(uint32_t);
562 if (slot.aoCapacityBytes < size_t(aoBytes) || !slot.aoBuffer.buffer) {
563 const size_t alloc = std::max(size_t(aoBytes),
564 slot.aoCapacityBytes ? slot.aoCapacityBytes * 2
565 : size_t(aoBytes));
566 slot.aoBuffer.allocate(frameToken(), device, vk::BufferUsageFlagBits::eVertexBuffer,
567 vk::DeviceSize(alloc),
568 vk::MemoryPropertyFlagBits::eHostVisible |
569 vk::MemoryPropertyFlagBits::eHostCoherent);
570 slot.aoCapacityBytes = alloc;
571 }
572 slot.aoBuffer.updateLocal(frameToken(), ao, size_t(aoBytes));
573
574 VoxelRectPC pc{};
575 pc.viewProj = mesh3dFrameUbo.mvp;
576 pc.chunkOrigin = glm::vec4(originX, originY, originZ, float(face));
577 pc.atlasInfo = glm::vec4(float(std::max(1, tilesPerRow)), 0.f, 0.f, 0.f);
578 pc.tint = mesh3dFrameUbo.tint;
579
580 auto &cb = currentPresentCb();
581 vk::DescriptorSet set = voxelRectSetFor(gpuTex);
582 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, voxelRectPipeline);
583 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, voxelRectPipelineLayout, 0, 1, &set, 0,
584 nullptr);
585 cb.pushConstants(voxelRectPipelineLayout, vk::ShaderStageFlagBits::eVertex, 0, sizeof(VoxelRectPC),
586 &pc);
587
588 vk::Buffer vbufs[3] = {voxelUnitQuadVerts.buffer, slot.buffer.buffer, slot.aoBuffer.buffer};
589 vk::DeviceSize offsets[3] = {0, 0, 0};
590 cb.bindVertexBuffers(0, 3, vbufs, offsets);
591 cb.bindIndexBuffer(voxelUnitQuadIndices.buffer, 0, vk::IndexType::eUint32);
592 cb.drawIndexed(6, uint32_t(count), 0, 0, 0);
593}
594
595void Graphics::setMesh3DNormalTexture(Texture *normal) { mesh3dNormalTexture = normal; }
596
598 mesh3dVirtualAtlas.z = enabled ? 1.f : 0.f;
599 mesh3dFrameUbo.virtualAtlas = mesh3dVirtualAtlas;
600}
601
602void Graphics::setMesh3DHeightTexture(Texture *heightTex) { mesh3dHeightTexture = heightTex; }
603
604void Graphics::setMesh3DVirtualTexture(bool enabled, int pageCountX, int pageCountY,
605 int atlasSlotsX, int atlasSlotsY,
606 float borderFraction) {
607 mesh3dVirtualTexture =
608 enabled ? glm::vec4(1.f, float(pageCountX), float(pageCountY), borderFraction)
609 : glm::vec4(0.f);
610 const float packedNormalMask = mesh3dVirtualAtlas.z;
611 mesh3dVirtualAtlas = enabled ? glm::vec4(float(atlasSlotsX), float(atlasSlotsY), packedNormalMask, 0.f)
612 : glm::vec4(0.f, 0.f, packedNormalMask, 0.f);
613 mesh3dFrameUbo.virtualTexture = mesh3dVirtualTexture;
614 mesh3dFrameUbo.virtualAtlas = mesh3dVirtualAtlas;
615}
616
617void Graphics::setMesh3DSceneDepth(Texture *depth) { mesh3dSceneDepthTexture = depth; }
618
619void Graphics::setMesh3DSceneColor(Texture *color) { mesh3dSceneColorTexture = color; }
620
622 if (mesh3dSceneColorTexture) return Mesh3DSceneColorCaptureStatus::ExplicitOverride;
623 if (!sceneColorPassOpen || sceneColorSlots.size() < 2 || !sceneColorResumeRenderPass ||
624 sceneColorSamples != vk::SampleCountFlagBits::e1)
625 return sceneColorHistoryValid ? Mesh3DSceneColorCaptureStatus::HistoryReuse
627
628 const size_t sourceIndex = currentFrameSlot() % sceneColorSlots.size();
629 const size_t snapshotIndex = (sourceIndex + 1u) % sceneColorSlots.size();
630 SceneColorSlot &source = sceneColorSlots[sourceIndex];
631 SceneColorSlot &snapshot = sceneColorSlots[snapshotIndex];
632 auto &cb = currentPresentCb();
633
634 cb.endRenderPass();
635 sceneColorPassOpen = false;
636 source.color.endSampledLayout();
637 source.color.setLayout(cb, vk::ImageLayout::eTransferSrcOptimal);
638 snapshot.color.setLayout(cb, vk::ImageLayout::eTransferDstOptimal);
639 vk::ImageCopy copy{};
640 copy.srcSubresource =
641 vk::ImageSubresourceLayers{vk::ImageAspectFlagBits::eColor, 0, 0, 1};
642 copy.dstSubresource =
643 vk::ImageSubresourceLayers{vk::ImageAspectFlagBits::eColor, 0, 0, 1};
644 copy.extent = vk::Extent3D{static_cast<uint32_t>(sceneColorWidth),
645 static_cast<uint32_t>(sceneColorHeight), 1};
646 cb.copyImage(source.color.image(), vk::ImageLayout::eTransferSrcOptimal,
647 snapshot.color.image(), vk::ImageLayout::eTransferDstOptimal, 1, &copy);
648 snapshot.color.setLayout(cb, vk::ImageLayout::eShaderReadOnlyOptimal);
649
650 source.color.setLayout(cb, vk::ImageLayout::eColorAttachmentOptimal);
651 source.depth.setLayout(cb, vk::ImageLayout::eDepthStencilAttachmentOptimal,
652 vk::ImageAspectFlagBits::eDepth);
653 vk::RenderPassBeginInfo resume{};
654 resume.renderPass = sceneColorResumeRenderPass;
655 resume.framebuffer = source.framebuffer;
656 resume.renderArea =
657 vk::Rect2D{{0, 0}, {static_cast<uint32_t>(sceneColorWidth),
658 static_cast<uint32_t>(sceneColorHeight)}};
659 cb.beginRenderPass(resume, vk::SubpassContents::eInline);
660 source.color.beginColorAttachment();
661 source.depth.beginDepthAttachment();
662 sceneColorPassOpen = true;
663 completedSceneColorSlot = snapshotIndex;
664 sceneColorHistoryValid = true;
666}
667
668void Graphics::setMesh3DEnv(Texture *cube, float intensity) {
669 mesh3dEnvTexture = cube;
670 mesh3dEnvIntensity = intensity < 0.f ? 0.f : intensity;
671 if (!cube) mesh3dEnvIntensity = 0.f;
672}
673
674void Graphics::setMesh3DEnvProbe(const glm::vec3 &center, const glm::vec3 &extent) {
675 mesh3dEnvProbeCenter = center;
676 mesh3dEnvProbeExtent = glm::max(extent, glm::vec3(0.f));
677 mesh3dFrameUbo.envProbeCenter = glm::vec4(mesh3dEnvProbeCenter, 1.f);
678 mesh3dFrameUbo.envProbeExtent = glm::vec4(mesh3dEnvProbeExtent, 0.f);
679}
680
682 mesh3dReflectionProbes = upload;
683}
684
685void Graphics::setMesh3DShadows(const ShadowUpload &upload) { mesh3dShadows = upload; }
686
687void Graphics::setMesh3DShadowReceive(bool receive) { mesh3dShadowReceive = receive; }
688
690 mesh3dSkinInfluenceLimit = static_cast<int>(count);
691}
692
693void Graphics::beginShadowPass(int cascadeIndex) {
694 ASSERT(initialized);
695 if (!shadowPipeline) createShadowResources();
696 if (cascadeIndex < 0 || cascadeIndex >= ShadowConfig::kTotalLayers) {
697 throw Exception("beginShadowPass: cascadeIndex out of range");
698 }
699 shadowPassCascade = cascadeIndex;
700 shadowPassDraws.clear();
701}
702
703void Graphics::drawMeshShadow(Mesh *mesh, const glm::mat4 &lightMVP, bool doubleSided) {
704 if (shadowPassCascade < 0) throw Exception("drawMeshShadow: call beginShadowPass first");
705 if (!mesh || !mesh->gpuHandle) throw Exception("drawMeshShadow: null mesh");
706 ShadowDraw d;
707 d.mesh = mesh;
708 d.mvp = lightMVP;
709 d.doubleSided = doubleSided;
710 prepareSkinPass(mesh, nullptr, lightMVP, glm::mat4(1.f), glm::vec4(0.f), d.skinSet,
711 d.skinUboOffset);
712 shadowPassDraws.push_back(d);
713}
714
715void Graphics::drawMeshShadowAlpha(Mesh *mesh, const glm::mat4 &lightMVP, Texture *albedo,
716 bool doubleSided, float lodWeight, bool lodFadeReverse,
717 bool lodDither) {
718 if (shadowPassCascade < 0) throw Exception("drawMeshShadowAlpha: call beginShadowPass first");
719 if (!mesh || !mesh->gpuHandle) throw Exception("drawMeshShadowAlpha: null mesh");
720 ShadowDraw d;
721 d.mesh = mesh;
722 d.mvp = lightMVP;
723 d.albedo = albedo;
724 d.alphaTest = true;
725 d.doubleSided = doubleSided;
726 d.lodFade = glm::vec4(std::clamp(lodWeight, 0.f, 1.f), lodFadeReverse ? 1.f : 0.f,
727 lodDither ? 1.f : 0.f, 0.f);
728 prepareSkinPass(mesh, albedo, lightMVP, glm::mat4(1.f), d.lodFade, d.skinSet,
729 d.skinUboOffset);
730 shadowPassDraws.push_back(d);
731}
732
734 if (shadowPassCascade < 0) throw Exception("endShadowPass: no active shadow pass");
735 if (!shadowPipeline || !shadowRenderPass) {
736 shadowPassCascade = -1;
737 shadowPassDraws.clear();
738 return;
739 }
740 const int cascade = shadowPassCascade;
741 shadowCascadeDraws[cascade] = std::move(shadowPassDraws);
742 shadowPassDraws.clear();
743 shadowPassCascade = -1;
744 shadowPendingMask |= (1u << cascade);
745 // GPU work is recorded into the swapchain command buffer in
746 // beginSwapchainRenderPass() so it shares the frame's submit and uses the
747 // ping-pong copy for this frame slot.
748}
749
751 ASSERT(initialized);
752 if (!initialized) throw Exception("beginGBufferPass: graphics not initialized");
753 if (w <= 0 || h <= 0) throw Exception("beginGBufferPass: invalid size");
754 if (!texSetLayout || !descriptorPool)
755 throw Exception("beginGBufferPass: textured descriptor layout not ready");
756 createGBufferResources(w, h);
757 gbufferPassActive = true;
758 gbufferPassDraws.clear();
759}
760
761void Graphics::drawMeshGBuffer(Mesh *mesh, const glm::mat4 &mvp, const glm::mat4 &model, float nearZ,
762 float farZ, Texture *albedo, float tintR, float tintG, float tintB,
763 float motionX, float motionY, float roughness, float metallic) {
764 if (!gbufferPassActive) throw Exception("drawMeshGBuffer: call beginGBufferPass first");
765 if (!mesh || !mesh->gpuHandle) throw Exception("drawMeshGBuffer: null mesh");
766 GBufferDraw d{};
767 d.mesh = mesh;
768 d.albedo = albedo;
769 d.push.mvp = mvp;
770 d.push.modelR0 = glm::vec4(model[0][0], model[1][0], model[2][0], model[3][0]);
771 d.push.modelR1 = glm::vec4(model[0][1], model[1][1], model[2][1], model[3][1]);
772 d.push.modelR2 = glm::vec4(model[0][2], model[1][2], model[2][2], model[3][2]);
773 auto u6 = [](float x) -> uint32_t {
774 return uint32_t(std::lround(std::clamp(x, 0.f, 1.f) * 63.f));
775 };
776 // Phase B: tint RGB6 | rough7 | metal7. Payload exceeds the 24-bit exact
777 // integer range of float, so bit-cast through the push constant (shader
778 // reads floatBitsToUint). Motion stays numeric (≤24 bits).
779 const uint32_t rough7 = uint32_t(std::lround(std::clamp(roughness, 0.f, 1.f) * 127.f));
780 const uint32_t metal7 = uint32_t(std::lround(std::clamp(metallic, 0.f, 1.f) * 127.f));
781 const uint32_t packedTint = u6(tintR) | (u6(tintG) << 6) | (u6(tintB) << 12) | (rough7 << 18) | (metal7 << 25);
782 auto motion12 = [](float value) {
783 return uint32_t(std::lround(std::clamp(value, -1.f, 1.f) * 2047.f)) + 2047u;
784 };
785 const uint32_t packedMotion = motion12(motionX) | (motion12(motionY) << 12);
786 d.push.clip = glm::vec4(nearZ, farZ, std::bit_cast<float>(packedTint), float(packedMotion));
787 prepareSkinPass(mesh, albedo, mvp, model, d.push.clip, d.skinSet, d.skinUboOffset);
788 gbufferPassDraws.push_back(d);
789}
790
791void Graphics::drawMeshGBufferAlpha(Mesh *mesh, const glm::mat4 &mvp, const glm::mat4 &model,
792 float nearZ, float farZ, Texture *albedo, float tintR,
793 float tintG, float tintB, float motionX, float motionY,
794 float roughness, float metallic) {
795 if (!gbufferPassActive) throw Exception("drawMeshGBufferAlpha: call beginGBufferPass first");
796 if (!mesh || !mesh->gpuHandle) throw Exception("drawMeshGBufferAlpha: null mesh");
797 GBufferDraw d{};
798 d.mesh = mesh;
799 d.albedo = albedo;
800 d.alphaTest = true;
801 d.push.mvp = mvp;
802 d.push.modelR0 = glm::vec4(model[0][0], model[1][0], model[2][0], model[3][0]);
803 d.push.modelR1 = glm::vec4(model[0][1], model[1][1], model[2][1], model[3][1]);
804 d.push.modelR2 = glm::vec4(model[0][2], model[1][2], model[2][2], model[3][2]);
805 auto u6 = [](float x) -> uint32_t {
806 return uint32_t(std::lround(std::clamp(x, 0.f, 1.f) * 63.f));
807 };
808 const uint32_t rough7 = uint32_t(std::lround(std::clamp(roughness, 0.f, 1.f) * 127.f));
809 const uint32_t metal7 = uint32_t(std::lround(std::clamp(metallic, 0.f, 1.f) * 127.f));
810 const uint32_t packedTint = u6(tintR) | (u6(tintG) << 6) | (u6(tintB) << 12) | (rough7 << 18) | (metal7 << 25);
811 auto motion12 = [](float value) {
812 return uint32_t(std::lround(std::clamp(value, -1.f, 1.f) * 2047.f)) + 2047u;
813 };
814 const uint32_t packedMotion = motion12(motionX) | (motion12(motionY) << 12);
815 d.push.clip = glm::vec4(nearZ, farZ, std::bit_cast<float>(packedTint), float(packedMotion));
816 prepareSkinPass(mesh, albedo, mvp, model, d.push.clip, d.skinSet, d.skinUboOffset);
817 gbufferPassDraws.push_back(d);
818}
819
821 if (!gbufferPassActive) throw Exception("endGBufferPass: no active gbuffer pass");
822 gbufferPassActive = false;
823
824 auto *slot = currentGBufferSlot();
825 if (!gbufferPipeline || !gbufferRenderPass || !slot || !slot->framebuffer) {
826 gbufferPassDraws.clear();
827 gbufferPending = false;
828 if (RenderControl* rc = getRenderControl()) rc->getGBuffer()->clear();
829 return;
830 }
831
832 gbufferPending = true;
833 if (RenderControl* rc = getRenderControl()) {
834 rc->getGBuffer()->setTargets(gbufferWidth, gbufferHeight, &slot->depthColorTex, &slot->normalTex,
835 &slot->albedoTex, &slot->depthTex, &slot->pbrParamsTex, &slot->emissiveTex);
836 }
837}
838
839void Graphics::ensureDecalPlaceholders() {
840 if (decalFlatAlbedo) return;
841 const uint8_t transparent[4] = {0, 0, 0, 0};
842 const uint8_t flatNormal[4] = {128, 128, 255, 255};
843 const uint8_t neutralParams[4] = {128, 128, 0, 255};
844 decalFlatAlbedo = newTexture(1, 1, transparent);
845 decalFlatNormal = newTexture(1, 1, flatNormal);
846 decalFlatParams = newTexture(1, 1, neutralParams);
847}
848
850 ASSERT(initialized);
851 if (!initialized) throw Exception("beginDecalPass: graphics not initialized");
852 if (w <= 0 || h <= 0) throw Exception("beginDecalPass: invalid size");
853 if (!texSetLayout || !descriptorPool)
854 throw Exception("beginDecalPass: textured descriptor layout not ready");
855 createDecalResources(w, h);
856 ensureDecalPlaceholders();
857 decalPassActive = true;
858 decalPassDraws.clear();
859}
860
861void Graphics::setDecalCamera(const glm::mat4 &viewProj, float nearZ, float farZ) {
862 decalViewProj = viewProj;
863 decalNear = nearZ > 1e-4f ? nearZ : 0.1f;
864 decalFar = farZ > decalNear ? farZ : decalNear + 1.f;
865}
866
868 Texture *params, const float uvRect[4], float fade,
869 float normalStrength, float roughnessStrength, float metalStrength,
870 float emissiveStrength, int blendMode, int projectionMode,
871 float blendSharpness, float parallaxScale, float parallaxMinLayers,
872 float parallaxMaxLayers, float edgeFadeWidth) {
873 if (!decalPassActive) throw Exception("drawDecal: call beginDecalPass first");
874 DecalDraw d{};
875 d.model = model;
876 d.albedo = albedo;
877 d.normal = normal;
878 d.params = params;
879 if (uvRect) {
880 for (int i = 0; i < 4; ++i) d.uvRect[i] = uvRect[i];
881 }
882 d.fade = fade;
883 d.normalStrength = normalStrength;
884 d.roughnessStrength = roughnessStrength;
885 d.metalStrength = metalStrength;
886 d.emissiveStrength = emissiveStrength;
887 d.blendMode = blendMode == 1 ? 1 : 0;
888 d.projectionMode = std::clamp(projectionMode, 0, 3);
889 d.blendSharpness = blendSharpness > 0.f ? blendSharpness : 4.f;
890 d.parallaxScale = std::clamp(parallaxScale, 0.f, 1.f);
891 d.parallaxMinLayers = std::clamp(parallaxMinLayers, 1.f, 64.f);
892 d.parallaxMaxLayers = std::clamp(parallaxMaxLayers, d.parallaxMinLayers, 64.f);
893 d.edgeFadeWidth = std::clamp(edgeFadeWidth, 0.f, 0.49f);
894 if (decalPassDraws.size() >= kMaxDecalInstances) return; // SSBO capacity guard
895 decalPassDraws.push_back(d);
896}
897
899 if (!decalPassActive) throw Exception("endDecalPass: no active decal pass");
900 decalPassActive = false;
901 auto *slot = currentDecalSlot();
902 if (!decalPipeline || !decalRenderPass || !slot || !slot->framebuffer) {
903 decalPassDraws.clear();
904 decalPending = false;
905 return;
906 }
907 decalPending = true;
908}
909
910void Graphics::recordDecalPass() {
911 if (!decalPending) return;
912 decalPending = false;
913 auto *slot = currentDecalSlot();
914 if (!slot || !decalPipeline || !decalRenderPass || !slot->framebuffer) {
915 decalPassDraws.clear();
916 return;
917 }
918 auto *gslot = currentGBufferSlot();
919 if (!gslot) {
920 decalPassDraws.clear();
921 return;
922 }
923
924 auto &cb = currentPresentCb();
925 recordDecalPassInto(cb, *slot, *gslot);
926}
927
928void Graphics::recordDecalPassInto(vk::CommandBuffer cb, DecalSlot &slot, GBufferSlot &gslot) {
929 const uint32_t w = uint32_t(decalWidth);
930 const uint32_t h = uint32_t(decalHeight);
931 if (w == 0 || h == 0) {
932 decalPassDraws.clear();
933 return;
934 }
935
936 DecalCameraUBO cam{};
937 cam.viewProj = decalViewProj;
938 cam.invViewProj = glm::inverse(decalViewProj);
939 cam.nearFarTexel =
940 glm::vec4(decalNear, decalFar, 1.f / float(w), 1.f / float(h));
941 updateRingLocal(slot.cameraUbo, 0, &cam, sizeof(cam));
942
943 slot.albedo.beginColorAttachment();
944 slot.normal.beginColorAttachment();
945 slot.params.beginColorAttachment();
946 std::array<vk::ClearValue, 3> clears{};
947 clears[0].color = vk::ClearColorValue(std::array<float, 4>{0, 0, 0, 0});
948 clears[1].color = vk::ClearColorValue(std::array<float, 4>{0, 0, 0, 0});
949 clears[2].color = vk::ClearColorValue(std::array<float, 4>{0, 0, 0, 0});
950 vk::RenderPassBeginInfo rpBegin{};
951 rpBegin.renderPass = decalRenderPass;
952 rpBegin.framebuffer = slot.framebuffer;
953 rpBegin.renderArea = vk::Rect2D{{0, 0}, {w, h}};
954 rpBegin.clearValueCount = uint32_t(clears.size());
955 rpBegin.pClearValues = clears.data();
956 cb.beginRenderPass(rpBegin, vk::SubpassContents::eInline);
957 setViewportAndScissor(cb, w, h);
958 cb.bindPipeline(vk::PipelineBindPoint::eGraphics, decalPipeline);
959 decalLayerFresh = true;
960
961 for (const auto &d : decalPassDraws) {
962 GpuTexture *gpuAlb = d.albedo && d.albedo->gpuHandle
963 ? static_cast<GpuTexture *>(d.albedo->gpuHandle)
964 : static_cast<GpuTexture *>(decalFlatAlbedo->gpuHandle);
965 GpuTexture *gpuNrm = d.normal && d.normal->gpuHandle
966 ? static_cast<GpuTexture *>(d.normal->gpuHandle)
967 : static_cast<GpuTexture *>(decalFlatNormal->gpuHandle);
968 GpuTexture *gpuPrm = d.params && d.params->gpuHandle
969 ? static_cast<GpuTexture *>(d.params->gpuHandle)
970 : static_cast<GpuTexture *>(decalFlatParams->gpuHandle);
971 DecalInstanceData inst{};
972 inst.model = d.model;
973 inst.uvRect = glm::vec4(d.uvRect[0], d.uvRect[1], d.uvRect[2], d.uvRect[3]);
974 inst.fadeParams =
975 glm::vec4(d.fade, d.normalStrength, d.roughnessStrength, d.metalStrength);
976 inst.extraParams = glm::vec4(d.emissiveStrength, float(d.blendMode),
977 float(d.projectionMode), d.blendSharpness);
978 inst.surfaceParams =
979 glm::vec4(d.parallaxScale, d.parallaxMinLayers, d.parallaxMaxLayers, d.edgeFadeWidth);
980 const vkb::BoundSet set = decalSetFor(slot, gpuAlb, gpuNrm, gpuPrm, &gslot.depthGpu,
981 &gslot.normalGpu);
982 cb.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, decalPipelineLayout, 0, 1,
983 set.ptr(), 0, nullptr);
984 cb.pushConstants(decalPipelineLayout,
985 vk::ShaderStageFlagBits::eVertex |
986 vk::ShaderStageFlagBits::eFragment,
987 0, sizeof(inst), &inst);
988 cb.draw(3, 1, 0, 0);
989 }
990 decalPassDraws.clear();
991 cb.endRenderPass();
992 slot.albedo.endSampledLayout();
993 slot.normal.endSampledLayout();
994 slot.params.endSampledLayout();
995}
996
997void Graphics::ensureDefaultEnvCubemap() {
998 if (defaultEnvCubemap) return;
999 const uint8_t black[4] = {0, 0, 0, 255};
1000 std::vector<uint8_t> faces(6u * 4u);
1001 for (int i = 0; i < 6; ++i) {
1002 faces[size_t(i) * 4u + 0] = black[0];
1003 faces[size_t(i) * 4u + 1] = black[1];
1004 faces[size_t(i) * 4u + 2] = black[2];
1005 faces[size_t(i) * 4u + 3] = black[3];
1006 }
1007 defaultEnvCubemap = newCubemap(1, faces.data());
1008}
1009
1011 mesh3dMetallic = metallic;
1012 mesh3dRoughness = roughness;
1013 mesh3dFrameUbo.ambient.w = metallic;
1014 mesh3dFrameUbo.cameraPos.w = roughness;
1015}
1016
1018 bool doubleSided, float alphaCutoff,
1019 const std::string &alphaTechnique) {
1020 mesh3dSurfaceMode = mode;
1021 mesh3dSurfaceBlend = blend;
1022 mesh3dSurfaceDepthWrite = depthWrite;
1023 mesh3dSurfaceDoubleSided = doubleSided;
1024 mesh3dAlphaCutoff = std::clamp(alphaCutoff, 0.f, 1.f);
1025 mesh3dAlphaTechnique = alphaTechnique;
1026}
1027
1028void Graphics::setMesh3DTexCellBomb(float cellScale, float strength, float rotAmount) {
1029 mesh3dTexBombScale = cellScale > 1e-3f ? cellScale : 1e-3f;
1030 mesh3dTexBombStrength = strength < 0.f ? 0.f : (strength > 1.f ? 1.f : strength);
1031 mesh3dTexBombRot = rotAmount < 0.f ? 0.f : (rotAmount > 1.f ? 1.f : rotAmount);
1032 mesh3dFrameUbo.texBomb =
1033 glm::vec4(mesh3dTexBombScale, mesh3dTexBombStrength, mesh3dTexBombRot, 0.f);
1034}
1035
1036void Graphics::setMesh3DParallax(float scale, float minLayers, float maxLayers) {
1037 mesh3dParallaxScale = scale < 0.f ? 0.f : (scale > 0.25f ? 0.25f : scale);
1038 float minL = minLayers < 1.f ? 1.f : minLayers;
1039 float maxL = maxLayers < minL ? minL : maxLayers;
1040 if (maxL > 64.f) maxL = 64.f;
1041 mesh3dParallaxMinLayers = minL;
1042 mesh3dParallaxMaxLayers = maxL;
1043 mesh3dFrameUbo.parallax =
1044 glm::vec4(mesh3dParallaxScale, mesh3dParallaxMinLayers, mesh3dParallaxMaxLayers, 0.f);
1045}
1046
1047void Graphics::setMesh3DLodDither(float weight, bool reverse, bool enabled) {
1048 mesh3dLodFade = glm::vec4(std::clamp(weight, 0.f, 1.f), reverse ? 1.f : 0.f,
1049 enabled ? 1.f : 0.f, 0.f);
1050 mesh3dFrameUbo.lodFade = mesh3dLodFade;
1051}
1052
1054 mesh3dLighting = pack;
1055 mesh3dFrameUbo.ambient = glm::vec4(glm::vec3(pack.ambient), mesh3dMetallic); const int n = std::max(0, std::min(pack.count, Lighting3DPack::kMaxLights));
1056 for (size_t i = 0; i < pack.diffuseProbeSh.size(); ++i)
1057 mesh3dFrameUbo.diffuseProbeSh[i] = pack.diffuseProbeSh[i];
1058 mesh3dFrameUbo.diffuseProbeInfo.x = pack.diffuseProbeShEnabled ? 1.f : 0.f;
1059 for (size_t i = 0; i < pack.diffuseVolumePosition.size(); ++i) {
1060 mesh3dFrameUbo.diffuseVolumePosition[i] = pack.diffuseVolumePosition[i];
1061 mesh3dFrameUbo.diffuseVolumeExtent[i] = pack.diffuseVolumeExtent[i];
1062 }
1063 for (size_t i = 0; i < pack.diffuseVolumeSh.size(); ++i)
1064 mesh3dFrameUbo.diffuseVolumeSh[i] = pack.diffuseVolumeSh[i];
1065 mesh3dFrameUbo.diffuseVolumeInfo.x = static_cast<float>(pack.diffuseVolumeProbeCount);
1066 mesh3dFrameUbo.diffuseVolumeInfo.y = pack.diffuseVolumeTrilinearCell ? 1.f : 0.f;
1067 mesh3dFrameUbo.lightDir.w = float(n);
1068 int dirI = -1;
1069 for (int i = 0; i < n; ++i) {
1070 if (pack.lights[i].posRadius.w <= 0.f) {
1071 dirI = i;
1072 break;
1073 }
1074 }
1075 if (dirI >= 0) {
1076 glm::vec3 d(pack.lights[dirI].posRadius);
1077 if (glm::length(d) < 1e-6f) d = glm::vec3(0.f, 1.f, 0.f);
1078 else d = glm::normalize(d);
1079 mesh3dFrameUbo.lightDir = glm::vec4(d, float(n));
1080 mesh3dFrameUbo.lightColor =
1081 glm::vec4(glm::vec3(pack.lights[dirI].color), mesh3dFrameUbo.lightColor.w);
1082 } else {
1083 // No directional: zero the legacy primary slot. mesh3d.frag always shades it.
1084 mesh3dFrameUbo.lightDir = glm::vec4(0.f, 1.f, 0.f, float(n));
1085 mesh3dFrameUbo.lightColor =
1086 glm::vec4(0.f, 0.f, 0.f, mesh3dFrameUbo.lightColor.w);
1087 }
1088}
1089
1090void Graphics::setCloudShadows(float strength, float worldCell, float time, float windSpeed,
1091 float windAngle, float coverage, float detail) {
1092 mesh3dFrameUbo.cloud = glm::vec4(std::clamp(strength, 0.f, 1.f), std::max(worldCell, 1e-4f),
1093 time, 0.f);
1094 mesh3dFrameUbo.cloudWind = glm::vec4(std::cos(windAngle) * windSpeed,
1095 std::sin(windAngle) * windSpeed,
1096 std::clamp(coverage, 0.f, 1.f), std::clamp(detail, 0.f, 1.f));
1097}
1098
1099void Graphics::ensureFlatNormalTexture3D() {
1100 if (flatNormalTexture3D) return;
1101 const uint8_t px[4] = {128, 128, 255, 255};
1102 flatNormalTexture3D = newTexture(1, 1, px);
1103}
1104
1105void Graphics::ensureFlatHeightTexture3D() {
1106 if (flatHeightTexture3D) return;
1107 // Mid-gray height: no relief when scale>0 without a real height map.
1108 const uint8_t px[4] = {128, 128, 128, 255};
1109 flatHeightTexture3D = newTexture(1, 1, px);
1110}
1111
1112vkb::BoundSet Graphics::mesh3dSetFor(GpuTexture *gpuTex, GpuTexture *normalTex, GpuTexture *envTex,
1113 GpuTexture *heightTex, GpuTexture *depthTex, GpuTexture *sceneColorTex,
1114 GpuTexture *decalAlbedo, GpuTexture *decalNormal,
1115 GpuTexture *decalParams,
1116 Mesh3dFrameSlots &fslots) {
1117 ASSERT(gpuTex != nullptr);
1118 ASSERT(normalTex != nullptr);
1119 ASSERT(envTex != nullptr);
1120 ASSERT(heightTex != nullptr);
1121 ASSERT(sceneColorTex != nullptr);
1122 ASSERT(decalAlbedo != nullptr);
1123 ASSERT(decalNormal != nullptr);
1124 ASSERT(decalParams != nullptr);
1125 ASSERT(currentShadowArrayView());
1126 ASSERT(fslots.uboRing.buffer);
1127 ASSERT(fslots.shadowRing.buffer);
1128
1129 auto probeTexture = [&](int index) -> GpuTexture * {
1130 if (index < mesh3dReflectionProbes.count) {
1131 Texture *texture = mesh3dReflectionProbes.probes[index].cubemap;
1132 if (texture && texture->gpuHandle) {
1133 auto *gpu = static_cast<GpuTexture *>(texture->gpuHandle);
1134 if (gpu->isCube) return gpu;
1135 }
1136 }
1137 return static_cast<GpuTexture *>(defaultEnvCubemap->gpuHandle);
1138 };
1139 GpuTexture *probe0 = probeTexture(0);
1140 GpuTexture *probe1 = probeTexture(1);
1141 Mesh3dSetKey key{gpuTex, normalTex, envTex, probe0, probe1, heightTex, depthTex, sceneColorTex,
1142 decalAlbedo, decalNormal, decalParams};
1143 key.paletteSlot = fslots.activePalette;
1144 if (fslots.palettes.empty()) uploadSkinPalette(nullptr, fslots);
1145 auto it = fslots.sets.find(key);
1146 if (it != fslots.sets.end()) return it->second;
1147
1148 vk::DescriptorSetAllocateInfo alloc{};
1149 alloc.descriptorPool = descriptorPool;
1150 alloc.descriptorSetCount = 1;
1151 alloc.pSetLayouts = &mesh3dSetLayout;
1152 vkb::UnboundSet unbound{device->allocateDescriptorSets(alloc).front()};
1153
1154 vkb::DescriptorSetUpdater updater(16, 16, 0);
1155 updater.beginDescriptorSet(unbound)
1156 .beginBuffers(0, 0, vk::DescriptorType::eUniformBufferDynamic)
1157 .buffer(fslots.uboRing.buffer, 0, sizeof(Mesh3DUBO))
1158 .beginBuffers(21, 0, vk::DescriptorType::eStorageBuffer)
1159 .buffer(fslots.palettes[fslots.activePalette].buffer, 0, fslots.palettes[fslots.activePalette].capacity)
1160 .beginImages(1, 0, vk::DescriptorType::eCombinedImageSampler)
1161 .image(vkb::SampledImage::forLaterSample(gpuTex->sampler, gpuTex->imageView()))
1162 .beginImages(2, 0, vk::DescriptorType::eCombinedImageSampler)
1163 .image(vkb::SampledImage::forLaterSample(normalTex->sampler, normalTex->imageView()))
1164 .beginImages(3, 0, vk::DescriptorType::eCombinedImageSampler)
1165 .image(vkb::SampledImage::forLaterSample(envTex->sampler, envTex->imageView()))
1166 .beginBuffers(4, 0, vk::DescriptorType::eUniformBufferDynamic)
1167 .buffer(fslots.shadowRing.buffer, 0, sizeof(ShadowUBO))
1168 .beginImages(5, 0, vk::DescriptorType::eCombinedImageSampler)
1169 .image(vkb::SampledImage::forLaterSample(shadowSampler, currentShadowArrayView()))
1170 .beginImages(6, 0, vk::DescriptorType::eCombinedImageSampler)
1171 .image(vkb::SampledImage::forLaterSample(heightTex->sampler, heightTex->imageView()))
1172 .beginImages(7, 0, vk::DescriptorType::eCombinedImageSampler)
1173 .image(vkb::SampledImage::forLaterSample(depthTex->sampler, depthTex->imageView()))
1174 .beginImages(8, 0, vk::DescriptorType::eCombinedImageSampler)
1175 .image(vkb::SampledImage::forLaterSample(decalAlbedo->sampler, decalAlbedo->imageView()))
1176 .beginImages(9, 0, vk::DescriptorType::eCombinedImageSampler)
1177 .image(vkb::SampledImage::forLaterSample(decalNormal->sampler, decalNormal->imageView()))
1178 .beginImages(10, 0, vk::DescriptorType::eCombinedImageSampler)
1179 .image(vkb::SampledImage::forLaterSample(decalParams->sampler, decalParams->imageView()))
1180 .beginImages(16, 0, vk::DescriptorType::eCombinedImageSampler)
1181 .image(vkb::SampledImage::forLaterSample(probe0->sampler, probe0->imageView()))
1182 .beginImages(17, 0, vk::DescriptorType::eCombinedImageSampler)
1183 .image(vkb::SampledImage::forLaterSample(probe1->sampler, probe1->imageView()))
1184 .beginImages(18, 0, vk::DescriptorType::eCombinedImageSampler)
1185 .image(vkb::SampledImage::forLaterSample(sceneColorTex->sampler, sceneColorTex->imageView()))
1186 .beginImages(20, 0, vk::DescriptorType::eCombinedImageSampler)
1187 .image(vkb::SampledImage::forLaterSample(shadowRawSampler, currentShadowArrayView()))
1188 .update(device.instance);
1189
1190 vkb::BoundSet bound = std::move(unbound).publish();
1191 fslots.sets.emplace(key, bound);
1192 return bound;
1193}
1194
1196 mesh3dFrameUbo.mvp = viewProj;
1197}
1198
1199void Graphics::setMesh3DView(const glm::mat4 &view) {
1200 mesh3dFrameUbo.view = view;
1201}
1202
1203void Graphics::setMesh3DClip(float nearZ, float farZ) {
1204 const float n = nearZ > 1e-4f ? nearZ : 0.1f;
1205 const float f = farZ > n ? farZ : n + 1.f;
1206 mesh3dFrameUbo.clipInfo = glm::vec4(n, f, mesh3dFrameUbo.clipInfo.z, mesh3dFrameUbo.clipInfo.w);
1207 mesh3dClustered.clipInfo.x = n;
1208 mesh3dClustered.clipInfo.y = f;
1209}
1210
1211} // namespace eve::graphics::vulkan
double value
float w
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float normalStrength
graphics::Texture * albedo
vk::ShaderModule vert
vk::ShaderModule frag
std::uint32_t key
glm::mat4 mvp
glm::vec3 n
Definition Grass.cpp:63
std::vector< std::uint32_t > indices
float blend
int h
std::vector< Colorf > px
std::array< float, 3 > scale
std::uint64_t bytes
MeleePoint3 b
Definition MeleeHit.cpp:41
float roughness
bool doubleSided
Texture * normal
float alphaCutoff
float metallic
std::vector< std::shared_ptr< DeviceBytes > > bindings
Definition OnnxGpgpu.cpp:38
float f
float d
int detail
bool lodFadeReverse
glm::vec3 eye
Mesh * mesh
float lodWeight
glm::mat4 viewProj
glm::mat4 view
glm::mat4 model
LocalPageCacheEntry slots[ShadowConfig::kLocalSlots]
std::uint32_t count
V3 dir
Definition TreeMesh.cpp:150
uint32_t index
const UnitySourceAsset & source
std::uint32_t depth
const AssetImportLimits & limits
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
Canvas public API.
Definition Canvas.h:17
void ensureFileTexturesReady()
Finish CPU decode and GPU upload for outstanding newTextureFromFile results. @thread Game/render thre...
Mesh3DSceneColorCaptureStatus
Observable result of requesting a same-frame mesh SceneColor snapshot.
Definition Graphics.h:1168
RenderControl * getRenderControl()
Shared compilable 3D render control (features → pass list + GBuffer). Owned by Graphics; valid for th...
Definition Graphics.cpp:254
GPU mesh handle (+ optional CPU morph targets).
Definition Mesh.h:25
Declarative, compilable 3D render control.
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
Definition Texture.h:18
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, float motionX=0.f, float motionY=0.f, float roughness=0.45f, float metallic=0.f) override
Draws mesh g buffer alpha.
void setMesh3DReflectionProbes(const ReflectionProbeUpload &upload) override
Sets the mesh 3 d reflection probes.
void beginShadowPass(int cascadeIndex) override
Begins shadow pass.
void begin3DFrame() override
Begins 3 d frame.
void setMesh3DParallax(float scale, float minLayers=8.f, float maxLayers=32.f) override
Sets the mesh 3 d parallax.
void setCloudShadows(float strength, float worldCell, float time, float windSpeed, float windAngle, float coverage, float detail) override
Sets the cloud shadows.
void drawMeshShadowAlpha(Mesh *mesh, const glm::mat4 &lightMVP, Texture *albedo=nullptr, bool doubleSided=true, float lodWeight=1.f, bool lodFadeReverse=false, bool lodDither=false) override
Draws mesh shadow alpha.
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
Draws voxel face instances.
void end3DFrameToCanvas() override
Ends 3 d frame to canvas.
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, float motionX=0.f, float motionY=0.f, float roughness=0.45f, float metallic=0.f) override
Draws mesh g buffer.
void endDecalPass() override
Ends decal pass.
void setMesh3DShadowReceive(bool receive) override
Sets the mesh 3 d shadow receive.
void setMesh3DLighting(const Lighting3DPack &pack) override
Sets the mesh 3 d lighting.
void setMesh3DEnv(Texture *cube, float intensity) override
Sets the mesh 3 d env.
Mesh3DSceneColorCaptureStatus captureMesh3DSceneColor() override
Capture mesh 3 d scene color.
void setDecalCamera(const glm::mat4 &viewProj, float nearZ, float farZ) override
Sets the decal camera.
void setMesh3DLodDither(float weight, bool reverse, bool enabled) override
Sets the mesh 3 d lod dither.
bool isCanvasActive() const override
True when canvas active.
void endGBufferPass() override
Ends g buffer pass.
void endShadowPass() override
Ends shadow pass.
void setMesh3DTexCellBomb(float cellScale, float strength, float rotAmount=1.f) override
Sets the mesh 3 d tex cell bomb.
void drawDecal(const glm::mat4 &model, Texture *albedo, Texture *normal, Texture *params, const float uvRect[4], float fade, float normalStrength, float roughnessStrength, float metalStrength, float emissiveStrength, int blendMode=0, int projectionMode=0, float blendSharpness=4.f, float parallaxScale=0.f, float parallaxMinLayers=8.f, float parallaxMaxLayers=24.f, float edgeFadeWidth=0.06f) override
Draws decal.
void setMesh3DSurface(SurfaceMode mode, BlendMode blend, bool depthWrite, bool doubleSided, float alphaCutoff, const std::string &alphaTechnique="cutoff") override
Sets the mesh 3 d surface.
void setMesh3DCameraPos(const glm::vec3 &eye) override
Sets the mesh 3 d camera pos.
void begin3DFrameToCanvas(Canvas *canvas) override
Begins 3 d frame to canvas.
void setMesh3DSceneColor(Texture *color) override
Sets the mesh 3 d scene color.
void setMesh3DViewProj(const glm::mat4 &viewProj) override
Sets the mesh 3 d view proj.
void drawMeshShadow(Mesh *mesh, const glm::mat4 &lightMVP, bool doubleSided=true) override
Draws mesh shadow.
void setMesh3DShadows(const ShadowUpload &upload) override
Sets the mesh 3 d shadows.
const vkb::BuiltRenderPass & getOffscreen3DRenderPass(bool hdr=false)
Returns the offscreen 3 d render pass.
Definition Graphics.h:1037
void beginGBufferPass(int width, int height) override
Begins g buffer pass.
void setMesh3DLight(const glm::vec3 &dir, const glm::vec3 &color) override
Sets the mesh 3 d light.
void setMesh3DHeightTexture(Texture *height) override
Sets the mesh 3 d height texture.
Texture * newCubemap(int faceSize, const uint8_t *rgbaFaces) override
Creates a cubemap. @ownership Caller deletes unless documented otherwise.
void setMesh3DView(const glm::mat4 &view) override
Sets the mesh 3 d view.
void setMesh3DVirtualTexture(bool enabled, int pageCountX, int pageCountY, int atlasSlotsX, int atlasSlotsY, float borderFraction) override
Sets the mesh 3 d virtual texture.
vkb::Device & getDevice()
Returns the device.
Definition Graphics.h:1014
void setMesh3DNormalTexture(Texture *normal) override
Sets the mesh 3 d normal texture.
void setMesh3DClip(float nearZ, float farZ) override
Sets the mesh 3 d clip.
Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false) override
Creates a texture. @ownership Caller deletes unless documented otherwise.
void setMesh3DSceneDepth(Texture *depth) override
Sets the mesh 3 d scene depth.
void setMesh3DEnvProbe(const glm::vec3 &center, const glm::vec3 &extent) override
Sets the mesh 3 d env probe.
void setMesh3DPackedNormalMask(bool enabled) override
Sets the mesh 3 d packed normal mask.
void setMesh3DMaterial(float metallic, float roughness) override
Sets the mesh 3 d material.
void setMesh3DSkinInfluenceLimit(SkinInfluenceLimit count) override
Sets the mesh 3 d skin influence limit.
FrameArena & currentFrameArena()
Per-frame arena for the current swapchain frame slot.
void beginDecalPass(int width, int height) override
Begins decal pass.
OffscreenCanvas public API.
Definition Canvas.h:15
vkb::ColorAttachmentImage & colorImage()
Color image.
Definition Canvas.h:50
std::vector< ParamSpec > params
SurfaceMode
How a 3D surface contributes to depth and color passes.
Definition SurfaceMode.h:6
eve::BlendMode BlendMode
Compatibility alias for the shared 2D blend mode.
Definition BlendMode.h:8
SkinInfluenceLimit
Number of strongest vertex influences retained by GPU skinning.
Definition IGraphics3D.h:19
std::vector< int64_t > attrs(const Node &n, const char *key, std::vector< int64_t > fallback)
Attrs.
BlendMode
Render-neutral 2D blend mode shared by graphics-facing modules.
Definition RenderTypes.h:11
bool enabled
Lighting3DPack public API.
Definition Light.h:172
std::array< glm::vec4, kMaxDiffuseVolumeProbes > diffuseVolumePosition
Definition Light.h:180
std::array< glm::vec4, kMaxDiffuseVolumeProbes > diffuseVolumeExtent
Definition Light.h:181
std::array< glm::vec4, kMaxDiffuseVolumeProbes *9 > diffuseVolumeSh
Definition Light.h:182
static constexpr int kMaxLights
Definition Light.h:173
Light3DGpu lights[kMaxLights]
Definition Light.h:175
std::array< glm::vec4, 9 > diffuseProbeSh
Definition Light.h:177
ReflectionProbeUpload public API.
Definition IGraphics3D.h:26
static constexpr int kTotalLayers
Definition Shadow.h:27
ShadowUpload public API.
Definition Shadow.h:54
GpuTexture public API.
Definition Graphics.h:257
glm::vec4 diffuseVolumePosition[Lighting3DPack::kMaxDiffuseVolumeProbes]
Definition Graphics.h:205
glm::vec4 diffuseVolumeExtent[Lighting3DPack::kMaxDiffuseVolumeProbes]
Definition Graphics.h:206
glm::vec4 diffuseVolumeSh[Lighting3DPack::kMaxDiffuseVolumeProbes *9]
Definition Graphics.h:207
glm::vec4 color