We now copy vertex data from the CPU to the GPU!

parent b12cb6d2
......@@ -4,32 +4,23 @@
//GLSL functions using global variables.
//Output the fragment color to the framebuffer existing at index 0.
// Get data from the vertex buffer, index 0/1.
layout(location = 0) in vec2 inPosition;
layout(location = 1) in vec3 inColor; //Vertex Attributes.
// Put data to the framebuffer.
layout(location = 0) out vec3 fragColor;
out gl_PerVertex {
vec4 gl_Position;
};
vec2 positions[3] = vec2[](
vec2(0.0, -0.5),
vec2(0.5, 0.5),
vec2(-0.5, 0.5)
);
//Let's make per-vertex colors!
vec3 colors[3] = vec3[](
vec3(1.0, 0.0, 0.0),
vec3(0.0, 1.0, 0.0),
vec3(0.0, 0.0, 1.0)
);
// Main is invoked for every vertex.
void main() {
// gl_VertexIndex contains the index of the current vertex.
// Usually indexing into the vertex buffer - here, into the hardcoded positions array.
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
fragColor = colors[gl_VertexIndex]; //Set the per-vertex fragment color. The fragment shader will interpolate.
gl_Position = vec4(inPosition, 0.0, 1.0);
fragColor = inColor; //Set the per-vertex fragment color. The fragment shader will interpolate.
//The position is made from these positions with dummy z and w components, to make clip coordinates.
//gl_Position is, as a global variable, the output.
......
#define GLFW_INCLUDE_VULKAN //Let GLFW include Vulkan.
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <iostream>
#include <stdexcept>
......@@ -9,6 +10,7 @@
#include <set>
#include <algorithm>
#include <fstream>
#include <array>
const int INIT_WIDTH = 800;
const int INIT_HEIGHT = 600;
......@@ -113,6 +115,69 @@ struct SwapChainSupportDetails {
struct Vertex {
glm::vec2 pos; //Position of Vertex.
glm::vec3 color; //Color of Vertex.
static VkVertexInputBindingDescription getBindingDescription() {
//We need to tell how to pass this "Vertex" struct data to the actual vertex shader once uploaded to GPU memory.
VkVertexInputBindingDescription bindingDescription = {};
bindingDescription.binding = 0; //The index to bind this vertex input to in the array of bindings.
// All our vertex data is packed in one array, so we only have one binding in our case.
bindingDescription.stride = sizeof(Vertex); //How far to step when incrementing vertices.
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; //Move to the next data entry after every VERTEX, as opposed to every INSTANCE.
return bindingDescription;
}
static std::array<VkVertexInputAttributeDescription, 2> getAttributeDescriptions() {
// How to extract a vertex attribute from a chunk of vertex data that comes from a VkVertexInputBindingDescription.
std::array<VkVertexInputAttributeDescription, 2> attributeDescriptions = {};
//First, the Vertex Position description.
attributeDescriptions[0].binding = 0; //From which binding the vertex per-vertex data comes from.
attributeDescriptions[0].location = 0; //The location directive of the input in the vertex shader. Lets the vertex shader gain access to the vertex info.
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT; //The data type of the attribute:
/* float: VK_FORMAT_R32_SFLOAT
vec2: VK_FORMAT_R32G32_SFLOAT
vec3: VK_FORMAT_R32G32B32_SFLOAT
vec4: VK_FORMAT_R32G32B32A32_SFLOAT
ivec2: VK_FORMAT_R32G32_SINT, a 2-component vector of 32-bit signed integers
uvec4: VK_FORMAT_R32G32B32A32_UINT, a 4-component vector of 32-bit unsigned integers
double: VK_FORMAT_R64_SFLOAT, a double-precision (64-bit) float
*/
//One should use the format where the # of color channels matches the shader data type.
// SFLOAT/UINT/SINT and bit width should also match the shader's input type.
// "format" implicitly defines the byte size of attribute data.
// "offset" specifies the # of bytes since the start of the per-vertex data to read from. WHERE TO FIND THE ATTRIBUTE BEING DESCRIBED!
attributeDescriptions[0].offset = offsetof(Vertex, pos); //We're looking for the POS attribute with this attribute description.
//Next, the Vertex Color description.
attributeDescriptions[1].binding = 0; //Still binding to the single vertex array we have.
attributeDescriptions[1].location = 1; //The vertex shader will access the color from location = 1.
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; //vec3 in the shader.
attributeDescriptions[1].offset = offsetof(Vertex, color); //We're looking for the COLOR attribute.
return attributeDescriptions;
}
};
// Interleaved - each Vertex struct entry now has { vec2 pos, vec3 color }
const std::vector<Vertex> vertices = {
{{0.0f, -0.5f}, {1.0f, 1.0f, 1.0f}},
{{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}},
{{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}}
};
class HelloTriangleApplication {
public :
void run() {
......@@ -150,6 +215,9 @@ class HelloTriangleApplication {
VkSemaphore imageAvailableSemaphore; //Signals that an image has been acquired.
VkSemaphore renderFinishedSemaphore; //Signals that the image has been rendered and is ready for presentation.
VkBuffer vertexBuffer; //The buffer that holds vertex data, which we can access.
VkDeviceMemory vertexBufferMemory; //The handle to the memory where the vertex buffer is actually stored (accessed from the vert buffer).
static void onWindowResized(GLFWwindow* window, int width, int height) {
//Handles GLFW resize events.
......@@ -960,12 +1028,15 @@ class HelloTriangleApplication {
// Attribute Descriptions: Attrs passe dto the vertex shader, where to load them from, at which offset.
//Now to handle that; vertex input.
auto bindingDescription = Vertex::getBindingDescription(); //Describes how to move vertex data around.
auto attributeDescription = Vertex::getAttributeDescriptions(); //Describes how to get usable vertex attributes from a binding description.
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 0;
vertexInputInfo.pVertexBindingDescriptions = nullptr; //Array of Structs defining details for loading vertex data.
vertexInputInfo.vertexAttributeDescriptionCount = 0;
vertexInputInfo.pVertexAttributeDescriptions = nullptr; //Array of Structs defining details for loading vertex data.
vertexInputInfo.vertexBindingDescriptionCount = 1; //One binding description.
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; //Array of Vertex defining details for moving vertex data around.
vertexInputInfo.vertexAttributeDescriptionCount = static_cast<uint32_t>(attributeDescription.size()); //Need a uint32_t vertion of the size of attributeDescriptions.
vertexInputInfo.pVertexAttributeDescriptions = attributeDescription.data(); //Array of Vertex defining details for loading vertex attributes.
//VkPipelineInputAssemblyStateCreateInfo describes inputted geometry from vertices, and primitive restart status. topology member values:
......@@ -1276,10 +1347,14 @@ class HelloTriangleApplication {
// Buffer to record to, graphics/compute pipeline, then the pipeline.
vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
//DRAW THE TRIANGLE! :P
//GET/DRAW THE TRIANGLE! :P
VkBuffer vertexBuffers[] = {vertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, vertexBuffers, offsets); //Bind the vertex buffers to bindings.
// /\ Fields: command buffer, offset, # of bindings to specify vert buffers for, array of vertex buffers to bind, byte offsets to read vertex data from.
//Buffer to record to, vertexCount, instanceCount (1 if not doing that), firstVertex (vertex buffer offset), firstInstance (instance offset).
vkCmdDraw(commandBuffers[i], 3, 1, 0, 0);
vkCmdDraw(commandBuffers[i], static_cast<uint32_t>(vertices.size()), 1, 0, 0); //vertexCount is now dynamic to vertices.
//End the render pass.
vkCmdEndRenderPass(commandBuffers[i]);
......@@ -1304,6 +1379,88 @@ class HelloTriangleApplication {
}
}
uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties) {
//GPUs offer different types of memory - each type allows certain operations and has perf characteristics.
// We need to combine our buffer requirements with our application requirements to find the right type of memory to use.
// \/ Struct Fields: memoryTypes (the memory types to access), memoryHeaps (distinct memory resources like dedicated VRAM, RAM swap space; the types of memory, affects perf)
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); //Get the memory properties of the physical device.
// Go through the memory types to find one tuitable for the buffer.
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
//typeFilter is used to specify the bit field of suitable memory types.
// We can find the index of suitable memory by iterating over all the memory types, and checking if the corresponding bit is set to 1.
// But we don't just wnat memtypes for vert buffer. We need to be able to write the vertex data into that memory.
// The memoryTypes array has VkMemoryTypes specifying the heap/properties of each memory type.
// properties has special features - like being able to write to it from CPU, indicated with VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT.
if ( (typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties ) {
//We did bitwise ANDs to check for binary intersections between memtypes/memtype index, and the properties of the memory type and the properties we want.
// We may have more than one desirable property - so, we need to check the bitwise AND for properties, that it equals the desired properties bit field, so we find
// a memory type that has everything we need and want.
return i;
}
}
throw std::runtime_error("Failed to find suitable memory type!");
}
void createVertexBuffer() {
// Buffers in Vulkan are regions of memory storing arbitrary data to be read by the GPU. Here, they store vertex data.
// Buffers DO NOT automatically allocate memory for themselves!
VkBufferCreateInfo bufferInfo = {};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = sizeof(vertices[0]) * vertices.size(); //The buffer will be the size of one vertex, multiplied by the # of vertices.
bufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; //The purpose for the data in the buffer. Can specify multiple purposes.
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; //Can be owned by a specific queue family, or shared. Owned = better performance.
// Ours will only be used by the graphics queue - so we'll let that own this buffer for better performance.
if (vkCreateBuffer(device, &bufferInfo, nullptr, &vertexBuffer) != VK_SUCCESS) {
throw std::runtime_error("Failed to create vertex buffer!");
}
//Now. The buffer has been created, but it doesn't have any memory actually assigned to it yet. So, we allocate!
// \/ Struct Fields: size (bytes of required memory), alignment (offset in bytes to where the buffer begins), memoryTypeBits (bit field of memory types suitable for the buffer.
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(device, vertexBuffer, &memRequirements); //We need to query the buffer's memory requirements.
//Time to actually allocate the memory!
VkMemoryAllocateInfo allocInfo = {};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size; //bytes of required memory.
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
// Use a memory type that lets us write to it from the CPU (HOST_VISIBLE_BIT), and also use COHERENT_BIT.
if (vkAllocateMemory(device, &allocInfo, nullptr, &vertexBufferMemory) != VK_SUCCESS) {
throw std::runtime_error("Failed to allocate vertex buffer memory!");
}
//Now, we can associate the newly allocated memory with the vertex buffer.
vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0); //4th param is the offset within this region of memory.
//Since we've allocated this memory specifically for the vertex buffer, the offset is 0. If non-zero, must be divisible by memRequirements.alignment.
//We need to fill the vertex buffer, by mapping the buffer memory into CPU-accessible memory using vkMapMemory.
void* data;
vkMapMemory(device, vertexBufferMemory, 0, bufferInfo.size, 0, &data);
// This allows us to access a region of specified memory defined by an offset and a size (0 and bufferInfo.sie).
// One can also specify VK_WHOLE_SIZE to map all of the memory.
// Second to last param can be used for flags, but there aren't any yet.
memcpy(data, vertices.data(), (size_t) bufferInfo.size); //memcpy copies into the "data" pointer, from "vertices.data()" pointer, using bufferInfo.size bytes).
vkUnmapMemory(device, vertexBufferMemory); //Unmap once CPU --> GPU memory copy is complete.
//Unfortunately the driver might not immediately copy the data, for example b/c of caching - buffer writes might not be visible in mapped memory yet. Two ways to prevent:
// Call a host-coherent memory heap. Ensures the mapped memory always matches the contents of the allocated memory. Worse performance, but doesn't matter here.
// Call vkFlushMappedMemoryRanges after writing to mapped memory, then call vkInvalidateMappedMemoryRanges after reading from mapped memory. More work!
}
void initVulkan() {
createInstance(); //The Vulkan instance is the link between application and Vulkan.
setupDebugCallback(); //Setup the debug callback that interfaces with the validation layer.
......@@ -1316,6 +1473,7 @@ class HelloTriangleApplication {
createGraphicsPipeline(); //The graphics pipeline is completely immutable - we have to build it from scratch, for performance.
createFramebuffers(); //Make the framebuffers for all the images in the swap chain - and use the one that corresponds to the retrieved image at draw time.
createCommandPool(); //Commands, like draw/memory transfer, are executed by command buffer objects. All the hard setup can be done in multiple threads, then!
createVertexBuffer(); //Vertex buffers store vertex data to be read by the GPU.
createCommandBuffers(); // We need one command buffer for each image in the swap chain, because they bind to framebuffers.
createSemaphore(); //Create the sephamores we need to synchronize things.
}
......@@ -1413,6 +1571,9 @@ class HelloTriangleApplication {
void cleanup() {
cleanupSwapChain(); //Does swapchain related cleanup.
vkDestroyBuffer(device, vertexBuffer, nullptr); //Kill the vertex buffer. It does not depend on the swap chain.
vkFreeMemory(device, vertexBufferMemory, nullptr); //We need to free all allocated memory manually. This is the memory holding the vertex buffer.
vkDestroySemaphore(device, renderFinishedSemaphore, nullptr);
vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); //Kill the semaphores.
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment