0

I'm following vulkan-tutorial and I've successfully rendered a spinning square. I'm at the point in the lessons right before applying textures. Before moving on within the lessons, I've been modularizing the code into a framework of interfaces one piece at a time. I have successfully managed to extract various Vulkan objects out of the main engine class into their own classes. Each of these class objects has an interface with an initialize, create, and cleanup function at a minimum.

I've done this with a Buffer class that is an abstract base class that my IndexBuffer, VertexBuffer, and UniformBuffer all derived from. I've done this with my CommandPool class, SyncObjects(VkSemaphore and VkFence) classes, my Pipelines(only MainGraphicsPipeline for now), and my SwapChain.

With all of these in their own classes, I'm now storing these classes as either shared_ptr<Class> or vector<shared_ptr<Class>> within my classes that have internal instances of these. This is the design flow that I've been staying with.

Everything was working perfectly fine until I started to have VkImageView types contained within their own class. Within my SwapChainclass it contained the private member:

std::vector<VkImageView> imageViews_ 

and these member functions that work on them:

void SwapChain::cleanupImageViews() {
    for (auto imageView : imageViews_) {
        vkDestroyImageView(*device_, imageView, nullptr);
    }
}

void SwapChain::createImageViews() {
    imageViews_.resize(images_.size());

    for (size_t i = 0; i < images_.size(); i++) {
        VkImageViewCreateInfo createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
        createInfo.image = images_[i];
        createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
        createInfo.format = imageFormat_;
        createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
        createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
        createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
        createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
        createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        createInfo.subresourceRange.baseMipLevel = 0;
        createInfo.subresourceRange.levelCount = 1;
        createInfo.subresourceRange.baseArrayLayer = 0;
        createInfo.subresourceRange.layerCount = 1;

        if (vkCreateImageView(*device_, &createInfo, nullptr, &imageViews_[i]) != VK_SUCCESS) {
            throw std::runtime_error("failed to create image views!");
        }
    }
}

With my code in this state, everything works fine. I'm able to render a spinning colored square, I can resize the window and close the window with 0 errors from both Visual Studio and from Vulkan Layers.


When I change to this pattern that I've done before:

std::vector<std::shared_ptr<ImageView>> imageViews_;

and my functions become:

void SwapChain::cleanupImageViews() {
    for (auto& imageView : imageViews_ ) {
        imageView->cleanup();
    }
}

void SwapChain::createImageViews() {
    imageViews_.resize(images_.size());

    for(auto& i : imageViews_) {
        i = std::shared_ptr<ImageView>();
        i->initialize(device_);
    }

    for (size_t i = 0; i < images_.size(); i++ ) {
        imagesViews_[i]->create(images_[i], imageFormat_);
    }
}

It fails when It calls the create() for the ImageViews giving me an unhandled exception: access read/write violation stating that the "this" pointer within my ImageView class is nullptr.


Here is what my ImageView class looks like:

ImageView.h

#pragma once

#include "utility.h"

namespace ForceEngine {
    namespace vk {

        class ImageView {
        private:
            VkImageView imageView_;
            VkDevice* device_;
            VkImage image_;
            VkFormat format_;

        public:
            ImageView() = default;
            ~ImageView() = default;

            void initialize(VkDevice* device);
            void create(VkImage image, VkFormat format);
            void cleanup();

            VkImageView* get() { return &imageView_; }

        private:
            void createImageView();        
        };

    } // namespace vk
} // namespace ForceEngine

ImageView.cpp

#include "ImageView.h"

namespace ForceEngine {
    namespace vk {
        void ImageView::initialize(VkDevice* device) {
            if (device == nullptr) {
                throw std::runtime_error("failed to initialize ImageView: device was nullptr!");
                device_ = device;
            }
        }

        void ImageView::create(VkImage image, VkFormat format) {
            //if (image == nullptr) throw std::runtime_error("failed to create Image View: image was nullptr!");
            //if (format == nullptr) throw std::runtime_error("failed to create Image View: format was nullptr!");
            image_ = image;  // This is where it is throwing the exception.
            format_ = format;
            createImageView();
        }

        void ImageView::createImageView() {
            VkImageViewCreateInfo createInfo{};
            createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
            createInfo.image = image_;
            createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
            createInfo.format = format_;
            createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
            createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
            createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
            createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
            createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
            createInfo.subresourceRange.baseMipLevel = 0;
            createInfo.subresourceRange.levelCount = 1;
            createInfo.subresourceRange.baseArrayLayer = 0;
            createInfo.subresourceRange.layerCount = 1;

            if (vkCreateImageView(*device_, &createInfo, nullptr, &imageView_) != VK_SUCCESS) {
                throw std::runtime_error("failed to create image views!");
            }
        }

