In our previous post, we implemented the support to use VertexBuffer objects to provide the vertex data we want to draw. This time we are going to do something completely different and focus instead on the support for resizing the main window of our application.
-- Prepare the window traits for the main window creation. function Class:createWindowTraits() local wt = nv.Window.Traits("main_window", 800, 600) wt.title = "NervSeed main window" wt.fullscreen = false wt.hidden = false wt.resizable = false return wt end
-- Prepare the window traits for the main window creation. ---@return nv.Window.Traits function Class:createWindowTraits() local wt = Class.super:createWindowTraits() logDEBUG("Setting window traits as resizable.") wt.resizable = true return wt end
2022-11-10 09:14:51.643215 [DEBUG] Resizing swapchain image specific cmd buffers to 3 2022-11-10 09:14:51.646637 [DEBUG] Done recording 3 command buffers. 2022-11-10 09:17:32.339473 [FATAL] Cannot present image from swapchain 2022-11-10 09:17:32.339685 [FATAL] Error in lua app: C++ exception
void VulkanRenderer::set_target_window(nv::Window* window) { // For now we assume the window is not Null: CHECK(window != nullptr, "Invalid target window."); // Assign the window: _window = window; // create the window surface: auto* eng = VulkanEngine::instance(); // Note that we use the default VulkanInstance below: auto* inst = eng->get_instance(0); _surface = inst->create_presentation_surface(_window->get_handle()); // Reset the swapchain pointer as we don't want to use it as previous // swapchain: _swapchain = nullptr; // We should also connect a resize handler on the window here: _window->set_resize_handler([this](int width, int height) { logDEBUG("Window resized to {}x{}", width, height); rebuild_swap_chain(); }); // Assign the default swapchain: rebuild_swap_chain(); }
void VulkanRenderer::rebuild_swap_chain() { // Should first ensure that we have a window pointer here: CHECK(_window != nullptr, "Invalid window pointer."); CHECK(_surface != nullptr, "Invalid window surface."); // get the window extent: int width = 0; int height = 0; _window->get_drawable_size(width, height); logDEBUG("Creating swapchain with drawable size {}x{}", width, height); CHECK(width > 0 && height > 0, "Invalid window dimensions."); auto extent = VkExtent2D{(U32)width, (U32)height}; // create the swapchain: _swapchain = _device->create_swap_chain(_surface.get(), extent, _presentFormat); U32 numImages = _swapchain->get_num_images(); logDEBUG("Done creating swapchain with {} images", numImages); }
-- Create our render pass: function Class:createRenderPass() local adesc = nvk.VulkanAttachmentDescription() -- color attachment: local schain = self.vkeng:get_swap_chain(self.swapchain) adesc:setFormat(schain:get_image_format()) adesc:setSamples(vk.SampleCountFlagBits['1_BIT']) adesc:setLoadOp(vk.AttachmentLoadOp.CLEAR) adesc:setStoreOp(vk.AttachmentStoreOp.STORE) adesc:setStencilLoadOp(vk.AttachmentLoadOp.DONT_CARE) adesc:setStencilStoreOp(vk.AttachmentStoreOp.DONT_CARE) adesc:setInitialLayout(vk.ImageLayout.UNDEFINED) adesc:setFinalLayout(vk.ImageLayout.PRESENT_SRC_KHR) -- depth attachment: -- adesc:addItem(vk.Format.D24_UNORM_S8_UINT) adesc:addItem(vk.Format.D32_SFLOAT) adesc:setSamples(vk.SampleCountFlagBits['1_BIT']) adesc:setLoadOp(vk.AttachmentLoadOp.CLEAR) adesc:setStoreOp(vk.AttachmentStoreOp.DONT_CARE) adesc:setStencilLoadOp(vk.AttachmentLoadOp.DONT_CARE) adesc:setStencilStoreOp(vk.AttachmentStoreOp.DONT_CARE) adesc:setInitialLayout(vk.ImageLayout.UNDEFINED) adesc:setFinalLayout(vk.ImageLayout.DEPTH_STENCIL_ATTACHMENT_OPTIMAL) -- Setup a first subpass here: local sdesc = nvk.VulkanSubpassDescription() sdesc:addColorAttachment(0, vk.ImageLayout.COLOR_ATTACHMENT_OPTIMAL) sdesc:setDepthStencilAttachment(1, vk.ImageLayout.DEPTH_STENCIL_ATTACHMENT_OPTIMAL) local ddesc = nvk.VulkanSubpassDependency() ddesc:setSrcStageMask(vk.PipelineStageFlagBits.COLOR_ATTACHMENT_OUTPUT_BIT + vk.PipelineStageFlagBits.EARLY_FRAGMENT_TESTS_BIT) ddesc:setDstStageMask(vk.PipelineStageFlagBits.COLOR_ATTACHMENT_OUTPUT_BIT + vk.PipelineStageFlagBits.EARLY_FRAGMENT_TESTS_BIT) ddesc:setSrcAccessMask(0) ddesc:setDstAccessMask(vk.AccessFlagBits.COLOR_ATTACHMENT_WRITE_BIT + vk.AccessFlagBits.DEPTH_STENCIL_ATTACHMENT_WRITE_BIT) local rpass = self.vkeng:create_render_pass(adesc, sdesc, ddesc) logDEBUG("Done creating render pass.") return rpass end
void VulkanRenderer::rebuild_swap_chain() { // Should first ensure that we have a window pointer here: CHECK(_window != nullptr, "Invalid window pointer."); CHECK(_surface != nullptr, "Invalid window surface."); CHECK(_renderpass != nullptr, "Invalid renderpass"); // Clear the framebuffers: _framebuffers.clear(); // Clear the previous list of images and views: _fbViews.clear(); // get the window extent: int width = 0; int height = 0; _window->get_drawable_size(width, height); logDEBUG("Creating swapchain with drawable size {}x{}", width, height); CHECK(width > 0 && height > 0, "Invalid window dimensions."); auto extent = VkExtent2D{(U32)width, (U32)height}; // create the swapchain: _swapchain = _device->create_swap_chain(_surface.get(), extent, _presentFormat); U32 numImages = _swapchain->get_num_images(); logDEBUG("Done creating swapchain with {} images", numImages); // Once the swapchain is reconstructed, we should also reconstruct the // framebuffers: _framebuffers.resize(numImages); // Get the attachment descriptions: const auto& adesc = _renderpass->get_attachment_descs(); U32 numAttachments = adesc.size(); // Index in the attachment list for the swapchain image itself: U32 swapchainImageIndex = 0; VulkanImageViewList views; for (U32 i = 0; i < numImages; ++i) { // Iterate on the list of attachment, preparing our views in the // process: views.clear(); for (U32 j = 0; j < numAttachments; ++j) { nv::RefPtr<VulkanImageView> view; if (j == swapchainImageIndex) { // This is where we should place the view on the swapchain // image: view = _swapchain->get_image(i)->create_view(); } else { // We must create the corresponding image first: const auto& att = adesc[j]; // Get the default usage for that attachment: auto usage = get_attachment_usage(att.format); auto img = _device->create_image( VK_IMAGE_TYPE_2D, att.format, {(U32)width, (U32)height, 1}, 1, 1, VK_SAMPLE_COUNT_1_BIT, usage); // Create the view for that image: view = img->create_view(); } // Store that view: views.push_back(view); _fbViews.push_back(view); } // Once we have all the views we can create the frambuffer: _framebuffers[i] = _device->create_framebuffer( _renderpass.get(), views, (U32)width, (U32)height, 1); } }
/** Add a command buffer specific to a given image index in the swapchain**/ void add_swapchain_img_cmd_buffer(VulkanCommandBuffer* cbuf, U32 imageIndex);
struct Callback { Callback(const Callback& rhs) = default; Callback(Callback&& rhs) = default; auto operator=(const Callback&) -> Callback& = default; auto operator=(Callback&&) -> Callback& = default; virtual ~Callback() = default; virtual void operator()() const = 0; };
class NVCORE_EXPORT LuaCallback : public Callback { NV_DECLARE_NO_COPY(LuaCallback) NV_DECLARE_NO_MOVE(LuaCallback) LuaCallback(lua_State* L, I32 idx); ~LuaCallback() override; void operator()() const override; luna::LuaFunction* func; };
inline void _lunaext_on_swapchain_updated(nvk::VulkanRenderer& obj, lua_State* L, luna::LuaFunction& func) { nv::RefPtr<nv::LuaCallback> cb = nv::create_ref_object<nv::LuaCallback>(func.state, func.index); obj.on_swapchain_updated(cb.get()); };
self.renderer:on_swapchain_updated(function() self:recordCommandBuffers(rpass, self.pipeline, vbuf) end)
// create the swapchain: // Note: we must provide the previous swapchain if the surface is not // changed: nv::RefPtr<VulkanSwapChain> prevSwapChain = _currentSwapChain; _currentSwapChain = _device->create_swap_chain( _surface.get(), extent, _presentFormat, prevSwapChain.get()); U32 numImages = _currentSwapChain->get_num_images(); logDEBUG("Done creating swapchain with {} images", numImages);
LuaCallback::LuaCallback(lua_State* L, I32 idx) : state(L), ref(LUA_NOREF) { CHECK(lua_isfunction(state, idx), "Invalid lua function."); // Duplicate the function at the given index: lua_pushvalue(L, idx); // Store the reference on the function and keep its ref index: // NOLINTNEXTLINE ref = luaL_ref(L, LUA_REGISTRYINDEX); CHECK(ref != LUA_NOREF, "Invalid ref."); } LuaCallback::~LuaCallback() { // destroy the ref: luaL_unref(state, LUA_REGISTRYINDEX, ref); }; void LuaCallback::operator()() const { // func->execute(); // Push the function: lua_rawgeti(state, LUA_REGISTRYINDEX, ref); // call with no args and no results: lua_call(state, 0, 0); }
auto operator=(RefPtr&& ref) noexcept -> RefPtr& { T* tmp_ptr = _ptr; // NOLINTNEXTLINE _ptr = ref._ptr; // we do not change the ptr count for the ref (since we add 1 and sub 1) // Just reset that ref ptr: ref._ptr = nullptr; // And unref our prev ptr: if (tmp_ptr) { tmp_ptr->unref(); } return *this; }
logDEBUG("Uninitializing VulkanApp") -- Wait untill all current operations are completed: self.vkeng:wait_device_idle() self.renderer:on_swapchain_updated(nil)
template <> struct LunaProvider<nv::RefObject> { using container_t = nv::RefPtr<nv::RefObject>; static auto get(const container_t& cont) -> nv::RefObject* { return cont.get(); }; static void set(container_t& cont, nv::RefObject* ptr) { logTRACE("Acquiring RefObject at {}", (const void*)ptr); cont = ptr; }; static void release(container_t& cont) { logTRACE("Releasing RefObject at {}", (const void*)cont.get()); // cont.release(); cont.reset(); }; static void destruct(lua_State* L, container_t& cont) { logTRACE("Resetting RefObject at {}", (const void*)cont.get()); cont.reset(); }; };
/** Get the swapchain width **/ auto get_swapchain_width() const -> U32; /** Get the swapchain height **/ auto get_swapchain_height() const -> U32;
-- Retrieve he width/height of the swapchain: local width = self.renderer:get_swapchain_width() local height = self.renderer:get_swapchain_height() -- Now we should update the viewport dimensions in our graphics pipeline config: local vp = cfg:getCurrentViewportState() vp:setViewport(width, height) -- Remove the previous pipeline: if self.pipeline ~= nil then self.vkeng:remove_pipeline(self.pipeline) end -- And we rebuild the pipeline with that config: self.pipeline = self.vkeng:create_graphics_pipeline(cfg, self.pipelineCache)