From 054599f6b0f59358421bd3e10b85ca409e7f7769 Mon Sep 17 00:00:00 2001 From: ink-soul Date: Thu, 25 May 2023 11:38:10 +0800 Subject: [PATCH] Update homework1.cpp --- homework/homework1/homework1.cpp | 263 +++++++++++-------------------- 1 file changed, 93 insertions(+), 170 deletions(-) diff --git a/homework/homework1/homework1.cpp b/homework/homework1/homework1.cpp index 5282594..5cfd033 100644 --- a/homework/homework1/homework1.cpp +++ b/homework/homework1/homework1.cpp @@ -30,8 +30,6 @@ void VulkanglTFModel::loadImages(tinygltf::Model& input) { - // Images can be stored inside the glTF (which is the case for the sample model), so instead of directly - // loading them from disk, we fetch them from the glTF loader and upload the buffers images.resize(input.images.size()); for (size_t i = 0; i < input.images.size(); i++) { tinygltf::Image& glTFImage = input.images[i]; @@ -290,179 +288,138 @@ void VulkanglTFModel::loadNode(const tinygltf::Node& inputNode, const tinygltf::Model& input, VulkanglTFModel::Node* parent, uint32_t nodeIndex, std::vector& indexBuffer, std::vector& vertexBuffer) { VulkanglTFModel::Node* node = new VulkanglTFModel::Node{}; - node->parent = parent; node->matrix = glm::mat4(1.0f); + node->parent = parent; node->index = nodeIndex; - node->skin = inputNode.skin; - //get distributions of node - if (inputNode.translation.size() == 3) - { - node->matrix =glm::translate(node->matrix,glm::vec3(glm::make_vec3(inputNode.translation.data()))); + // Get the local node matrix + // It's either made up from translation, rotation, scale or a 4x4 matrix + if (inputNode.translation.size() == 3) { + node->matrix = glm::translate(node->matrix, glm::vec3(glm::make_vec3(inputNode.translation.data()))); } - - if (inputNode.rotation.size() == 4) - { //rotation is given by quaternion + if (inputNode.rotation.size() == 4) { glm::quat q = glm::make_quat(inputNode.rotation.data()); - node->matrix = glm::mat4(q); + node->matrix *= glm::mat4(q); } - - if (inputNode.scale.size() == 3) - { - node->matrix =glm::scale(node->matrix,glm::vec3(glm::make_vec3(inputNode.scale.data()))); + if (inputNode.scale.size() == 3) { + node->matrix = glm::scale(node->matrix, glm::vec3(glm::make_vec3(inputNode.scale.data()))); } - - if (inputNode.matrix.size() == 16) - { + if (inputNode.matrix.size() == 16) { node->matrix = glm::make_mat4x4(inputNode.matrix.data()); - } + }; - //find children of nodes if exists - if (inputNode.children.size() > 0) - { - for (size_t i = 0; i < inputNode.children.size(); i++) - { + // Load node's children + if (inputNode.children.size() > 0) { + for (size_t i = 0; i < inputNode.children.size(); i++) { loadNode(input.nodes[inputNode.children[i]], input, node, inputNode.children[i], indexBuffer, vertexBuffer); } - } - //load meshes in nodes if exists - if (inputNode.mesh > -1) - { + + // If the node contains mesh data, we load vertices and indices from the buffers + // In glTF this is done via accessors and buffer views + if (inputNode.mesh > -1) { const tinygltf::Mesh mesh = input.meshes[inputNode.mesh]; - for (size_t i = 0; i < mesh.primitives.size(); i++) - { - const tinygltf::Primitive& glTFPrimmitive = mesh.primitives[i]; + // Iterate through all primitives of this node's mesh + for (size_t i = 0; i < mesh.primitives.size(); i++) { + const tinygltf::Primitive& glTFPrimitive = mesh.primitives[i]; uint32_t firstIndex = static_cast(indexBuffer.size()); uint32_t vertexStart = static_cast(vertexBuffer.size()); uint32_t indexCount = 0; - //vertices - const float* positionBuffer = nullptr; - const float* normalsBuffer = nullptr; - const float* texcoordsBuffer = nullptr; - const float* tangentsBuffer = nullptr; - size_t vertexCount = 0; - //skin joints - const float* jointWeightsBuffer = nullptr; - const uint16_t * jointIndicesBuffer = nullptr; - - bool hasSkin = false; - //get buffer by index in primmitive.attributes + // Vertices { - if (glTFPrimmitive.attributes.find("POSITION") != glTFPrimmitive.attributes.end()) - { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimmitive.attributes.find("POSITION")->second]; - const tinygltf::BufferView &view = input.bufferViews[accessor.bufferView]; - positionBuffer = reinterpret_cast (&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + const float* positionBuffer = nullptr; + const float* normalsBuffer = nullptr; + const float* texCoordsBuffer = nullptr; + const float* tangentsBuffer = nullptr; + size_t vertexCount = 0; + + // Get buffer data for vertex positions + if (glTFPrimitive.attributes.find("POSITION") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("POSITION")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + positionBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); vertexCount = accessor.count; } - if (glTFPrimmitive.attributes.find("NORMAL") != glTFPrimmitive.attributes.end()) - { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimmitive.attributes.find("NORMAL")->second]; - const tinygltf::BufferView &view = input.bufferViews[accessor.bufferView]; - normalsBuffer = reinterpret_cast (&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - - } - //texture and tangent data - if (glTFPrimmitive.attributes.find("TEXCOORD_0") != glTFPrimmitive.attributes.end()) - { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimmitive.attributes.find("TEXCOORD_0")->second]; - const tinygltf::BufferView &view = input.bufferViews[accessor.bufferView]; - texcoordsBuffer = reinterpret_cast (&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - - } - if (glTFPrimmitive.attributes.find("TANGENT") != glTFPrimmitive.attributes.end()) - { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimmitive.attributes.find("TANGENT")->second]; + // Get buffer data for vertex normals + if (glTFPrimitive.attributes.find("NORMAL") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("NORMAL")->second]; const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; - tangentsBuffer = reinterpret_cast (&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - + normalsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); } - //skin joints and weights data - if (glTFPrimmitive.attributes.find("JOINTS_0") != glTFPrimmitive.attributes.end()) - { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimmitive.attributes.find("JOINTS_0")->second]; - const tinygltf::BufferView &view = input.bufferViews[accessor.bufferView]; - jointIndicesBuffer = reinterpret_cast (&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - - } - if (glTFPrimmitive.attributes.find("WEIGHTS_0") != glTFPrimmitive.attributes.end()) - { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimmitive.attributes.find("WEIGHTS_0")->second]; - const tinygltf::BufferView &view = input.bufferViews[accessor.bufferView]; - jointWeightsBuffer = reinterpret_cast (&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); - + // Get buffer data for vertex texture coordinates + // glTF supports multiple sets, we only load the first one + if (glTFPrimitive.attributes.find("TEXCOORD_0") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TEXCOORD_0")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + texCoordsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); } - hasSkin = (jointIndicesBuffer && jointWeightsBuffer); - - for (size_t v = 0; v < vertexCount; v++) + if (glTFPrimitive.attributes.find("TANGENT") != glTFPrimitive.attributes.end()) { + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.attributes.find("TANGENT")->second]; + const tinygltf::BufferView& view = input.bufferViews[accessor.bufferView]; + tangentsBuffer = reinterpret_cast(&(input.buffers[view.buffer].data[accessor.byteOffset + view.byteOffset])); + } + + // Append data to model's vertex buffer + for (size_t v = 0; v < vertexCount; v++) { Vertex vert{}; vert.pos = glm::vec4(glm::make_vec3(&positionBuffer[v * 3]), 1.0f); - vert.uv = texcoordsBuffer ? glm::make_vec2(&texcoordsBuffer[v*2]) : glm::vec3(0.0f); vert.normal = glm::normalize(glm::vec3(normalsBuffer ? glm::make_vec3(&normalsBuffer[v * 3]) : glm::vec3(0.0f))); - vert.color = glm::vec3(1.0f,1.0f,nodeIndex); + vert.uv = texCoordsBuffer ? glm::make_vec2(&texCoordsBuffer[v * 2]) : glm::vec3(0.0f); vert.tangent = tangentsBuffer ? glm::normalize(glm::make_vec3(&tangentsBuffer[v * 4])) : glm::vec3(0.0f); - vert.jointIndices = hasSkin ? glm::vec4(glm::make_vec4(&jointIndicesBuffer[v * 4])) : glm::vec4(0.0f); - vert.jointWeights = hasSkin ? glm::make_vec4(&jointWeightsBuffer[v * 4]) : glm::vec4(0.0f); + vert.color = glm::vec3(1.0f, 1.0f, nodeIndex);//Temp set index in color attribute vertexBuffer.push_back(vert); } } + // Indices { - const tinygltf::Accessor& accessor = input.accessors[glTFPrimmitive.indices]; - const tinygltf::BufferView& bufferview = input.bufferViews[accessor.bufferView]; - const tinygltf::Buffer& buffer = input.buffers[bufferview.buffer]; + const tinygltf::Accessor& accessor = input.accessors[glTFPrimitive.indices]; + const tinygltf::BufferView& bufferView = input.bufferViews[accessor.bufferView]; + const tinygltf::Buffer& buffer = input.buffers[bufferView.buffer]; indexCount += static_cast(accessor.count); - switch (accessor.componentType) - { + // glTF supports different component types of indices + switch (accessor.componentType) { case TINYGLTF_PARAMETER_TYPE_UNSIGNED_INT: { - const uint32_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferview.byteOffset]); - for (size_t index = 0; index < accessor.count; index++) - { + const uint32_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferView.byteOffset]); + for (size_t index = 0; index < accessor.count; index++) { indexBuffer.push_back(buf[index] + vertexStart); } break; - } - case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT:{ - const uint16_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferview.byteOffset]); - for (size_t index = 0; index < accessor.count; index++) - { + case TINYGLTF_PARAMETER_TYPE_UNSIGNED_SHORT: { + const uint16_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferView.byteOffset]); + for (size_t index = 0; index < accessor.count; index++) { indexBuffer.push_back(buf[index] + vertexStart); } break; } case TINYGLTF_PARAMETER_TYPE_UNSIGNED_BYTE: { - const uint8_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferview.byteOffset]); - for (size_t index = 0; index < accessor.count; index++) - { + const uint8_t* buf = reinterpret_cast(&buffer.data[accessor.byteOffset + bufferView.byteOffset]); + for (size_t index = 0; index < accessor.count; index++) { indexBuffer.push_back(buf[index] + vertexStart); } break; } default: - std::cerr << "index component type" << accessor.componentType << "not supported" << std::endl; - + std::cerr << "Index component type " << accessor.componentType << " not supported!" << std::endl; return; } } Primitive primitive{}; primitive.firstIndex = firstIndex; primitive.indexCount = indexCount; - primitive.materialIndex = glTFPrimmitive.material; + primitive.materialIndex = glTFPrimitive.material; node->mesh.primitives.push_back(primitive); - } } - if (parent) - { + + if (parent) { parent->children.push_back(node); } - else - { + else { nodes.push_back(node); } } @@ -662,6 +619,7 @@ VulkanExample::VulkanExample(): void VulkanExample::setupFrameBuffer() { + VulkanExampleBase::setupFrameBuffer(); if (pbrFrameBuffer.bCreate && (pbrFrameBuffer.fbo.width != width || pbrFrameBuffer.fbo.height != height)) { pbrFrameBuffer.color.destroy(device); @@ -674,6 +632,7 @@ void VulkanExample::setupFrameBuffer() VkFormat attachDepthFormat; VkBool32 validDepthFormat = vks::tools::getSupportedDepthFormat(physicalDevice, &attachDepthFormat); assert(validDepthFormat); + VulkanExample::createAttachment(VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, &pbrFrameBuffer.color, width, height); VulkanExample::createAttachment(attachDepthFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, &pbrFrameBuffer.depth, width, height); @@ -878,11 +837,6 @@ void VulkanExample::getEnabledFeatures() size_t indexBufferSize = indexBuffer.size() * sizeof(uint32_t); model.indices.count = static_cast(indexBuffer.size()); - struct StagingBuffer { - VkBuffer buffer; - VkDeviceMemory memory; - } vertexStaging, indexStaging; - // Create host visible staging buffers (source) VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_TRANSFER_SRC_BIT, @@ -1017,15 +971,6 @@ void VulkanExample::getEnabledFeatures() }; VkPipelineLayoutCreateInfo pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), static_cast(setLayouts.size())); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayouts.pbrLayout)); - /* - - // We will use push constants to push the local matrices of a primitive to the vertex shader - VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0); - // Push constant ranges are part of the pipeline layout - pipelineLayoutCI.pushConstantRangeCount = 1; - pipelineLayoutCI.pPushConstantRanges = &pushConstantRange; - - */ // Descriptor set for scene matrices VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.matrices, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); @@ -1038,33 +983,18 @@ void VulkanExample::getEnabledFeatures() }; vkUpdateDescriptorSets(device, 4, writeDescriptorSets.data(), 0, nullptr); - - // Descriptor set for glTF model skin joint matrices - /* - if (glTFModel.skins.size() > 0) - { - for (auto& skin : glTFModel.skins) - { - const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.jointMatrices, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &skin.descriptorSet)); - VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(skin.descriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &skin.ssbo.descriptor); - vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); - } - } - else - */ for (auto& material : glTFModel.materials) { const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.materialUniform, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &material.materialData.descriptorSet)); - VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(material.materialData.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &material.materialData.buffer.descriptor); + VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet( + material.materialData.descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &material.materialData.buffer.descriptor); vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); } - // Descriptor sets for glTF model materials - for (auto& image : glTFModel.images) - { + // Descriptor sets for materials + for (auto& image : glTFModel.images) { const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &image.descriptorSet)); VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(image.descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &image.texture.descriptor); @@ -1076,18 +1006,18 @@ void VulkanExample::getEnabledFeatures() VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(skinDescriptorSet, VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 0, &shaderData.skinSSBO.descriptor); vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); } - - //Tone Mapping pipeline layout + + //Tone Mapping pipeline layout { - auto pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.textures, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayouts.tonemappingLayout)); + auto pipelineLayoutCI = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.textures, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCI, nullptr, &pipelineLayouts.tonemappingLayout)); - const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &tonemappingDescriptorSet)); + const VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.textures, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &tonemappingDescriptorSet)); - auto imageInfo = vks::initializers::descriptorImageInfo(colorSampler, pbrFrameBuffer.color.imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); - VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(tonemappingDescriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &imageInfo); - vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); + auto imageInfo = vks::initializers::descriptorImageInfo(colorSampler, pbrFrameBuffer.color.imageView, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); + VkWriteDescriptorSet writeDescriptorSet = vks::initializers::writeDescriptorSet(tonemappingDescriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 0, &imageInfo); + vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); } } @@ -1113,9 +1043,6 @@ void VulkanExample::getEnabledFeatures() vks::initializers::vertexInputAttributeDescription(0, 2, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, uv)), // Location 2: Texture coordinates vks::initializers::vertexInputAttributeDescription(0, 3, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, color)), // Location 3: Color vks::initializers::vertexInputAttributeDescription(0, 4, VK_FORMAT_R32G32B32_SFLOAT, offsetof(VulkanglTFModel::Vertex, tangent)), // Location 4 : Tangent - // POI: Per-Vertex Joint indices and weights are passed to the vertex shader - //{5, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(VulkanglTFModel::Vertex, jointIndices)}, - //{6, 0, VK_FORMAT_R32G32B32A32_SFLOAT, offsetof(VulkanglTFModel::Vertex, jointWeights)}, }; VkPipelineVertexInputStateCreateInfo vertexInputStateCI = vks::initializers::pipelineVertexInputStateCreateInfo(); vertexInputStateCI.vertexBindingDescriptionCount = static_cast(vertexInputBindings.size()); @@ -1123,7 +1050,7 @@ void VulkanExample::getEnabledFeatures() vertexInputStateCI.vertexAttributeDescriptionCount = static_cast(vertexInputAttributes.size()); vertexInputStateCI.pVertexAttributeDescriptions = vertexInputAttributes.data(); - const std::array shaderStages = { + std::array shaderStages = { loadShader(getHomeworkShadersPath() + "homework1/mesh.vert.spv", VK_SHADER_STAGE_VERTEX_BIT), loadShader(getHomeworkShadersPath() + "homework1/mesh.frag.spv", VK_SHADER_STAGE_FRAGMENT_BIT) }; @@ -1149,6 +1076,7 @@ void VulkanExample::getEnabledFeatures() rasterizationStateCI.lineWidth = 1.0f; VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe)); } + //Create Tone Mapping render pipeline prepareToneMappingPipeline(); } @@ -1206,7 +1134,10 @@ void VulkanExample::getEnabledFeatures() VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &shaderData.skinSSBO, sizeof(glm::mat4) * glTFModel.nodeCount)); - + // Map persistent + VK_CHECK_RESULT(shaderData.buffer.map()); + VK_CHECK_RESULT(shaderData.skinSSBO.map()); + for (auto& material : glTFModel.materials) { VK_CHECK_RESULT(vulkanDevice->createBuffer( @@ -1219,9 +1150,7 @@ void VulkanExample::getEnabledFeatures() } - // Map persistent - VK_CHECK_RESULT(shaderData.buffer.map()); - VK_CHECK_RESULT(shaderData.skinSSBO.map()); + updateUniformBuffers(); } @@ -1705,12 +1634,6 @@ void VulkanExample::getEnabledFeatures() VkRenderPass renderpass; VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassCI, nullptr, &renderpass)); - struct { - VkImage image; - VkImageView view; - VkDeviceMemory memory; - VkFramebuffer framebuffer; - } offscreen; //framebuffer { // Color attachment