18#include <SDL2/SDL_vulkan.h>
41#include "common/config.h"
47#include "zeroerr/assert.h"
52#include <assimp/matrix3x3.h>
53#include <assimp/matrix4x4.h>
54#include <assimp/mesh.h>
55#include <assimp/vector3.h>
56#include <glm/gtc/matrix_transform.hpp>
64bool wantVulkanValidation() {
65#if defined(EVENGINE_IOS)
68 const char* vkVal = std::getenv(
"EVENGINE_VULKAN_VALIDATION");
69 return vkVal && vkVal[0] !=
'\0' && vkVal[0] !=
'0';
73void disableImplicitLayersIfSafe() {
74 if (wantVulkanValidation())
return;
75 if (
const char* existing = std::getenv(
"VK_LOADER_LAYERS_DISABLE")) {
76 if (existing[0] !=
'\0')
return;
78 if (
const char* layers = std::getenv(
"VK_INSTANCE_LAYERS")) {
79 if (layers[0] !=
'\0')
return;
82 _putenv_s(
"VK_LOADER_LAYERS_DISABLE",
"~implicit~");
84 setenv(
"VK_LOADER_LAYERS_DISABLE",
"~implicit~", 0);
88bool extensionAvailable(
const std::vector<vk::ExtensionProperties>& props,
const char*
name) {
89 for (
const auto&
p : props) {
90 if (std::strcmp(
p.extensionName,
name) == 0)
return true;
95void addIfAvailable(std::vector<const char*>& exts,
const std::vector<vk::ExtensionProperties>& props,
97 if (extensionAvailable(props,
name)) exts.push_back(
name);
100std::vector<const char*> collectFastInstanceExtensions(
const std::vector<vk::ExtensionProperties>& props,
101 vk::InstanceCreateFlags* flagsOut) {
102 std::vector<const char*> exts;
103 addIfAvailable(exts, props,
"VK_KHR_surface");
105 addIfAvailable(exts, props,
"VK_EXT_swapchain_colorspace");
107 addIfAvailable(exts, props,
"VK_KHR_win32_surface");
108#elif defined(__ANDROID__)
109 addIfAvailable(exts, props,
"VK_KHR_android_surface");
110#elif defined(__APPLE__)
111 addIfAvailable(exts, props,
"VK_EXT_metal_surface");
112 if (extensionAvailable(props,
"VK_KHR_portability_enumeration")) {
113 exts.push_back(
"VK_KHR_portability_enumeration");
114 if (flagsOut) *flagsOut |= vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR;
117 addIfAvailable(exts, props,
"VK_KHR_xcb_surface");
118 addIfAvailable(exts, props,
"VK_KHR_xlib_surface");
119 addIfAvailable(exts, props,
"VK_KHR_wayland_surface");
124void initVulkanDispatcher() {
125#if defined(EVENGINE_MACOSX)
126 if (PFN_vkGetInstanceProcAddr sdlGpa =
127 reinterpret_cast<PFN_vkGetInstanceProcAddr
>(SDL_Vulkan_GetVkGetInstanceProcAddr())) {
128 VULKAN_HPP_DEFAULT_DISPATCHER.init(sdlGpa);
129 vkb::InstanceBuilder::loaded =
true;
133 if (!vkb::InstanceBuilder::loaded) {
134 vkb::InstanceBuilder::loadDefault();
135 vkb::InstanceBuilder::loaded =
true;
139vkb::Instance createInstanceFast() {
140 initVulkanDispatcher();
141 disableImplicitLayersIfSafe();
145 const auto props = vk::enumerateInstanceExtensionProperties();
146 vk::InstanceCreateFlags flags{};
147 const auto exts = collectFastInstanceExtensions(props, &flags);
149 if (wantVulkanValidation()) {
150 vkb::InstanceBuilder builder;
151 builder.require_api_version(1, 0);
152 builder.request_validation_layers();
153 builder.use_default_debug_messenger();
154 for (
auto* e : exts) builder.enable_extension(e);
155#if defined(EVENGINE_MACOSX) || defined(EVENGINE_IOS)
156 builder.set_headless(
true);
157 builder.add_flags(flags);
159 std::printf(
"EVEngine: Vulkan validation layers enabled (EVENGINE_VULKAN_VALIDATION)\n");
160 return builder.build();
163 vk::ApplicationInfo app{};
164 app.pApplicationName =
"EVEngine";
165 app.pEngineName =
"EVEngine";
166 app.apiVersion = VK_MAKE_VERSION(1, 0, 0);
168 vk::InstanceCreateInfo ci{};
170 ci.pApplicationInfo = &app;
171 ci.enabledExtensionCount =
static_cast<uint32_t
>(exts.size());
172 ci.ppEnabledExtensionNames = exts.data();
175 inst.instance = vk::createInstance(ci);
176 VULKAN_HPP_DEFAULT_DISPATCHER.init(inst.instance);
180struct InstanceWarmup {
187InstanceWarmup& instanceWarmup() {
188 static InstanceWarmup
state;
192void startVulkanInstanceWarmupImpl() {
193#if defined(EVENGINE_MACOSX) || defined(EVENGINE_IOS)
197 auto&
w = instanceWarmup();
198 std::lock_guard<std::mutex> lock(
w.mu);
199 if (
w.started)
return;
201 w.future = std::async(std::launch::async, [] {
202 StartupStage stage(
" vulkan: instance (warmup thread)");
203 return createInstanceFast();
208vkb::Instance consumeWarmedInstance() {
209#if defined(EVENGINE_MACOSX) || defined(EVENGINE_IOS)
210 StartupStage stage(
" vulkan: instance");
211 return createInstanceFast();
213 startVulkanInstanceWarmupImpl();
214 std::future<vkb::Instance> fut;
216 auto&
w = instanceWarmup();
217 std::lock_guard<std::mutex> lock(
w.mu);
219 throw Exception(
"Vulkan instance warmup already consumed");
222 fut = std::move(
w.future);
224 StartupStage waitStage(
" vulkan: wait instance warmup");
229void discardWarmedInstance() noexcept {
230#if !defined(EVENGINE_MACOSX) && !defined(EVENGINE_IOS)
231 std::future<vkb::Instance> fut;
233 auto&
w = instanceWarmup();
234 std::lock_guard<std::mutex> lock(
w.mu);
235 if (!
w.started ||
w.consumed)
return;
237 fut = std::move(
w.future);
240 vkb::Instance warmed = fut.get();
241 if (
static_cast<VkInstance
>(warmed.instance) != VK_NULL_HANDLE) warmed.destroy();
242 }
catch (
const std::exception& e) {
243 std::fprintf(stderr,
"[vulkan] instance warmup cleanup failed: %s\n", e.what());
245 std::fprintf(stderr,
"[vulkan] instance warmup cleanup failed with an unknown error\n");
250struct RegisterVulkanWarmup {
252} gRegisterVulkanWarmup;
258#if defined(VKB_ENABLE_VMA)
259VmaAllocatorOwner::~VmaAllocatorOwner() {
260 if (allocator_) vmaDestroyAllocator(allocator_);
263void VmaAllocatorOwner::create(
const vkb::Instance& instance,
const vkb::PhysicalDevice& physicalDevice,
265 VmaAllocatorCreateInfo createInfo{};
266 createInfo.instance =
static_cast<VkInstance
>(instance.instance);
267 createInfo.physicalDevice =
static_cast<VkPhysicalDevice
>(physicalDevice.instance);
268 createInfo.device =
static_cast<VkDevice
>(
device.instance);
269 VmaVulkanFunctions vulkanFunctions{};
273 vulkanFunctions.vkGetInstanceProcAddr = VULKAN_HPP_DEFAULT_DISPATCHER.vkGetInstanceProcAddr;
274 if (!vulkanFunctions.vkGetInstanceProcAddr) {
275 throw Exception(
"VMA could not resolve vkGetInstanceProcAddr from the Vulkan dispatcher");
277 vulkanFunctions.vkGetDeviceProcAddr =
reinterpret_cast<PFN_vkGetDeviceProcAddr
>(
278 vulkanFunctions.vkGetInstanceProcAddr(createInfo.instance,
"vkGetDeviceProcAddr"));
279 if (!vulkanFunctions.vkGetDeviceProcAddr) {
280 throw Exception(
"VMA could not resolve vkGetDeviceProcAddr");
282 createInfo.pVulkanFunctions = &vulkanFunctions;
285 createInfo.vulkanApiVersion = VK_API_VERSION_1_0;
286 const VkResult result = vmaCreateAllocator(&createInfo, &allocator_);
287 if (result != VK_SUCCESS)
throw Exception(
"vmaCreateAllocator failed: %d",
int(result));
288 device.attachVmaAllocator(allocator_);
304 discardWarmedInstance();
305 if (
static_cast<VkInstance
>(inst.instance) != VK_NULL_HANDLE) inst.destroy();
310 if (
auto* rt = eve::cap::query<eve::graphics::IRayTracing>()) rt->detachFromGraphics();
311 destroyPbrResources();
313 if (gpuQueryPool_)
device->destroyQueryPool(gpuQueryPool_);
314 gpuQueryPool_ =
nullptr;
321 destroyGpuParticleResources();
322 ownedCanvases.clear();
324 ownedGpuMeshes.clear();
325 ownedTextures.clear();
326 for (
auto&
g : ownedGpuTextures) {
327 if (
g->sampler)
device->destroySampler(
g->sampler);
329 ownedGpuTextures.clear();
330 texturesByPath.clear();
331 for (
auto&
g : ownedGpuShaders) {
332 if (
g->swapchainPipeline)
device->destroyPipeline(
g->swapchainPipeline);
333 if (
g->offscreenPipeline)
device->destroyPipeline(
g->offscreenPipeline);
334 if (
g->swapchainOpaquePipeline)
device->destroyPipeline(
g->swapchainOpaquePipeline);
335 if (
g->offscreenOpaquePipeline)
device->destroyPipeline(
g->offscreenOpaquePipeline);
336 if (
g->hdrOffscreenPipeline)
device->destroyPipeline(
g->hdrOffscreenPipeline);
337 if (
g->hdrOffscreenOpaquePipeline)
device->destroyPipeline(
g->hdrOffscreenOpaquePipeline);
338 if (
g->mesh3dPipeline)
device->destroyPipeline(
g->mesh3dPipeline);
339 if (
g->mesh3dXrayPipeline)
device->destroyPipeline(
g->mesh3dXrayPipeline);
340 if (
g->mesh3dOffscreenPipeline)
device->destroyPipeline(
g->mesh3dOffscreenPipeline);
341 if (
g->mesh3dHdrOffscreenPipeline)
device->destroyPipeline(
g->mesh3dHdrOffscreenPipeline);
344 ownedGpuShaders.clear();
345 ownedShaders.clear();
346 destroySwapchainResources();
347 if (pipeline)
device->destroyPipeline(pipeline);
348 if (solidAlphaPipeline)
device->destroyPipeline(solidAlphaPipeline);
349 if (additiveSolidPipeline)
device->destroyPipeline(additiveSolidPipeline);
350 if (premultipliedSolidPipeline)
device->destroyPipeline(premultipliedSolidPipeline);
351 if (multiplySolidPipeline)
device->destroyPipeline(multiplySolidPipeline);
352 if (pipelineLayout)
device->destroyPipelineLayout(pipelineLayout);
353 if (texPipeline)
device->destroyPipeline(texPipeline);
354 if (additiveTexPipeline)
device->destroyPipeline(additiveTexPipeline);
355 if (premultipliedTexPipeline)
device->destroyPipeline(premultipliedTexPipeline);
356 if (multiplyTexPipeline)
device->destroyPipeline(multiplyTexPipeline);
357 if (opaqueTexPipeline)
device->destroyPipeline(opaqueTexPipeline);
358 if (particleDistortionPipeline)
device->destroyPipeline(particleDistortionPipeline);
359 if (sceneTonemapPipeline)
device->destroyPipeline(sceneTonemapPipeline);
360 if (texPipelineLayout)
device->destroyPipelineLayout(texPipelineLayout);
361 if (shaderPipelineLayout)
device->destroyPipelineLayout(shaderPipelineLayout);
362 if (mesh3dPipeline)
device->destroyPipeline(mesh3dPipeline);
363 if (mesh3dTransparentPipeline)
device->destroyPipeline(mesh3dTransparentPipeline);
364 for (
auto pipeline : mesh3dSurfacePipelines)
365 if (pipeline)
device->destroyPipeline(pipeline);
366 for (
auto pipeline : primitive3DPipelines)
367 if (pipeline)
device->destroyPipeline(pipeline);
368 destroyOffscreen3DResources();
369 if (mesh3dPipelineLayout)
device->destroyPipelineLayout(mesh3dPipelineLayout);
370 if (mesh3dShaderPipelineLayout)
device->destroyPipelineLayout(mesh3dShaderPipelineLayout);
371 mesh3dSetLayoutUnique.reset();
372 mesh3dFrameSlots.clear();
373 destroyVoxelRectResources();
374 if (mesh3dClusteredPipeline)
device->destroyPipeline(mesh3dClusteredPipeline);
375 if (mesh3dClusteredPipelineLayout)
device->destroyPipelineLayout(mesh3dClusteredPipelineLayout);
376 mesh3dClusteredSetLayoutUnique.reset();
377 mesh3dClusteredFrameSlots.clear();
378 destroyShadowResources();
379 destroyGBufferResources();
380 if (skinPassPipelineLayout)
device->destroyPipelineLayout(skinPassPipelineLayout);
381 skinPassPipelineLayout =
nullptr;
382 skinPassSetLayoutUnique.reset();
383 skinPassSetLayout =
nullptr;
384 destroyDecalResources();
385 destroySceneColorResources();
386 destroyUiColorResources();
387 auto destroyBuf = [&](vkb::GenericBuffer&
b) {
b.release(); };
388 for (
auto& st : clusteredStorages) {
389 destroyBuf(st.lightsBuf);
390 destroyBuf(st.tableBuf);
391 destroyBuf(st.indicesBuf);
393 clusteredStorages.clear();
394 if (lit2dPipeline)
device->destroyPipeline(lit2dPipeline);
395 if (lit2dAdditivePipeline)
device->destroyPipeline(lit2dAdditivePipeline);
396 if (lit2dPremultipliedPipeline)
device->destroyPipeline(lit2dPremultipliedPipeline);
397 if (lit2dMultiplyPipeline)
device->destroyPipeline(lit2dMultiplyPipeline);
398 if (lit2dOpaquePipeline)
device->destroyPipeline(lit2dOpaquePipeline);
399 if (offscreenLitPipeline)
device->destroyPipeline(offscreenLitPipeline);
400 if (offscreenLitAdditivePipeline)
device->destroyPipeline(offscreenLitAdditivePipeline);
401 if (offscreenLitPremultipliedPipeline)
device->destroyPipeline(offscreenLitPremultipliedPipeline);
402 if (offscreenLitMultiplyPipeline)
device->destroyPipeline(offscreenLitMultiplyPipeline);
403 if (offscreenLitOpaquePipeline)
device->destroyPipeline(offscreenLitOpaquePipeline);
404 if (lit2dPipelineLayout)
device->destroyPipelineLayout(lit2dPipelineLayout);
405 lit2dSetLayoutUnique.reset();
406 for (
auto&
m : lit2dSets)
m.clear();
408 offscreenLit2dSets.clear();
409 destroyBuf(offscreenLighting2dUbo);
410 if (offscreenSolidPipeline)
device->destroyPipeline(offscreenSolidPipeline);
411 if (offscreenSolidAlphaPipeline)
device->destroyPipeline(offscreenSolidAlphaPipeline);
412 if (offscreenAdditiveSolidPipeline)
device->destroyPipeline(offscreenAdditiveSolidPipeline);
413 if (offscreenPremultipliedSolidPipeline)
device->destroyPipeline(offscreenPremultipliedSolidPipeline);
414 if (offscreenMultiplySolidPipeline)
device->destroyPipeline(offscreenMultiplySolidPipeline);
415 if (offscreenTexPipeline)
device->destroyPipeline(offscreenTexPipeline);
416 if (offscreenAdditiveTexPipeline)
device->destroyPipeline(offscreenAdditiveTexPipeline);
417 if (offscreenPremultipliedTexPipeline)
device->destroyPipeline(offscreenPremultipliedTexPipeline);
418 if (offscreenMultiplyTexPipeline)
device->destroyPipeline(offscreenMultiplyTexPipeline);
419 if (offscreenOpaqueTexPipeline)
device->destroyPipeline(offscreenOpaqueTexPipeline);
420 if (offscreenRenderPass)
device->destroyRenderPass(offscreenRenderPass);
421 if (hdrOffscreenTexPipeline)
device->destroyPipeline(hdrOffscreenTexPipeline);
422 if (hdrOffscreenOpaqueTexPipeline)
device->destroyPipeline(hdrOffscreenOpaqueTexPipeline);
423 if (hdrOffscreenAdditiveTexPipeline)
device->destroyPipeline(hdrOffscreenAdditiveTexPipeline);
424 if (hdrOffscreenPremultipliedTexPipeline)
device->destroyPipeline(hdrOffscreenPremultipliedTexPipeline);
425 if (hdrOffscreenMultiplyTexPipeline)
device->destroyPipeline(hdrOffscreenMultiplyTexPipeline);
426 if (hdrOffscreenSolidPipeline)
device->destroyPipeline(hdrOffscreenSolidPipeline);
427 if (hdrOffscreenSolidAlphaPipeline)
device->destroyPipeline(hdrOffscreenSolidAlphaPipeline);
428 if (hdrOffscreenAdditiveSolidPipeline)
device->destroyPipeline(hdrOffscreenAdditiveSolidPipeline);
429 if (hdrOffscreenPremultipliedSolidPipeline)
device->destroyPipeline(hdrOffscreenPremultipliedSolidPipeline);
430 if (hdrOffscreenMultiplySolidPipeline)
device->destroyPipeline(hdrOffscreenMultiplySolidPipeline);
431 if (hdrOffscreenLitPipeline)
device->destroyPipeline(hdrOffscreenLitPipeline);
432 if (hdrOffscreenLitAdditivePipeline)
device->destroyPipeline(hdrOffscreenLitAdditivePipeline);
433 if (hdrOffscreenLitPremultipliedPipeline)
device->destroyPipeline(hdrOffscreenLitPremultipliedPipeline);
434 if (hdrOffscreenLitMultiplyPipeline)
device->destroyPipeline(hdrOffscreenLitMultiplyPipeline);
435 if (hdrOffscreenLitOpaquePipeline)
device->destroyPipeline(hdrOffscreenLitOpaquePipeline);
436 if (hdrOffscreenTonemapPipeline)
device->destroyPipeline(hdrOffscreenTonemapPipeline);
437 if (hdrOffscreenParticleDistortionPipeline)
device->destroyPipeline(hdrOffscreenParticleDistortionPipeline);
438 destroyPresentComposeResources();
439 if (hdrOffscreenRenderPass)
device->destroyRenderPass(hdrOffscreenRenderPass);
440 texSetLayoutUnique.reset();
441 if (descriptorPool)
device->destroyDescriptorPool(descriptorPool);
442 if (uploadPool)
device->destroyCommandPool(uploadPool);
443 if (renderpass)
device->destroyRenderPass(renderpass);
444 if (surface) inst.instance.destroySurfaceKHR(surface);
447void Graphics::createInstanceAndDevice(
const std::vector<const char*>& extNames,
void* nativeWindow,
448 vk::SurfaceKHR* surfaceOut) {
450 if (
static_cast<VkInstance
>(inst.instance) == VK_NULL_HANDLE) inst = consumeWarmedInstance();
452 if (nativeWindow !=
nullptr && surfaceOut !=
nullptr) {
454 auto* window =
static_cast<SDL_Window*
>(nativeWindow);
455 VkSurfaceKHR rawSurface = VK_NULL_HANDLE;
456 if (!SDL_Vulkan_CreateSurface(window,
static_cast<VkInstance
>(inst.instance), &rawSurface))
457 throw Exception(
"SDL_Vulkan_CreateSurface failed: %s", SDL_GetError());
458 surface = rawSurface;
459 *surfaceOut = rawSurface;
463 StartupStage stage(
" vulkan: physical device + device");
464 auto selector = inst.selectPhysicalDevice();
465 selector.set_minimum_version(1, 0);
467 selector.set_surface(surface);
473 selector.defer_surface_initialization();
475#if defined(EVENGINE_MACOSX) || defined(EVENGINE_IOS)
476 selector.add_required_extension(
"VK_KHR_portability_subset");
480 selector.add_desired_extension(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME);
481 selector.add_desired_extension(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME);
482 selector.add_desired_extension(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
483 selector.add_desired_extension(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME);
484 selector.add_desired_extension(VK_KHR_SPIRV_1_4_EXTENSION_NAME);
485 selector.add_desired_extension(VK_KHR_SHADER_FLOAT_CONTROLS_EXTENSION_NAME);
486 selector.add_desired_extension(VK_KHR_RAY_QUERY_EXTENSION_NAME);
487 auto phys = selector.select();
491 const auto& pp = phys.properties;
492 const char* vendor =
"unknown";
493 switch (pp.vendorID) {
494 case 0x10DE: vendor =
"NVIDIA";
break;
495 case 0x1002: vendor =
"AMD";
break;
496 case 0x8086: vendor =
"Intel";
break;
497 case 0x13B5: vendor =
"ARM";
break;
498 case 0x5143: vendor =
"Qualcomm";
break;
501 char vendorHex[16], deviceHex[16], driverHex[16];
502 std::snprintf(vendorHex,
sizeof(vendorHex),
"%04X", pp.vendorID);
503 std::snprintf(deviceHex,
sizeof(deviceHex),
"%04X", pp.deviceID);
504 std::snprintf(driverHex,
sizeof(driverHex),
"%08X", pp.driverVersion);
505 const uint32_t maj = VK_API_VERSION_MAJOR(pp.apiVersion);
506 const uint32_t
min = VK_API_VERSION_MINOR(pp.apiVersion);
507 const uint32_t pat = VK_API_VERSION_PATCH(pp.apiVersion);
508 eve::recordLogEvent(
"info", std::string(
"gpu: ") + vendor +
" '" + std::string(pp.deviceName.data()) +
509 "' vendorId=0x" + vendorHex +
" deviceId=0x" + deviceHex +
510 " api=" + std::to_string(maj) +
"." + std::to_string(
min) +
"." +
511 std::to_string(pat) +
" driver=0x" + driverHex +
512 (nativeWindow ?
"" :
" [headless]"));
515 const vk::PhysicalDeviceFeatures supported = phys->getFeatures();
516 if (supported.samplerAnisotropy) {
517 phys.features.samplerAnisotropy = VK_TRUE;
518 maxSamplerAnisotropy = phys.properties.limits.maxSamplerAnisotropy;
519 if (maxSamplerAnisotropy < 1.f) maxSamplerAnisotropy = 1.f;
521 maxSamplerAnisotropy = 1.f;
527 gpuDrivenCaps_ = GpuDrivenCaps{};
528 vk::PhysicalDeviceVulkan12Features vk12{};
529 vk::PhysicalDeviceFeatures2 features2{};
530 vk12.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
531 features2.sType = vk::StructureType::ePhysicalDeviceFeatures2;
532 features2.pNext = &vk12;
533 phys->getFeatures2(&features2);
534 gpuDrivenCaps_.
api12 = phys.properties.apiVersion >= VK_API_VERSION_1_2;
538 supported.shaderSampledImageArrayDynamicIndexing == VK_TRUE;
544 phys.features.shaderSampledImageArrayDynamicIndexing = VK_TRUE;
545 phys.features.shaderStorageBufferArrayDynamicIndexing = VK_TRUE;
546 phys.features.multiDrawIndirect = VK_TRUE;
553 rayTracingCaps_ = RayTracingCaps{};
554 vk::PhysicalDeviceAccelerationStructureFeaturesKHR rtAsEnable{};
555 vk::PhysicalDeviceRayTracingPipelineFeaturesKHR rtPipeEnable{};
556 vk::PhysicalDeviceRayQueryFeaturesKHR rtQueryEnable{};
557 bool enableRtFeatures =
false;
559 const auto extProps = phys->enumerateDeviceExtensionProperties();
560 auto hasExt = [&](
const char*
name) {
561 for (
const auto&
p : extProps) {
562 if (std::strcmp(
p.extensionName,
name) == 0)
return true;
566 const bool extsOk = hasExt(VK_KHR_ACCELERATION_STRUCTURE_EXTENSION_NAME) &&
567 hasExt(VK_KHR_RAY_TRACING_PIPELINE_EXTENSION_NAME) &&
568 hasExt(VK_KHR_DEFERRED_HOST_OPERATIONS_EXTENSION_NAME);
569 const bool api12 = phys.properties.apiVersion >= VK_API_VERSION_1_2;
570 const bool bdaExt = hasExt(VK_KHR_BUFFER_DEVICE_ADDRESS_EXTENSION_NAME) || api12;
571 const bool spirv14 = hasExt(VK_KHR_SPIRV_1_4_EXTENSION_NAME) || api12;
573 vk::PhysicalDeviceBufferDeviceAddressFeatures bdaFeat{};
574 bdaFeat.sType = vk::StructureType::ePhysicalDeviceBufferDeviceAddressFeatures;
575 vk::PhysicalDeviceAccelerationStructureFeaturesKHR asFeat{};
576 asFeat.sType = vk::StructureType::ePhysicalDeviceAccelerationStructureFeaturesKHR;
577 asFeat.pNext = &bdaFeat;
578 vk::PhysicalDeviceRayTracingPipelineFeaturesKHR rtFeat{};
579 rtFeat.sType = vk::StructureType::ePhysicalDeviceRayTracingPipelineFeaturesKHR;
580 rtFeat.pNext = &asFeat;
581 vk::PhysicalDeviceFeatures2 features2Rt{};
582 features2Rt.sType = vk::StructureType::ePhysicalDeviceFeatures2;
583 features2Rt.pNext = &rtFeat;
584 phys->getFeatures2(&features2Rt);
586 vk::PhysicalDeviceRayQueryFeaturesKHR rqFeat{};
587 rqFeat.sType = vk::StructureType::ePhysicalDeviceRayQueryFeaturesKHR;
588 if (hasExt(VK_KHR_RAY_QUERY_EXTENSION_NAME)) {
591 vk::PhysicalDeviceFeatures2 features2Rq{};
592 features2Rq.sType = vk::StructureType::ePhysicalDeviceFeatures2;
593 features2Rq.pNext = &rqFeat;
594 phys->getFeatures2(&features2Rq);
597 const bool featuresOk = rtFeat.rayTracingPipeline == VK_TRUE && asFeat.accelerationStructure == VK_TRUE &&
598 bdaFeat.bufferDeviceAddress == VK_TRUE;
600 if (extsOk && bdaExt && spirv14 && featuresOk) {
605 if (hasExt(VK_KHR_RAY_QUERY_EXTENSION_NAME) && rqFeat.rayQuery == VK_TRUE)
608 vk::PhysicalDeviceRayTracingPipelinePropertiesKHR rtProps{};
609 rtProps.sType = vk::StructureType::ePhysicalDeviceRayTracingPipelinePropertiesKHR;
610 vk::PhysicalDeviceProperties2 props2{};
611 props2.sType = vk::StructureType::ePhysicalDeviceProperties2;
612 props2.pNext = &rtProps;
613 phys->getProperties2(&props2);
622 rtAsEnable.sType = vk::StructureType::ePhysicalDeviceAccelerationStructureFeaturesKHR;
623 rtAsEnable.accelerationStructure = VK_TRUE;
624 rtPipeEnable.sType = vk::StructureType::ePhysicalDeviceRayTracingPipelineFeaturesKHR;
625 rtPipeEnable.rayTracingPipeline = VK_TRUE;
626 rtPipeEnable.pNext = &rtAsEnable;
628 rtQueryEnable.sType = vk::StructureType::ePhysicalDeviceRayQueryFeaturesKHR;
629 rtQueryEnable.rayQuery = VK_TRUE;
630 rtAsEnable.pNext = &rtQueryEnable;
632 enableRtFeatures =
true;
638 vkb::DeviceBuilder deviceBuilder = phys.createDevice();
640 vk::PhysicalDeviceVulkan12Features vk12Enable{};
641 vk12Enable.sType = vk::StructureType::ePhysicalDeviceVulkan12Features;
643 if (enableRtFeatures) {
644 vk12Enable.bufferDeviceAddress = VK_TRUE;
645 deviceBuilder.add_pNext(&rtPipeEnable);
647 deviceBuilder.add_pNext(&vk12Enable);
648 device = deviceBuilder.build();
650 VULKAN_HPP_DEFAULT_DISPATCHER.init(
device.instance);
651#if defined(VKB_ENABLE_VMA)
652 vmaAllocatorOwner_.create(inst, phys, device);
654 maxSamplerAnisotropy =
device.caps.maxSamplerAnisotropy;
656 "gpu: logical device created (gpuDriven=" +
658 ", rayTracing=" +
std::
string(rayTracingCaps_.rayTracingAvailable() ?
"on" :
"off") +
659#if defined(VKB_ENABLE_VMA)
662 ", allocator=native" +
664 ", maxAniso=" +
std::to_string(maxSamplerAnisotropy) +
")");
669 StartupStage initStage(
"graphics: initHeadless (total)");
677 throw Exception(
"Graphics::initHeadless: already initialized with a window");
683 createInstanceAndDevice({},
nullptr,
nullptr);
685 uploadPool =
device.createCommandPool();
690 StartupStage stage(
" vulkan: headless renderpass + pipelines");
693 vkb::RenderPassBuilder rpBuilder{
device};
695 rpBuilder.addPresentAttachment(vk::Format::eB8G8R8A8Unorm, vk::AttachmentLoadOp::eClear)
696 .addDepthAttachment(depthFormat, vk::AttachmentLoadOp::eClear, vk::AttachmentStoreOp::eDontCare)
697 .addSubpass(vkb::SubpassBuilder()
698 .addAttachmentRef(0, vk::ImageLayout::eColorAttachmentOptimal)
699 .setDepthStencilAttachment(1, vk::ImageLayout::eDepthStencilAttachmentOptimal))
701 VK_SUBPASS_EXTERNAL, 0,
702 vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests,
703 vk::PipelineStageFlagBits::eColorAttachmentOutput | vk::PipelineStageFlagBits::eEarlyFragmentTests,
705 vk::AccessFlagBits::eColorAttachmentRead | vk::AccessFlagBits::eColorAttachmentWrite |
706 vk::AccessFlagBits::eDepthStencilAttachmentWrite)
708 depthImage = vkb::DepthStencilImage{
device, uint32_t(
width), uint32_t(
height), depthFormat};
709 pipelineLayout = createPipelineLayout(
device);
710 createTexturedPipeline();
711 createMesh3DPipeline();
712 createMesh3DClusteredPipeline();
713 createVoxelRectPipeline();
718 StartupStage stage(
" vulkan: shadow resources (3x2048^2 maps + pipelines)");
719 createShadowResources();
723 const uint8_t whitePixel[4] = {255, 255, 255, 255};
725 const std::vector<uint8_t> cubePx(6 * 4, 255);
726 defaultBindlessCube =
newCubemap(1, cubePx.data());
729 StartupStage stage(
" vulkan: gpu-driven bindless set + pipeline");
730 initGpuDrivenResources();
736 eve::cap::provide<eve::service::IGpuTimer>(
this);
737 StartupStage initStage(
"graphics: initWithWindow (total)");
744 if (sdlWindow == nativeWindow && surface)
return;
745 sdlWindow = nativeWindow;
746 recreateSurfaceForResume();
751 rebuildSwapchainIfNeeded();
754 sdlWindow = nativeWindow;
755 auto* window =
static_cast<SDL_Window*
>(nativeWindow);
756 ASSERT(window !=
nullptr);
757 if (!window)
throw Exception(
"Graphics::initWithWindow: null SDL_Window");
759 unsigned int count = 0;
760 if (!SDL_Vulkan_GetInstanceExtensions(window, &
count,
nullptr))
761 throw Exception(
"SDL_Vulkan_GetInstanceExtensions failed: %s", SDL_GetError());
763 std::vector<const char*> extNames(
count);
764 if (!SDL_Vulkan_GetInstanceExtensions(window, &
count, extNames.data()))
765 throw Exception(
"SDL_Vulkan_GetInstanceExtensions failed: %s", SDL_GetError());
767 createInstanceAndDevice(extNames, nativeWindow, &surface);
769 uploadPool =
device.createCommandPool();
772 SDL_Vulkan_GetDrawableSize(window, &pw, &ph);
774 SDL_GetWindowSize(window, &lw, &lh);
778 StartupStage stage(
" vulkan: swapchain + solid pipelines");
779 createSwapchainAndPipeline();
783 StartupStage stage(
" vulkan: textured/lit2d pipelines");
784 createTexturedPipeline();
788 createMesh3DPipeline();
792 createMesh3DClusteredPipeline();
796 createVoxelRectPipeline();
800 StartupStage stage(
" vulkan: shadow resources (3x2048^2 maps + pipelines)");
801 createShadowResources();
805 const uint8_t whitePixel[4] = {255, 255, 255, 255};
807 const std::vector<uint8_t> cubePx(6 * 4, 255);
808 defaultBindlessCube =
newCubemap(1, cubePx.data());
811 StartupStage stage(
" vulkan: gpu-driven bindless set + pipeline");
812 initGpuDrivenResources();
818 if (!initialized)
return;
819 if (swapchainPassOpen || sceneColorPassOpen) abortOpen3DFrame();
827 destroySwapchainResources();
829 swapchain = vkb::Swapchain{};
831 inst.instance.destroySurfaceKHR(surface);
832 surface = VK_NULL_HANDLE;
835 swapchainDirty =
true;
838void Graphics::destroySwapchainResources() {
839 destroyPbrResources();
840 presentRecording = {};
842 presentModel.destroy();
843 presentModel = vkb::Present{};
844 destroyGpuDrivenCullResources();
845 depthImage = vkb::DepthStencilImage{};
846 destroyReadbackResources();
849 for (
auto& fb : frame2dBuffers) releaseFrame2dBuffers(fb);
850 frame2dBuffers.clear();
851 releaseFrame2dBuffers(offscreenBuffers);
854 for (
auto& ubo : lighting2dUboSlots) ubo.
release();
855 lighting2dUboSlots.clear();
861uint32_t Graphics::frameSlotCount()
const {
862 uint32_t
n = presentModel.frames_in_flight;
863 if (
n == 0)
n = swapchain.image_count;
867size_t Graphics::currentFrameSlot()
const {
868 const uint32_t
n = frameSlotCount();
869 size_t slot = presentRecording ? presentRecording.slot().index : swapchain.current_frame;
870 return (
n > 0 && slot >=
n) ? 0 : slot;
873vk::CommandBuffer& Graphics::currentPresentCb() {
874 if (offscreen3DPassOpen && offscreen3DCB)
return offscreen3DCB;
875 if (swapchainPass)
return swapchainPass.commandBuffer();
876 return presentRecording.commandBuffer();
879vkb::FrameSlot Graphics::frameToken()
const {
880 if (offscreen3DPassOpen)
return vkb::FrameSlot{0};
881 if (presentRecording)
return presentRecording.slot();
882 return vkb::FrameSlot::gpuIdle();
887void Graphics::invalidateTextureBindings() {
888 for (
auto& frame : mesh3dFrameSlots) frame.sets.
clear();
889 for (
auto& frame : mesh3dClusteredFrameSlots) frame.sets.
clear();
890 for (
auto&
m : lit2dSets)
m.
clear();
891 offscreenLit2dSets.clear();
893 voxelRectSets.clear();
896Graphics::Frame2DBuffers& Graphics::currentFrame2DBuffers() {
897 return currentSlot(frame2dBuffers, frameSlotCount(), currentFrameSlot());
900Graphics::Mesh3dFrameSlots& Graphics::currentMesh3dFrameSlots() {
901 return currentSlot(mesh3dFrameSlots, frameSlotCount(), currentFrameSlot());
904Graphics::Mesh3dClusteredFrameSlots& Graphics::currentMesh3dClusteredFrameSlots() {
905 return currentSlot(mesh3dClusteredFrameSlots, frameSlotCount(), currentFrameSlot());
908vkb::GenericBuffer& Graphics::currentLighting2dUbo() {
909 auto& ubo = currentSlot(lighting2dUboSlots, frameSlotCount(), currentFrameSlot());
912 ubo.allocate(frameToken(), device, vk::BufferUsageFlagBits::eUniformBuffer,
sizeof(Lighting2DUBO),
913 vk::MemoryPropertyFlagBits::eHostVisible | vk::MemoryPropertyFlagBits::eHostCoherent);
917std::unordered_map<Graphics::LitSetKey, vkb::BoundSet, Graphics::LitSetKeyHash>& Graphics::currentLit2dSets() {
918 return currentSlot(lit2dSets, frameSlotCount(), currentFrameSlot());
921Graphics::ClusteredStorage& Graphics::currentClusteredStorage() {
922 return currentSlot(clusteredStorages, frameSlotCount(), currentFrameSlot());
925Graphics::VoxelInstanceFrame& Graphics::currentVoxelInstanceFrame() {
926 return currentSlot(voxelInstanceFrames, frameSlotCount(), currentFrameSlot());
929Graphics::ShadowMapSlot& Graphics::currentShadowMap() {
930 ASSERT(!shadowMaps.empty());
931 return shadowMaps[currentFrameSlot() % shadowMaps.size()];
934vk::ImageView Graphics::currentShadowArrayView() {
935 if (shadowMaps.empty())
return vk::ImageView{};
936 return currentShadowMap().image.arrayView();
939Graphics::GBufferSlot* Graphics::currentGBufferSlot() {
940 if (gbufferSlots.empty())
return nullptr;
941 return &gbufferSlots[currentFrameSlot() % gbufferSlots.size()];
945 if (
auto* slot = currentGBufferSlot(); slot && gbufferPending && slot->depthColorTex.gpuHandle)
946 return &slot->depthColorTex;
951Graphics::DecalSlot* Graphics::currentDecalSlot() {
952 if (decalSlots.empty())
return nullptr;
953 return &decalSlots[currentFrameSlot() % decalSlots.size()];
956Graphics::SceneColorSlot* Graphics::currentSceneColorSlot() {
957 if (sceneColorSlots.empty())
return nullptr;
958 return &sceneColorSlots[currentFrameSlot() % sceneColorSlots.size()];
962 auto* slot = currentSceneColorSlot();
963 if (!slot || !slot->colorTex.gpuHandle)
return nullptr;
964 return &slot->colorTex;
967Graphics::UiColorSlot* Graphics::currentUiColorSlot() {
968 if (uiColorSlots.empty())
return nullptr;
969 return &uiColorSlots[currentFrameSlot() % uiColorSlots.size()];
972void Graphics::dropPendingOffscreenPasses() {
973 shadowPendingMask = 0;
974 for (
auto&
d : shadowCascadeDraws)
d.
clear();
975 gbufferPending =
false;
976 gbufferPassDraws.clear();
977 decalPending =
false;
978 decalPassDraws.clear();
981bool Graphics::beginSwapchainRenderPass() {
982 if (!beginPresentCommandBuffer()) {
983 dropPendingOffscreenPasses();
986 recordDeferredFrameGraph();
987 beginSwapchainColorPass();
991void Graphics::beginSwapchainColorPass() {
992 std::array<vk::ClearValue, 2> clears{};
993 clears[0].color = vk::ClearColorValue(std::array<float, 4>{clearColor.r, clearColor.g, clearColor.b, clearColor.a});
994 clears[1].depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
995 swapchainPass = presentRecording.beginRenderPass(renderpass, clears.data(), uint32_t(clears.size()));
998bool Graphics::rebuildSwapchainIfNeeded() {
999 const bool wantRecreate = surfaceNeedsRecreate.load() || swapchainDirty || presentModel.needs_recreate;
1000 if (!wantRecreate)
return true;
1001 if (!isRenderSurfaceStable())
return false;
1003 if (surfaceNeedsRecreate.exchange(
false)) recreateSurfaceForResume();
1004 if (presentModel.needs_recreate) {
1005 presentModel.needs_recreate =
false;
1006 swapchainDirty =
true;
1008 if (swapchainDirty) {
1010 createSwapchainAndPipeline();
1015bool Graphics::beginPresentCommandBuffer() {
1016 for (
int attempt = 0; attempt < 3; ++attempt) {
1017 if (!rebuildSwapchainIfNeeded())
return false;
1018 presentRecording = presentModel.begin();
1019 if (presentRecording) {
1024 if (presentModel.needs_recreate) {
1025 presentModel.needs_recreate =
false;
1026 swapchainDirty =
true;
1034bool Graphics::isRenderSurfaceReady()
const {
1035#if defined(EVENGINE_ANDROID) || defined(EVENGINE_IOS)
1036 auto*
window =
static_cast<SDL_Window*
>(sdlWindow);
1037 if (!window)
return false;
1041 SDL_Vulkan_GetDrawableSize(window, &pw, &ph);
1042 return pw > 0 && ph > 0;
1048bool Graphics::isRenderSurfaceStable() {
1049#if defined(EVENGINE_ANDROID) || defined(EVENGINE_IOS)
1050 auto*
window =
static_cast<SDL_Window*
>(sdlWindow);
1051 if (!window)
return false;
1053 SDL_Vulkan_GetDrawableSize(window, &pw, &ph);
1054 if (pw <= 0 || ph <= 0) {
1055 surfaceStableFrames = 0;
1058 if (pw != pendingSurfaceW || ph != pendingSurfaceH) {
1060 pendingSurfaceW = pw;
1061 pendingSurfaceH = ph;
1062 surfaceStableFrames = 1;
1066 if (surfaceStableFrames < 3) {
1067 ++surfaceStableFrames;
1076void Graphics::recreateSurfaceForResume() {
1081 if (!initialized)
return;
1082 auto*
window =
static_cast<SDL_Window*
>(sdlWindow);
1083 if (!window)
return;
1088 destroySwapchainResources();
1089 swapchain.destroy();
1090 swapchain = vkb::Swapchain{};
1092 inst.instance.destroySurfaceKHR(surface);
1093 surface = VK_NULL_HANDLE;
1097 VkSurfaceKHR rawSurface = VK_NULL_HANDLE;
1098 if (!SDL_Vulkan_CreateSurface(window,
static_cast<VkInstance
>(inst.instance), &rawSurface))
1099 throw Exception(
"resume: SDL_Vulkan_CreateSurface failed: %s", SDL_GetError());
1100 surface = rawSurface;
1101 device.surface = surface;
1104 int pw = 0, ph = 0, lw = 0, lh = 0;
1105 SDL_Vulkan_GetDrawableSize(window, &pw, &ph);
1106 SDL_GetWindowSize(window, &lw, &lh);
1109 swapchainDirty =
true;
1113 if (!initialized)
return;
1114 if (headless_)
return;
1117 if (!isRenderSurfaceReady()) {
1120 if (swapchainPassOpen)
1124 hasPendingClear =
false;
1136 if (gpuQueryPool_)
return;
1138 timestampPeriod_ =
device.physical_device.properties.limits.timestampPeriod;
1139 vk::QueryPoolCreateInfo ci;
1140 ci.queryType = vk::QueryType::eTimestamp;
1142 gpuQueryPool_ =
device->createQueryPool(ci);
1143 gpuTimingReady_ =
true;
1145 gpuTimingReady_ =
false;
1146 gpuQueryPool_ =
nullptr;
1151 if (!gpuTimingReady_ || !presentRecording)
return;
1152 vk::CommandBuffer cb = presentRecording.commandBuffer();
1153 cb.resetQueryPool(gpuQueryPool_, 0, 2);
1154 cb.writeTimestamp(vk::PipelineStageFlagBits::eTopOfPipe, gpuQueryPool_, 0);
1158 if (!gpuTimingReady_ || !presentRecording)
return;
1159 vk::CommandBuffer cb = presentRecording.commandBuffer();
1160 cb.writeTimestamp(vk::PipelineStageFlagBits::eBottomOfPipe, gpuQueryPool_, 1);
1164 if (!gpuTimingReady_ || !gpuQueryPool_)
return;
1166 std::array<uint64_t, 2> ts{};
1167 const auto r =
device->getQueryPoolResults(gpuQueryPool_, 0, 2,
sizeof(ts), ts.data(),
sizeof(uint64_t),
1168 vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait);
1169 if (
r == vk::Result::eSuccess && ts[1] > ts[0])
1170 gpuFrameMs_ = float(
double(ts[1] - ts[0]) *
double(timestampPeriod_) / 1'000'000.0);
Persistent, thread-safe record of engine crash and problem events.
std::function< eve::Result< void >()> release
EVENGINE_API_FOUNDATION public API.
Screen-space buffers for mid/post effects and Hybrid deferred lighting.
Texture * getDepthTexture() const
Returns the depth texture.
bool isValid() const
True when valid.
std::vector< std::pair< WindowDestroyedCallback, void * > > windowDestroyedCallbacks_
std::unique_ptr< AntiAliasing > pipelineAA_
std::vector< DeferredFileTexture > deferredFileTextures_
void retireResourceLifetime() const
Retire resource lifetime.
void clearPresentOverlay()
Drop the present overlay callback (window gone; re-register on UI init).
std::unique_ptr< RenderControl > renderControl_
bool isActive() const
True when active.
std::unique_ptr< GlobalIllumination > pipelineGI_
std::unique_ptr< AmbientOcclusion > pipelineAO_
GPU texture created via Graphics::newTexture. Owns GPU resources through an opaque backend handle.
void onNativeWindowDestroyed() override
On native window destroyed.
void draw(eve::graphics::Graphics *gfx, const glm::mat4 &matrix) const override
Draws .
void present() override
Present.
Texture * getSceneColorTexture() override
Returns the scene color texture.
std::string getBackendName() const override
Returns the backend name.
void waitForSharedGpuResources()
Block until all in-flight GPU work (all frames) has completed.
bool isCanvasActive() const override
True when canvas active.
void initWithWindow(void *nativeWindow) override
Initializes with window.
void writeGpuTimestampEnd()
Writes an end timestamp into the current present command buffer.
void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override
Sets the viewport size.
float gpuFrameMs() const override
Gpu frame ms.
Graphics()
Starts asynchronous VkInstance creation (overlaps later window init).
bool gpuTimingAvailable() const override
Gpu timing available.
bool supportsRuntimeGlslCompilation() const override
Supports runtime glsl compilation.
Texture * getSceneLinearDepthTexture() override
Returns the scene linear depth texture.
Texture * newCubemap(int faceSize, const uint8_t *rgbaFaces) override
Creates a cubemap. @ownership Caller deletes unless documented otherwise.
void readGpuFrameTiming()
Reads the query results after the frame's submit has completed.
void initGpuTiming()
Creates the GPU timestamp query pool; call after device init.
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 initHeadless(int width, int height) override
Initializes headless.
~Graphics() override
Graphics.
void clear(std::optional< Color > color, std::optional< int > stencil, std::optional< double > depth) override
Clears .
void writeGpuTimestampBegin()
Writes a begin timestamp into the current present command buffer.
EVENGINE_API_FOUNDATION void registerVulkanInstanceWarmup(void(*start)())
Register the graphics-backend function that starts VkInstance creation.
constexpr uint32_t kMaxBindlessTextures
bool glslRuntimeCompilationAvailable()
Whether this build can compile GLSL text to SPIR-V at runtime.
void detachGraphicsArtifactProvider(Graphics *graphics) noexcept
Detach a Graphics instance before its derived backend is destroyed.
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.
void recordLogEvent(const std::string &level, const std::string &message)
Appends a timestamped event line to the crash/error log.
uint32_t shaderGroupHandleAlignment
bool accelerationStructure
bool available
Full KHR RT pipeline path usable
uint32_t maxRecursionDepth
uint32_t shaderGroupHandleSize
uint32_t shaderGroupBaseAlignment
bool rayQuery
Optional VK_KHR_ray_query
bool gpuDrivenAvailable() const
Gpu driven available.
bool samplerArrayCapacity
bool shaderSampledImageArrayDynamicIndexing
std::future< vkb::Instance > future