        void ImageView::cleanup() {
            vkDestroyImageView(*device_, imageView_, nullptr);
        }

    } // namespace vk
} // namespace ForceEngine

Within my class, I've tried having image_ as a pointer and as a non-pointer and as for the signature of create(), I've tried passing the parameters by copy, reference, pointer, const reference, const pointer, etc. and to no avail, none of the above has worked. Everything keeps causing an exception. I don't know what's causing the access violation. For some reason, the ImageView class is not properly allocating memory for the image_ member as it states that this was nullptr for this class or it can't write to memory, etc. Yet, I've followed this same pattern for all of my other Vulkan Objects and did not have this issue until now.

What are the takes on the usages and proper setup of VkImageView and VkImage within the context of the SwapChain? Currently, the VkImages are still stored within the SwapChain as std::vector<VkImage> I can create it successfully within the class directly, but when I try to extract the VkImageView out into its own class object is when I start to run into this problem. I'm learning Vulkan through this tutorial and I'm starting to get a grasp of how it is designed, but I'm still no expert at the API. Right now any help would be appreciated. And, yes I've stepped through the debugger, I've watched my variables, I've watched the call stack, and for the life of me, I'm completely stumped. If you need more information than this please don't hesitate to ask.

5
  • 1
    "Before moving on within the lessons, I've been modularizing the code into a framework of interfaces one piece at a time." That is a very dangerous thing. Modularization ought to wait until you have some command over the whole of the API, not just disparate fragments of it. Pre-emptively doing this, for example, might lead you to have separate object types for different uses of buffers, which is absolutely the wrong abstraction to build (since the API does not directly make such a restriction). Commented May 2, 2020 at 20:24
  • 2
    "i = std::shared_ptr<ImageView>();" That creates a default-constructed shared_ptr<T>. And a default-constructed shared_ptr<T> does not store a T; it stores a nullptr. Which you then immediately deference. Presumably, you meant std::make_shared<ImageView>. Also, why are you using two-stage object construction; why can't you have ImageView's constructor do what ImageView::Initialize does? Commented May 2, 2020 at 20:28
  • @NicolBolas I had overlooked the shared_ptr. Yes it should have been make_shared<T>! After correcting that, there was another bug in the code that I was able to track down. In ImageView::initialize() I was assigning device_ in the inside of the if statement block where I was checking to see if it was a nullptr instead of after it. After fixing those two small bugs, everything is now working correctly. The reason I don't want to pass values to the constructors is that I may want to construct the object at one point and to initialize and create it at a later point in time. Commented May 2, 2020 at 22:32
  • @NicolBolas Right up to the point I had asked this question, I was working on this project for about 6 hours... It just got to the point where either I truly needed a break, or just needed a second set of eyes! About modularizing. I know how to do that, as for the API it's a learning process and I'll learn the ins and outs as I go. I'm quite familiar with OpenGL and DirectX and yes I know that Vulkan is neither of them, yet I understand the core principles behind the scenes! I'm not just reading from this website, I'm also using Khronos's Registry, and other sources. Commented May 2, 2020 at 22:35
  • @NicolBolas I appreciate your feedback, suggestions, and tips, I always take them into consideration. I may not always follow them, but I always consider. Now I'm able to move on. I'm modularizing this for that the "Engine" was becoming very cumbersome. Almost 500 lines of code just in the header and 1-2k lines in the CPP file. I have a few more objects to move out as independent classes and once I've finished with that and remove all of the commented code, My header will be maybe 100 lines and its CPP file down to about 500. Maybe I was using the wrong term modularizing and meant refactoring. Commented May 2, 2020 at 22:39

1 Answer 1

3

User nicol-bolas pointed out in the comment section that I had i = std::shared_ptr<ImageView>() and that I should have had i = std::make_shared<ImageView>() and yes that is correct and that is how my other independent classes are being created. This was an overlooked typo mistake. So I give credit to NicolBolas for that. However, after fixing that bug, it allowed me to find and fix the real bug.

The actual bug that was causing the Unhandled Exception had nothing to do with the ImageView, VkImageView, VkImage objects. The real culprit was within the ImageView class itself within its initialize() method. I was assigning its member device_ within the scope of the if statement, where I was checking to see if the device pointer being passed in was nullptr or not.

I originally had this:

void ImageView::initialize(VkDevice* device) {
    if (device == nullptr) { throw std::runtime_error("failed to initialize Image View: device was nullptr!");
        device_ = nullptr;
    }
}

And the device_ was never being set. It should have been:

void ImageView::initialize(VkDevice* device) {
    if (device == nullptr) throw std::runtime_error("failed to initialize Image View: device was nullptr!");
        device_ = nullptr;
}

Now the code works again and I have a spinning colored square. The code exits with 0 errors and no messages from Vulkan Layers.

I had simply overlooked the {} within the if statement of the function.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.