KKlouzal
KKlouzal

Reputation: 722

Vector Push_Back VS Emplace_Back

I have two different methods for adding elements to a vector.

GUI_Vertices.emplace_back();

GUI_Vertices.back().pos.x = ((float)x / 400) - 1.f;
GUI_Vertices.back().pos.y = ((float)y / 300) - 1.f;
GUI_Vertices.back().texCoord.x = u;
GUI_Vertices.back().texCoord.y = v;
GUI_Vertices.back().color.r = m_Color.r / 128;
GUI_Vertices.back().color.g = m_Color.g / 128;
GUI_Vertices.back().color.b = m_Color.b / 128;
GUI_Vertices.back().color.a = m_Color.a / 128;

The above code works, however I am forced to add a new element to the GUI_Vertices vector.

Vertex NewVertex;

NewVertex.pos.x = ((float)x / 400) - 1.f;
NewVertex.pos.y = ((float)y / 300) - 1.f;
NewVertex.texCoord.x = u;
NewVertex.texCoord.y = v;
NewVertex.color.r = m_Color.r / 128;
NewVertex.color.g = m_Color.g / 128;
NewVertex.color.b = m_Color.b / 128;
NewVertex.color.a = m_Color.a / 128;

GUI_Vertices.emplace_back(NewVertex);

The above code works sometimes and I can conditionally add the NewVertex into the GUI_Vertices vector if needed.

Here is the definition of Vertex:

struct Vertex {
    glm::vec3 pos;
    glm::vec4 color;
    glm::vec2 texCoord;

    static VkVertexInputBindingDescription getBindingDescription() {
        VkVertexInputBindingDescription bindingDescription = {};
        bindingDescription.binding = 0;
        bindingDescription.stride = sizeof(Vertex);
        bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;

        return bindingDescription;
    }

    static std::array<VkVertexInputAttributeDescription, 3> getAttributeDescriptions() {
        std::array<VkVertexInputAttributeDescription, 3> attributeDescriptions = {};

        attributeDescriptions[0].binding = 0;
        attributeDescriptions[0].location = 0;
        attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT;
        attributeDescriptions[0].offset = offsetof(Vertex, pos);

        attributeDescriptions[1].binding = 0;
        attributeDescriptions[1].location = 1;
        attributeDescriptions[1].format = VK_FORMAT_R32G32B32A32_SFLOAT;
        attributeDescriptions[1].offset = offsetof(Vertex, color);

        attributeDescriptions[2].binding = 0;
        attributeDescriptions[2].location = 2;
        attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT;
        attributeDescriptions[2].offset = offsetof(Vertex, texCoord);

        return attributeDescriptions;
    }

    bool operator==(const Vertex& other) const {
        return pos == other.pos && color == other.color && texCoord == other.texCoord;
    }
};

namespace std {
    template<> struct hash<Vertex> {
        size_t operator()(Vertex const& vertex) const {
            return ((hash<glm::vec3>()(vertex.pos) ^
                (hash<glm::vec4>()(vertex.color) << 1)) >> 1) ^
                (hash<glm::vec2>()(vertex.texCoord) << 1);
        }
    };
}

Later on in program execution, after adding all our Vertex elements to the GUI_Vertex vector I perform the following operation on GUI_Vertex:

memcpy(GUI_VertexAllocation->GetMappedData(), GUI_Vertices.data(), sizeof(Vertex) * GUI_Vertices.size());

I'm copying the memory from GUI_Vertices into a preallocated buffer which will be used by Vulkan to render our vertices.

Now i'm trying to figure out why the first method of adding Vertex objects into GUI_Vertices always works and the second method only sometimes works.

Here is a link to the entire project https://github.com/kklouzal/WorldEngine/blob/GUI_Indirect_Draw/Vulkan/VulkanGWEN.hpp

After recompiling the project the second method will occasionally work so I'm getting some undefined behavior here. I have checked the validity of GUI_Vertices up until the point where we do our memcpy and the data appears to be valid so I'm not sure whats going on.

I would like to get the second method working so I can conditionally add new vertices into the buffer.

Upvotes: 1

Views: 277

Answers (1)

Nicol Bolas
Nicol Bolas

Reputation: 473946

NewVertex.pos.x = ((float)x / 400) - 1.f;
NewVertex.pos.y = ((float)y / 300) - 1.f;
...
glm::vec3 pos;

emplace_back will always perform value initialization on the object it creates, which initializes all of the data members. By contrast, Vertex NewVertex; will default-initialize the object, which leaves its members uninitialized (since the GLM types have trivial default constructors).

So pos.z is uninitialized. And your code doesn't initialize it yourself. So you're sending uninitialized garbage to the GPU.

If you create the object with Vertex NewVertex{};, then it will be value-initialized, just like emplace_back does.

Upvotes: 10

Related Questions