17#include <SDL2/SDL_vulkan.h>
35#include "common/config.h"
39#include "zeroerr/assert.h"
44#include <assimp/mesh.h>
45#include <assimp/matrix3x3.h>
46#include <assimp/matrix4x4.h>
47#include <assimp/vector3.h>
48#include <glm/gtc/matrix_transform.hpp>
59 if (!initialized)
return;
67 ownedCanvases.clear();
69 ownedGpuMeshes.clear();
70 ownedTextures.clear();
71 for (
auto &g : ownedGpuTextures) {
72 if (g->sampler) device->destroySampler(g->sampler);
74 ownedGpuTextures.clear();
75 texturesByPath.clear();
76 for (
auto &g : ownedGpuShaders) {
77 if (g->swapchainPipeline) device->destroyPipeline(g->swapchainPipeline);
78 if (g->offscreenPipeline) device->destroyPipeline(g->offscreenPipeline);
79 if (g->mesh3dPipeline) device->destroyPipeline(g->mesh3dPipeline);
80 if (g->mesh3dXrayPipeline) device->destroyPipeline(g->mesh3dXrayPipeline);
83 ownedGpuShaders.clear();
85 destroySwapchainResources();
86 if (pipeline) device->destroyPipeline(pipeline);
87 if (solidAlphaPipeline) device->destroyPipeline(solidAlphaPipeline);
88 if (additiveSolidPipeline) device->destroyPipeline(additiveSolidPipeline);
89 if (pipelineLayout) device->destroyPipelineLayout(pipelineLayout);
90 if (texPipeline) device->destroyPipeline(texPipeline);
91 if (additiveTexPipeline) device->destroyPipeline(additiveTexPipeline);
92 if (opaqueTexPipeline) device->destroyPipeline(opaqueTexPipeline);
93 if (texPipelineLayout) device->destroyPipelineLayout(texPipelineLayout);
94 if (shaderPipelineLayout) device->destroyPipelineLayout(shaderPipelineLayout);
95 if (mesh3dPipeline) device->destroyPipeline(mesh3dPipeline);
96 destroyOffscreen3DResources();
97 if (mesh3dPipelineLayout) device->destroyPipelineLayout(mesh3dPipelineLayout);
98 if (mesh3dShaderPipelineLayout) device->destroyPipelineLayout(mesh3dShaderPipelineLayout);
99 mesh3dSetLayoutUnique.reset();
100 mesh3dFrameSlots.clear();
101 destroyVoxelRectResources();
102 if (mesh3dClusteredPipeline) device->destroyPipeline(mesh3dClusteredPipeline);
103 if (mesh3dClusteredPipelineLayout) device->destroyPipelineLayout(mesh3dClusteredPipelineLayout);
104 mesh3dClusteredSetLayoutUnique.reset();
105 mesh3dClusteredFrameSlots.clear();
106 destroyShadowResources();
107 destroyGBufferResources();
108 destroySceneColorResources();
109 destroyUiColorResources();
110 auto destroyBuf = [&](vkb::GenericBuffer &
b) {
b.release(); };
111 for (
auto &st : clusteredStorages) {
112 destroyBuf(st.lightsBuf);
113 destroyBuf(st.tableBuf);
114 destroyBuf(st.indicesBuf);
116 clusteredStorages.clear();
117 if (lit2dPipeline) device->destroyPipeline(lit2dPipeline);
118 if (offscreenLitPipeline) device->destroyPipeline(offscreenLitPipeline);
119 if (lit2dPipelineLayout) device->destroyPipelineLayout(lit2dPipelineLayout);
120 lit2dSetLayoutUnique.reset();
121 for (
auto &
m : lit2dSets)
m.clear();
123 offscreenLit2dSets.clear();
124 destroyBuf(offscreenLighting2dUbo);
125 if (offscreenSolidPipeline) device->destroyPipeline(offscreenSolidPipeline);
126 if (offscreenSolidAlphaPipeline) device->destroyPipeline(offscreenSolidAlphaPipeline);
127 if (offscreenAdditiveSolidPipeline) device->destroyPipeline(offscreenAdditiveSolidPipeline);
128 if (offscreenTexPipeline) device->destroyPipeline(offscreenTexPipeline);
129 if (offscreenAdditiveTexPipeline) device->destroyPipeline(offscreenAdditiveTexPipeline);
130 if (offscreenOpaqueTexPipeline) device->destroyPipeline(offscreenOpaqueTexPipeline);
131 if (offscreenRenderPass) device->destroyRenderPass(offscreenRenderPass);
132 texSetLayoutUnique.reset();
133 if (descriptorPool) device->destroyDescriptorPool(descriptorPool);
134 if (uploadPool) device->destroyCommandPool(uploadPool);
135 if (renderpass) device->destroyRenderPass(renderpass);
136 if (surface) inst.instance.destroySurfaceKHR(surface);
140 StartupStage initStage(
"graphics: initWithWindow (total)");
147 if (sdlWindow == nativeWindow && surface)
return;
148 sdlWindow = nativeWindow;
149 recreateSurfaceForResume();
154 rebuildSwapchainIfNeeded();
157 sdlWindow = nativeWindow;
158 auto *window =
static_cast<SDL_Window *
>(nativeWindow);
159 ASSERT(window !=
nullptr);
160 if (!window)
throw Exception(
"Graphics::initWithWindow: null SDL_Window");
162 unsigned int count = 0;
163 if (!SDL_Vulkan_GetInstanceExtensions(window, &count,
nullptr))
164 throw Exception(
"SDL_Vulkan_GetInstanceExtensions failed: %s", SDL_GetError());
166 std::vector<const char *> extNames(count);
167 if (!SDL_Vulkan_GetInstanceExtensions(window, &count, extNames.data()))
168 throw Exception(
"SDL_Vulkan_GetInstanceExtensions failed: %s", SDL_GetError());
170 vkb::InstanceBuilder builder;
171 builder.require_api_version(1, 0);
172#if !defined(EVENGINE_IOS)
175 const char *vkVal = std::getenv(
"EVENGINE_VULKAN_VALIDATION");
176 const bool wantValidation = vkVal && vkVal[0] !=
'\0' && vkVal[0] !=
'0';
177 if (wantValidation) {
178 builder.request_validation_layers();
179 builder.use_default_debug_messenger();
180 std::printf(
"EVEngine: Vulkan validation layers enabled (EVENGINE_VULKAN_VALIDATION)\n");
183 for (
auto *
name : extNames) builder.enable_extension(
name);
184#if defined(EVENGINE_MACOSX) || defined(EVENGINE_IOS)
187 builder.set_headless(
true);
191 if (vkb::SystemInfo::query().is_extension_available(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME)) {
192 builder.enable_extension(VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME);
193 builder.add_flags(vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR);
198 inst = builder.build();
200 VkSurfaceKHR rawSurface = VK_NULL_HANDLE;
201 if (!SDL_Vulkan_CreateSurface(window,
static_cast<VkInstance
>(inst.instance), &rawSurface))
202 throw Exception(
"SDL_Vulkan_CreateSurface failed: %s", SDL_GetError());
203 surface = rawSurface;
207 StartupStage stage(
" vulkan: physical device + device");
208 auto selector = inst.selectPhysicalDevice();
209 selector.set_surface(surface).set_minimum_version(1, 0);
210#if defined(EVENGINE_MACOSX) || defined(EVENGINE_IOS)
211 selector.add_required_extension(
"VK_KHR_portability_subset");
213 auto phys = selector.select();
215 const vk::PhysicalDeviceFeatures supported = phys->getFeatures();
216 if (supported.samplerAnisotropy) {
217 phys.features.samplerAnisotropy = VK_TRUE;
218 maxSamplerAnisotropy = phys.properties.limits.maxSamplerAnisotropy;
219 if (maxSamplerAnisotropy < 1.f) maxSamplerAnisotropy = 1.f;
221 maxSamplerAnisotropy = 1.f;
224 vkb::DeviceBuilder deviceBuilder = phys.createDevice();
225 device = deviceBuilder.build();
226 maxSamplerAnisotropy = device.caps.maxSamplerAnisotropy;
229 uploadPool = device.createCommandPool();
232 SDL_Vulkan_GetDrawableSize(window, &pw, &ph);
234 SDL_GetWindowSize(window, &lw, &lh);
238 StartupStage stage(
" vulkan: swapchain + solid pipelines");
239 createSwapchainAndPipeline();
242 StartupStage stage(
" vulkan: textured/lit2d pipelines");
243 createTexturedPipeline();
247 createMesh3DPipeline();
251 createMesh3DClusteredPipeline();
255 createVoxelRectPipeline();
262 StartupStage stage(
" vulkan: shadow resources (3x2048^2 maps + pipelines)");
263 createShadowResources();
267 const uint8_t whitePixel[4] = {255, 255, 255, 255};
273 if (!initialized)
return;
274 if (swapchainPassOpen || sceneColorPassOpen) abortOpen3DFrame();
282 destroySwapchainResources();
284 swapchain = vkb::Swapchain{};
286 inst.instance.destroySurfaceKHR(surface);
287 surface = VK_NULL_HANDLE;
290 swapchainDirty =
true;
293void Graphics::destroySwapchainResources() {
294 presentRecording = {};
296 presentModel.destroy();
297 presentModel = vkb::Present{};
298 depthImage = vkb::DepthStencilImage{};
299 destroyReadbackResources();
302 for (
auto &fb : frame2dBuffers) releaseFrame2dBuffers(fb);
303 frame2dBuffers.clear();
304 releaseFrame2dBuffers(offscreenBuffers);
307 for (
auto &ubo : lighting2dUboSlots) ubo.release();
308 lighting2dUboSlots.clear();
314uint32_t Graphics::frameSlotCount()
const {
315 uint32_t
n = presentModel.frames_in_flight;
316 if (
n == 0)
n = swapchain.image_count;
320size_t Graphics::currentFrameSlot()
const {
321 const uint32_t
n = frameSlotCount();
322 size_t slot = presentRecording ? presentRecording.slot().index : swapchain.current_frame;
323 return (
n > 0 && slot >=
n) ? 0 : slot;
326vk::CommandBuffer &Graphics::currentPresentCb() {
327 if (offscreen3DPassOpen && offscreen3DCB)
328 return offscreen3DCB;
330 return swapchainPass.commandBuffer();
331 return presentRecording.commandBuffer();
334vkb::FrameSlot Graphics::frameToken()
const {
335 if (offscreen3DPassOpen)
return vkb::FrameSlot{0};
336 if (presentRecording)
return presentRecording.slot();
337 return vkb::FrameSlot::gpuIdle();
340void Graphics::waitForSharedGpuResources() {
341 presentModel.waitForAllFrames();
344void Graphics::invalidateTextureBindings() {
345 for (
auto &frame : mesh3dFrameSlots)
347 for (
auto &frame : mesh3dClusteredFrameSlots)
349 for (
auto &
m : lit2dSets)
m.
clear();
350 offscreenLit2dSets.clear();
352 voxelRectSets.clear();
355Graphics::Frame2DBuffers &Graphics::currentFrame2DBuffers() {
356 return currentSlot(frame2dBuffers, frameSlotCount(), currentFrameSlot());
359Graphics::Mesh3dFrameSlots &Graphics::currentMesh3dFrameSlots() {
360 return currentSlot(mesh3dFrameSlots, frameSlotCount(), currentFrameSlot());
363Graphics::Mesh3dClusteredFrameSlots &Graphics::currentMesh3dClusteredFrameSlots() {
364 return currentSlot(mesh3dClusteredFrameSlots, frameSlotCount(), currentFrameSlot());
367vkb::GenericBuffer &Graphics::currentLighting2dUbo() {
368 auto &ubo = currentSlot(lighting2dUboSlots, frameSlotCount(), currentFrameSlot());
371 ubo.allocate(frameToken(), device, vk::BufferUsageFlagBits::eUniformBuffer,
sizeof(Lighting2DUBO),
372 vk::MemoryPropertyFlagBits::eHostVisible |
373 vk::MemoryPropertyFlagBits::eHostCoherent);
377std::unordered_map<Graphics::LitSetKey, vkb::BoundSet, Graphics::LitSetKeyHash> &
378Graphics::currentLit2dSets() {
379 return currentSlot(lit2dSets, frameSlotCount(), currentFrameSlot());
382Graphics::ClusteredStorage &Graphics::currentClusteredStorage() {
383 return currentSlot(clusteredStorages, frameSlotCount(), currentFrameSlot());
386Graphics::VoxelInstanceFrame &Graphics::currentVoxelInstanceFrame() {
387 return currentSlot(voxelInstanceFrames, frameSlotCount(), currentFrameSlot());
390Graphics::ShadowMapSlot &Graphics::currentShadowMap() {
391 ASSERT(!shadowMaps.empty());
392 return shadowMaps[currentFrameSlot() % shadowMaps.size()];
395vk::ImageView Graphics::currentShadowArrayView() {
396 if (shadowMaps.empty())
return vk::ImageView{};
397 return currentShadowMap().image.arrayView();
400Graphics::GBufferSlot *Graphics::currentGBufferSlot() {
401 if (gbufferSlots.empty())
return nullptr;
402 return &gbufferSlots[currentFrameSlot() % gbufferSlots.size()];
405Graphics::SceneColorSlot *Graphics::currentSceneColorSlot() {
406 if (sceneColorSlots.empty())
return nullptr;
407 return &sceneColorSlots[currentFrameSlot() % sceneColorSlots.size()];
411 auto *slot = currentSceneColorSlot();
412 if (!slot || !slot->colorTex.gpuHandle)
return nullptr;
413 return &slot->colorTex;
416Graphics::UiColorSlot *Graphics::currentUiColorSlot() {
417 if (uiColorSlots.empty())
return nullptr;
418 return &uiColorSlots[currentFrameSlot() % uiColorSlots.size()];
421void Graphics::dropPendingOffscreenPasses() {
422 shadowPendingMask = 0;
423 for (
auto &
d : shadowCascadeDraws)
d.
clear();
424 gbufferPending =
false;
425 gbufferPassDraws.clear();
428bool Graphics::beginSwapchainRenderPass() {
429 if (!beginPresentCommandBuffer()) {
430 dropPendingOffscreenPasses();
433 recordDeferredFrameGraph();
434 beginSwapchainColorPass();
438void Graphics::beginSwapchainColorPass() {
439 std::array<vk::ClearValue, 2> clears{};
440 clears[0].color = vk::ClearColorValue(
441 std::array<float, 4>{clearColor.r, clearColor.g, clearColor.b, clearColor.a});
442 clears[1].depthStencil = vk::ClearDepthStencilValue{1.0f, 0};
443 swapchainPass = presentRecording.beginRenderPass(renderpass, clears.data(), uint32_t(clears.size()));
446bool Graphics::rebuildSwapchainIfNeeded() {
447 const bool wantRecreate =
448 surfaceNeedsRecreate.load() || swapchainDirty || presentModel.needs_recreate;
449 if (!wantRecreate)
return true;
450 if (!isRenderSurfaceStable())
return false;
452 if (surfaceNeedsRecreate.exchange(
false))
453 recreateSurfaceForResume();
454 if (presentModel.needs_recreate) {
455 presentModel.needs_recreate =
false;
456 swapchainDirty =
true;
458 if (swapchainDirty) {
460 createSwapchainAndPipeline();
465bool Graphics::beginPresentCommandBuffer() {
466 for (
int attempt = 0; attempt < 3; ++attempt) {
467 if (!rebuildSwapchainIfNeeded())
return false;
468 presentRecording = presentModel.begin();
469 if (presentRecording)
return true;
471 if (presentModel.needs_recreate) {
472 presentModel.needs_recreate =
false;
473 swapchainDirty =
true;
481bool Graphics::isRenderSurfaceReady()
const {
482#if defined(EVENGINE_ANDROID) || defined(EVENGINE_IOS)
483 auto *
window =
static_cast<SDL_Window *
>(sdlWindow);
484 if (!window)
return false;
488 SDL_Vulkan_GetDrawableSize(window, &pw, &ph);
489 return pw > 0 && ph > 0;
495bool Graphics::isRenderSurfaceStable() {
496#if defined(EVENGINE_ANDROID) || defined(EVENGINE_IOS)
497 auto *
window =
static_cast<SDL_Window *
>(sdlWindow);
498 if (!window)
return false;
500 SDL_Vulkan_GetDrawableSize(window, &pw, &ph);
501 if (pw <= 0 || ph <= 0) {
502 surfaceStableFrames = 0;
505 if (pw != pendingSurfaceW || ph != pendingSurfaceH) {
507 pendingSurfaceW = pw;
508 pendingSurfaceH = ph;
509 surfaceStableFrames = 1;
513 if (surfaceStableFrames < 3) {
514 ++surfaceStableFrames;
523void Graphics::recreateSurfaceForResume() {
528 if (!initialized)
return;
529 auto *
window =
static_cast<SDL_Window *
>(sdlWindow);
535 destroySwapchainResources();
537 swapchain = vkb::Swapchain{};
539 inst.instance.destroySurfaceKHR(surface);
540 surface = VK_NULL_HANDLE;
544 VkSurfaceKHR rawSurface = VK_NULL_HANDLE;
545 if (!SDL_Vulkan_CreateSurface(window,
static_cast<VkInstance
>(inst.instance), &rawSurface))
546 throw Exception(
"resume: SDL_Vulkan_CreateSurface failed: %s", SDL_GetError());
547 surface = rawSurface;
548 device.surface = surface;
551 int pw = 0, ph = 0, lw = 0, lh = 0;
552 SDL_Vulkan_GetDrawableSize(window, &pw, &ph);
553 SDL_GetWindowSize(window, &lw, &lh);
554 if (pw > 0 && ph > 0)
557 swapchainDirty =
true;
561 if (!initialized)
return;
564 if (!isRenderSurfaceReady()) {
567 if (swapchainPassOpen)
571 hasPendingClear =
false;
std::vector< std::pair< WindowDestroyedCallback, void * > > windowDestroyedCallbacks_
std::unique_ptr< AntiAliasing > pipelineAA_
void clearPresentOverlay()
Drop the present overlay callback (window gone; re-register on UI init).
std::unique_ptr< RenderControl > renderControl_
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
Called by the Window module when the native window backing the render surface is destroyed (window cl...
void draw(eve::graphics::Graphics *gfx, const glm::mat4 &matrix) const override
Draws the object with the specified transformation matrix.
Texture * getSceneColorTexture() override
Sampleable 3D color target for the current frame (RGB = lit, A = linear depth). Valid after begin3DFr...
std::string getBackendName() const override
Renderer backend id used by sibling modules (e.g. Gpgpu).
bool isCanvasActive() const override
void initWithWindow(void *nativeWindow) override
Bind to an existing native window (SDL_Window*) and create Vulkan device/swapchain....
void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override
Sets the current graphics display viewport dimensions.
Texture * newTexture(int width, int height, const uint8_t *rgba, bool repeatU=false, bool repeatV=false) override
void clear(std::optional< Color > color, std::optional< int > stencil, std::optional< double > depth) override
WidgetDesc window(std::string title, std::vector< WidgetDesc > children, std::string id)
Top-level window widget with a title bar.