I get a very strange crash (in debug mode only) when using the move assignment operator with a r-value:
Cleaning and recompiling the code in debug mode does not help. The crash occurs before VulkanBuffer::operator=() is called.
// ok
//VulkanBuffer myBuffer(logicalDevice, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE);
//buffer_ = std::move(myBuffer);
// ok
//VulkanBuffer myBuffer = VulkanBuffer(logicalDevice, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE);
//buffer_ = std::move(myBuffer);
// crash in debug mode, release mode works fine
//buffer_ = std::move( VulkanBuffer(logicalDevice, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE) );
// crash in debug mode, release mode works fine
buffer_ = VulkanBuffer(logicalDevice, bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_SHARING_MODE_EXCLUSIVE);
I get this exception:
Exception thrown at 0x0000000059F95E35 (nvoglv64.dll) in Vulkan.exe: 0xC0000005: Access violation reading location 0xFFFFFFFFFFFFFFFF.
This exception occurs in the VulkanBuffer non-default constructor
Can anyone shed some light on this? It seems to me that the code should be equivalent.
The actual code is as below:
Declaration of VulkanBuffer
#pragma once
#include "VulkanLogicalDevice.h"
#include "Vertex.h"
#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>
#include <memory>
#include <vector>
class VulkanCommandPool;
class VulkanBuffer
{
public:
VulkanBuffer();
VulkanBuffer(const std::shared_ptr<VulkanLogicalDevice>& logicalDevice, VkDeviceSize deviceSizeInbytes, VkBufferUsageFlags bufferUsageFlags, VkSharingMode sharingMode);
~VulkanBuffer();
VulkanBuffer(const VulkanBuffer &rhs) = delete;
VulkanBuffer & operator=(const VulkanBuffer &rhs) = delete;
VulkanBuffer(VulkanBuffer &&rhs) = delete;
VulkanBuffer & operator=(VulkanBuffer &&rhs);
VkBuffer & handle() { return buffer_; }
const VkBuffer & handle() const { return buffer_; }
const VkBufferCreateInfo & getBufferInfo() const { return createInfo_; }
void copyDataFrom(const VulkanBuffer & rhs, const VulkanCommandPool &commandPool, VkDeviceSize dataSizeInBytes);
friend void swap(VulkanBuffer &lhs, VulkanBuffer &rhs);
private:
std::shared_ptr<VulkanLogicalDevice> logicalDevice_;
VkBufferCreateInfo createInfo_;
VkBuffer buffer_;
};
Definition of VulkanBuffer
#include "VulkanBuffer.h"
#include "VulkanCommandPool.h"
#include "Vertex.h"
#include <iostream>
void swap(VulkanBuffer &lhs, VulkanBuffer &rhs)
{
std::swap(lhs.logicalDevice_, rhs.logicalDevice_);
std::swap(lhs.buffer_, rhs.buffer_);
std::swap(lhs.createInfo_, rhs.createInfo_);
}
VulkanBuffer::VulkanBuffer()
: buffer_(VK_NULL_HANDLE)
{}
/// \param logicalDevice Vulkan device
/// \param deviceSizeInbytes number of bytes of the vertices to be stored in this vertex buffer
/// \param bufferUsageFlags what will the buffer be used for, eg VK_BUFFER_USAGE_VERTEX_BUFFER_BIT
/// \param sharingMode is the buffer used by more than one queue family, eg: VK_SHARING_MODE_EXCLUSIVE, VK_SHARING_MODE_CONCURRENT
VulkanBuffer::VulkanBuffer(const std::shared_ptr<VulkanLogicalDevice>& logicalDevice, VkDeviceSize deviceSizeInbytes, VkBufferUsageFlags bufferUsageFlags, VkSharingMode sharingMode)
: logicalDevice_(logicalDevice), buffer_(VK_NULL_HANDLE)
{
createInfo_.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
createInfo_.size = deviceSizeInbytes;
createInfo_.usage = bufferUsageFlags; // indicates this data is for a vertex buffer.
createInfo_.sharingMode = sharingMode; // ownership by one queue family or multiple
if (vkCreateBuffer(logicalDevice->handle(), &createInfo_, nullptr, &buffer_) != VK_SUCCESS) {
throw std::runtime_error("failed to create buffer!");
}
}
VulkanBuffer::~VulkanBuffer()
{
if (buffer_ != VK_NULL_HANDLE)
vkDestroyBuffer(logicalDevice_->handle(), buffer_, nullptr);
}
VulkanBuffer & VulkanBuffer::operator=(VulkanBuffer &&rhs)
{
swap(*this, rhs);
return *this;
}
void VulkanBuffer::copyDataFrom(const VulkanBuffer & rhs, const VulkanCommandPool &commandPool, VkDeviceSize dataSizeInBytes)
{
if (buffer_ == VK_NULL_HANDLE || rhs.buffer_ == VK_NULL_HANDLE)
{
std::cout << "Illegal VulkanBuffer::copyDataFrom(), one or more buffers not initialized.\n";
return;
}
VkCommandBufferAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = commandPool.handle();
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(logicalDevice_->handle(), &allocInfo, &commandBuffer);
VkCommandBufferBeginInfo beginInfo = {};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
vkBeginCommandBuffer(commandBuffer, &beginInfo);
VkBufferCopy copyRegion = {};
copyRegion.size = dataSizeInBytes;
vkCmdCopyBuffer(commandBuffer, rhs.handle(), buffer_, 1, ©Region);
vkEndCommandBuffer(commandBuffer);
VkSubmitInfo submitInfo = {};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
vkQueueSubmit(logicalDevice_->getGraphicsQueue(), 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(logicalDevice_->getGraphicsQueue());
vkFreeCommandBuffers(logicalDevice_->handle(), commandPool.handle(), 1, &commandBuffer);
}
edit: added this as part of Jherico's suggestion: When the class crashes, the following is printed from the class functions:
handle: 0000000000000000 VulkanBuffer: default ctor()
handle: 0000000000000000 VulkanBuffer: non default ctor() start
handle: 000001B2A00C84E0 VulkanBuffer: non default ctor() end
handle: 0000000000000000 VulkanBuffer: non default ctor() start
when it does not crash, the following is printed
handle: 0000000000000000 VulkanBuffer: default ctor()
handle: 0000000000000000 VulkanBuffer: non default ctor() start
handle: 000001989ADB56E0 VulkanBuffer: non default ctor() end
handle: 0000000000000000 VulkanBuffer: non default ctor() start
handle: 000001989ADB6310 VulkanBuffer: non default ctor() end
handle: 0000000000000000 VulkanBuffer: operator=()
handle lhs: 0000000000000000 handle rhs: 000001989ADB6310 VulkanBuffer: Swap() start
handle lhs: 000001989ADB6310 handle rhs: 0000000000000000 VulkanBuffer: Swap() end
handle: 000001989ADB6310 VulkanBuffer: copyDataFrom()
handle: 0000000000000000 VulkanBuffer: dtor() // instruction re-ordered here?
handle: 000001989ADB56E0 VulkanBuffer: dtor()
You seem to have forgoten to fully initialize the VkBufferCreateInfo
(i.e. createInfo_
).
You only seem to partially initialize here:
createInfo_.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
createInfo_.size = deviceSizeInbytes;
createInfo_.usage = bufferUsageFlags; // indicates this data is for a vertex buffer.
createInfo_.sharingMode = sharingMode; // ownership by one queue family or multiple
That leaves these members uninitialized: pNext
, flags
, queueFamilyIndexCount
and pQueueFamilyIndices
. Notably if pNext
happens to be non-NULL
, the driver will try to dereference it (and crash because the pointer points nowhere).
This is the first kind of bugs you should be thinking of if something happens only in a Debug profile. In Release profile 99 % of the time uninitialized variable will be zero. The compiler does help you here and in Debug profile it fills uninitialized memory with (non-zero) garbage, so it crashes for you in order to discover an otherwisely hidden bug.
This suggests that you've got some sort of double-destruction of a buffer going on. You should have validation layers enabled while developing, because if you do so, they validation should catch this before it triggers a crash.
In terms of figuring out what exactly is going wrong, the easiest way is probably to put logging in your move assignment operator, ctor and dtor (which includes the handle of the buffer being passed to vkDestroyBuffer
) so that you can see what the actual sequence of calls is.
User contributions licensed under CC BY-SA 3.0