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.
i = std::shared_ptr<ImageView>();" That creates a default-constructedshared_ptr<T>. And a default-constructedshared_ptr<T>does not store aT; it stores anullptr. Which you then immediately deference. Presumably, you meantstd::make_shared<ImageView>. Also, why are you using two-stage object construction; why can't you haveImageView's constructor do whatImageView::Initializedoes?shared_ptr. Yes it should have beenmake_shared<T>! After correcting that, there was another bug in the code that I was able to track down. In ImageView::initialize() I was assigningdevice_in the inside of the if statement block where I was checking to see if it was anullptrinstead 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.