/* -*- tab-width: 4; -*- */ /* vi: set sw=2 ts=4 expandtab: */ /* * Copyright 2017-2020 Mark Callow. * SPDX-License-Identifier: Apache-2.0 */ /** * @internal * @class TextureArray * @~English * * @brief Test loading of 2D textures. * * @author Mark Callow, www.edgewise-consulting.com. * * @par Acknowledgement * Thanks to Sascha Willems' - www.saschawillems.de - for VulkanTextOverlay. */ #if defined(_WIN32) #if _MSC_VER < 1900 #define snprintf _snprintf #endif #define _CRT_SECURE_NO_WARNINGS #endif #include #include #include #include #include "TexturedCube.h" #include "cube.h" #define UNIFORM_OFFSET(f) (size_t)(&((Uniforms *)0)->mvp) #define VERTEX_BUFFER_FIRST_BINDING_ID 0 VulkanLoadTestSample* TexturedCube::create(VulkanContext& vkctx, uint32_t width, uint32_t height, const char* const szArgs, const std::string sBasePath) { return new TexturedCube(vkctx, width, height, szArgs, sBasePath); } TexturedCube::TexturedCube(VulkanContext& vkctx, uint32_t width, uint32_t height, const char* const /*szArgs*/, const std::string sBasePath) : VulkanLoadTestSample(vkctx, width, height, sBasePath), numTextures(1) { zoom = 1.0f; rotation = { 0.0f, 0.0f, 0.0f }; uniforms.lodBias = 0.0; try { prepareUniformBuffer(); prepareCubeDataBuffers(); setupVertexDescriptions(); createDescriptorSetLayout(); preparePipeline(); prepareDescriptorPool(); prepareDescriptorSet(); vkctx.createDrawCommandBuffers(); for (uint32_t i = 0; i < vkctx.swapchain.imageCount; i++) { buildCommandBuffer(i); } } catch (std::exception& e) { (void)e; // To quiet unused variable warnings from some compilers. cleanup(); throw; } } TexturedCube::~TexturedCube() { cleanup(); } void TexturedCube::resize(uint32_t width, uint32_t height) { w_width = width; w_height = height; uniforms.projection = perspective(45.f, width / (float)height, 1.f, 100.f); vkctx.destroyDrawCommandBuffers(); vkctx.createDrawCommandBuffers(); for (uint32_t i = 0; i < vkctx.swapchain.imageCount; i++) { buildCommandBuffer(i); } } void TexturedCube::run(uint32_t msTicks) { /* Setup the view matrix : just turn around the cube. */ vec3 up(0.f, 1.f, 0.f); const float fDistance = 5.0f; vec3 eye((float)cos( msTicks*0.001f ) * fDistance, (float)sin( msTicks*0.0007f ) * fDistance, (float)sin( msTicks*0.001f ) * fDistance); vec3 center = vec3(0.f, 0.f, 0.f); uniforms.viewPos = vec4(eye, 1.0); uniforms.view = lookAt(eye, center, up); updateUniformBuffer(); } /* ------------------------------------------------------------------------- */ void TexturedCube::cleanup() { } void TexturedCube::buildCommandBuffer(const int bufferIndex) { const VkCommandBuffer& cmdBuf = vkctx.drawCmdBuffers[bufferIndex]; const VkCommandBufferBeginInfo cmd_buf_info = { VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, NULL, 0, NULL }; #if 0 typedef union VkClearColorValue { float float32[4]; int32_t int32[4]; uint32_t uint32[4]; } VkClearColorValue; typedef struct VkClearDepthStencilValue { float depth; uint32_t stencil; } VkClearDepthStencilValue; typedef union VkClearValue { VkClearColorValue color; VkClearDepthStencilValue depthStencil; } VkClearValue; #endif // clang suggested all these braces. I could not find documentation of // union initialization except using designated intializers which are not // in C++11. VkClearValue clear_values[2] = { { {{0.0f, 0.2f, 0.2f, 1.0f}} }, { {{0.0f, 0}} } }; const VkRenderPassBeginInfo rp_begin = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, NULL, vkctx.renderPass, vkctx.framebuffers[bufferIndex], { {0, 0}, {w_width, w_height} }, ARRAY_LEN(clear_values), clear_values, }; VK_CHECK_RESULT(vkBeginCommandBuffer(cmdBuf, &cmd_buf_info)); vkCmdBeginRenderPass(cmdBuf, &rp_begin, VK_SUBPASS_CONTENTS_INLINE); VkViewport viewport = { 0, 0, (float)w_width, (float)w_height, 0.0f, 1.0f }; vkCmdSetViewport(cmdBuf, 0, 1, &viewport); VkRect2D scissor; scissor.offset.x = 0; scissor.offset.y = 0; scissor.extent.width = w_width; scissor.extent.height = w_height; vkCmdSetScissor(cmdBuf, 0, 1, &scissor); vkCmdBindDescriptorSets(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, NULL); vkCmdBindPipeline(cmdBuf, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); VkDeviceSize offsets[1] = { 0 }; vkCmdBindVertexBuffers(cmdBuf, VERTEX_BUFFER_FIRST_BINDING_ID, 1, &static_cast(vertices.buf), offsets); vkCmdBindIndexBuffer(cmdBuf, indices.buf, 0, VK_INDEX_TYPE_UINT16); vkCmdDrawIndexed(cmdBuf, indices.count, 1, 0, 0, 0); vkCmdEndRenderPass(vkctx.drawCmdBuffers[bufferIndex]); VK_CHECK_RESULT(vkEndCommandBuffer(cmdBuf)); } void TexturedCube::prepareUniformBuffer() { vkctx.createBuffer( vk::BufferUsageFlagBits::eUniformBuffer, sizeof(Uniforms), &uniforms, &uniformData.buffer, &uniformData.memory, &uniformData.descriptor); } void TexturedCube::updateUniformBuffer() { #if 0 /* Move to run? */ mat4 viewMatrix = glm::translate(mat4(), vec3(0.0f, 0.0f, zoom)); uniforms.model = viewMatrix * translate(mat4(), cameraPos); uniforms.model = rotate(uniforms.model, rotation.x, vec3(1.0f, 0.0f, 0.0f)); uniforms.model = rotate(uniforms.model, rotation.y, vec3(0.0f, 1.0f, 0.0f)); uniforms.model = rotate(uniforms.model, rotation.z, vec3(0.0f, 0.0f, 1.0f)); uniforms.viewPos = glm::vec4(0.0f, 0.0f, -zoom, 0.0f); #endif uint8_t *pData; VK_CHECK_RESULT(vkMapMemory(vkctx.device, uniformData.memory, 0, sizeof(uniforms), 0, (void **)&pData)); memcpy(pData, &uniforms, sizeof(uniforms)); vkUnmapMemory(vkctx.device, uniformData.memory); } struct Vertex { float position[3]; float normal[3]; float uv[2]; float color[4]; }; void TexturedCube::prepareCubeDataBuffers() { #if 1 std::vector vertexBuffer; vertexBuffer.insert(vertexBuffer.end(), &cube_face[0], &cube_face[ARRAY_LEN(cube_face)]); #if 0 vertexBuffer.insert(vertexBuffer.end(), &cube_normal[0], &cube_normal[ARRAY_LEN(cube_normal)]); vertexBuffer.insert(vertexBuffer.end(), &cube_texture[0], &cube_texture[ARRAY_LEN(cube_texture)]); vertexBuffer.insert(vertexBuffer.end(), &cube_color[0], &cube_color[ARRAY_LEN(cube_color)]); #endif vkctx.createBuffer( vk::BufferUsageFlagBits::eVertexBuffer, vertexBuffer.size() * sizeof(float), vertexBuffer.data(), &vertices.buf, &vertices.mem); #else // It appears to be impossible in Vulkan to have multiple non-interleaved // attributes in 1 buffer object. So interleave the data. std::vector vertexBuffer; uint i, j, k; for (i = 0, j = 0, k = 0; i < ARRAY_LEN(cube_face); i++, j++, k++) { Vertex vertex; vertex.position[0] = cube_face[i]; vertex.position[1] = cube_face[i+1]; vertex.position[2] = cube_face[i+2]; vertex.normal[0] = cube_normal[i]; vertex.normal[1] = cube_normal[++i]; vertex.normal[2] = cube_normal[++i]; vertex.uv[0] = cube_texture[j]; vertex.uv[1] = cube_texture[++j]; vertex.color[0] = cube_color[k]; vertex.color[1] = cube_color[++k]; vertex.color[2] = cube_color[++k]; vertex.color[3] = cube_color[++k]; vertexBuffer.push_back(vertex); } createBuffer( VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, vertexBuffer.size() * sizeof(Vertex), vertexBuffer.data(), &vertices.buf, &vertices.mem); #endif // Setup indices indices.count = ARRAY_LEN(cube_index_buffer); vkctx.createBuffer( vk::BufferUsageFlagBits::eIndexBuffer, sizeof(cube_index_buffer), (void*)cube_index_buffer, &indices.buf, &indices.mem); } static VkVertexInputBindingDescription initVertexInputBindingDescription(uint32_t binding, uint32_t stride, VkVertexInputRate inputRate) { VkVertexInputBindingDescription vInputBindDescription = {}; vInputBindDescription.binding = binding; vInputBindDescription.stride = stride; vInputBindDescription.inputRate = inputRate; return vInputBindDescription; } static VkVertexInputAttributeDescription initVertexInputAttributeDescription(uint32_t binding, uint32_t location, VkFormat format, uint32_t offset) { VkVertexInputAttributeDescription vInputAttribDescription = {}; vInputAttribDescription.location = location; vInputAttribDescription.binding = binding; vInputAttribDescription.format = format; vInputAttribDescription.offset = offset; return vInputAttribDescription; } static VkPipelineVertexInputStateCreateInfo initPipelineVertexInputStateCreateInfo() { VkPipelineVertexInputStateCreateInfo pipelineVertexInputStateCreateInfo = {}; pipelineVertexInputStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; pipelineVertexInputStateCreateInfo.pNext = NULL; return pipelineVertexInputStateCreateInfo; } void TexturedCube::setupVertexDescriptions() { // Binding description vertices.bindingDescriptions.resize(1); vertices.bindingDescriptions[0] = initVertexInputBindingDescription( VERTEX_BUFFER_FIRST_BINDING_ID, /*sizeof(Vertex),*/CUBE_FACE_STRIDE, VK_VERTEX_INPUT_RATE_VERTEX); // Attribute descriptions // Describes memory layout and shader positions vertices.attributeDescriptions.resize(1); //vertices.attributeDescriptions.resize(4); // Location 0 : position vertices.attributeDescriptions[0] = initVertexInputAttributeDescription( VERTEX_BUFFER_FIRST_BINDING_ID, 0, VK_FORMAT_R32G32B32_SFLOAT, 0); #if 0 // Location 1 : vertex normal vertices.attributeDescriptions[1] = initVertexInputAttributeDescription( VERTEX_BUFFER_FIRST_BINDING_ID, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float)*3);//sizeof(cube_face)); // Location 2 : texture coordinates vertices.attributeDescriptions[2] = initVertexInputAttributeDescription( VERTEX_BUFFER_FIRST_BINDING_ID, 2, VK_FORMAT_R32G32_SFLOAT, sizeof(float)*6);//sizeof(cube_face) + sizeof(cube_normal)); // Location 3 : colors vertices.attributeDescriptions[3] = initVertexInputAttributeDescription( VERTEX_BUFFER_FIRST_BINDING_ID, 3, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float)*6 + sizeof(float)*2);//sizeof(cube_face) + sizeof(cube_normal) + sizeof(cube_texture)); #endif vertices.inputState = initPipelineVertexInputStateCreateInfo(); vertices.inputState.vertexBindingDescriptionCount = static_cast(vertices.bindingDescriptions.size()); vertices.inputState.pVertexBindingDescriptions = vertices.bindingDescriptions.data(); vertices.inputState.vertexAttributeDescriptionCount = static_cast(vertices.attributeDescriptions.size()); vertices.inputState.pVertexAttributeDescriptions = vertices.attributeDescriptions.data(); } void TexturedCube::createDescriptorSetLayout() { const VkDescriptorSetLayoutBinding layoutBindings[1] = { { 0, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, VK_SHADER_STAGE_VERTEX_BIT, NULL, }, #if 0 { 1, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, numTextures, VK_SHADER_STAGE_FRAGMENT_BIT, NULL, }, #endif }; const VkDescriptorSetLayoutCreateInfo dslcInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, NULL, 0, 1,//2, layoutBindings, }; VK_CHECK_RESULT(vkCreateDescriptorSetLayout(vkctx.device, &dslcInfo, NULL, &descriptorSetLayout)); } void TexturedCube::preparePipeline() { const VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = { VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, NULL, 0, 1, &descriptorSetLayout, 0, NULL, }; VK_CHECK_RESULT(vkCreatePipelineLayout(vkctx.device, &pipelineLayoutCreateInfo, NULL, &pipelineLayout)); VkPipelineInputAssemblyStateCreateInfo ias = { }; ias.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; ias.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; VkPipelineRasterizationStateCreateInfo rs = { }; rs.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rs.polygonMode = VK_POLYGON_MODE_FILL; rs.cullMode = VK_CULL_MODE_NONE; //VK_CULL_MODE_BACK_BIT; rs.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; // Must be false because we haven't enabled the depthClamp device feature. rs.depthClampEnable = VK_FALSE; rs.rasterizerDiscardEnable = VK_FALSE; rs.depthBiasEnable = VK_FALSE; rs.lineWidth = 1.0f; VkPipelineColorBlendAttachmentState cbas = { }; cbas.colorWriteMask = 0xf; cbas.blendEnable = VK_FALSE; VkPipelineColorBlendStateCreateInfo cbs = { }; cbs.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; cbs.attachmentCount = 1; cbs.pAttachments = &cbas; VkPipelineDepthStencilStateCreateInfo dss = { }; dss.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; dss.depthTestEnable = VK_TRUE; dss.depthWriteEnable = VK_TRUE; dss.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; dss.depthBoundsTestEnable = VK_FALSE; dss.back.failOp = VK_STENCIL_OP_KEEP; dss.back.passOp = VK_STENCIL_OP_KEEP; dss.back.compareOp = VK_COMPARE_OP_ALWAYS; dss.stencilTestEnable = VK_FALSE; dss.front = dss.back; VkPipelineViewportStateCreateInfo vps = { }; vps.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; vps.viewportCount = 1; vps.scissorCount = 1; std::vector dynamicStateEnables = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo ds = { }; ds.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; ds.pDynamicStates = dynamicStateEnables.data(); ds.dynamicStateCount = static_cast(dynamicStateEnables.size()); VkPipelineMultisampleStateCreateInfo mss = { }; mss.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; mss.pSampleMask = NULL; mss.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; // Load shaders // Two stages: vs and fs //std::array shaderStages; std::array shaderStages; #if defined(INCLUDE_SHADERS) shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; //shaderStages[0].pNext = NULL; //shaderStages[0].flags = 0; shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; shaderStages[0].module = prepareVertShader(); shaderStages[0].pName = "main"; //shaderStages[0].pSpecializationInfo = nullptr; shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; //shaderStages[1].pNext = NULL; //shaderStages[1].flags = 0; shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shaderStages[1].module = prepareFragShader(); shaderStages[1].pName = "main"; //shaderStages[1].pSpecializationInfo = nullptr; #else std::string filepath = getAssetPath() + "shaders/"; shaderStages[0] = loadShader(filepath + "cube.vert.spv", vk::ShaderStageFlagBits::eVertex); shaderStages[1] = loadShader(filepath + "cube.frag.spv", vk::ShaderStageFlagBits::eFragment); #endif VkPipelineCacheCreateInfo pc { }; pc.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO; VK_CHECK_RESULT(vkCreatePipelineCache(vkctx.device, &pc, NULL, &pipelineCache)); VkGraphicsPipelineCreateInfo pipelineCreateInfo { }; pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineCreateInfo.renderPass = vkctx.renderPass; pipelineCreateInfo.layout = pipelineLayout; pipelineCreateInfo.pVertexInputState = &static_cast(vertices.inputState); pipelineCreateInfo.pInputAssemblyState = &ias; pipelineCreateInfo.pRasterizationState = &rs; pipelineCreateInfo.pColorBlendState = &cbs; pipelineCreateInfo.pMultisampleState = &mss; pipelineCreateInfo.pViewportState = &vps; pipelineCreateInfo.pDepthStencilState = &dss; pipelineCreateInfo.pDynamicState = &ds; pipelineCreateInfo.stageCount = (uint32_t)shaderStages.size(); pipelineCreateInfo.pStages = shaderStages.data(); VK_CHECK_RESULT(vkCreateGraphicsPipelines(vkctx.device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline)); } #if defined(INCLUDE_SHADERS) VkShaderModule TexturedCube::prepareVertShader() { size_t codeSize; // INCLUDE_SHADERS requires use of glslc or addition of its -mfmt=num // option to glslangValidator. In order not to add the additional // dependency on glslc we'll load the shaders from files. uint32_t vert[] = { #include "cube.vert.spv" }; codeSize = sizeof(vert); return createShaderModule(vert, codeSize); } VkShaderModule TexturedCube::prepareFragShader() { size_t codeSize; uint32_t frag[] = { #include "cube.frag.spv" }; codeSize = sizeof(frag); return createShaderModule(frag, codeSize); } VkShaderModule TexturedCube::createShaderModule(uint32_t* spv, size_t size) { VkShaderModule module; VkShaderModuleCreateInfo shCreateInfo = {}; shCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; shCreateInfo.codeSize = size; shCreateInfo.pCode = spv; shCreateInfo.flags = 0; VK_CHECK_RESULT(vkCreateShaderModule(vkctx.device, &shCreateInfo, NULL, &module)); return module; } #endif void TexturedCube::prepareDescriptorPool() { const VkDescriptorPoolSize typeCounts[2] = { { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, }, { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, numTextures, }, }; const VkDescriptorPoolCreateInfo dpoolCreateInfo = { VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, NULL, 0, 1, 2, typeCounts, }; VkResult U_ASSERT_ONLY err; err = vkCreateDescriptorPool(vkctx.device, &dpoolCreateInfo, NULL, &descriptorPool); assert(!err); } static VkWriteDescriptorSet initWriteDescriptorSet( VkDescriptorSet dstSet, VkDescriptorType type, uint32_t binding, const VkDescriptorBufferInfo* bufferInfo) { VkWriteDescriptorSet writeDescriptorSet = {}; writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; writeDescriptorSet.pNext = NULL; writeDescriptorSet.dstSet = dstSet; writeDescriptorSet.descriptorType = type; writeDescriptorSet.dstBinding = binding; writeDescriptorSet.pBufferInfo = bufferInfo; // Default value in all examples writeDescriptorSet.descriptorCount = 1; return writeDescriptorSet; } void TexturedCube::prepareDescriptorSet() { VkDescriptorSetAllocateInfo allocInfo = { }; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; allocInfo.descriptorSetCount = 1; allocInfo.pSetLayouts = &descriptorSetLayout; VK_CHECK_RESULT(vkAllocateDescriptorSets(vkctx.device, &allocInfo, &descriptorSet)); #if 0 // Image descriptor for the color map texture vk::DescriptorImageInfo texDescriptor( texture.sampler, texture.view, vk::ImageLayout::eShaderReadOnlyOptimal); #endif std::vector writeDescriptorSets = { // Binding 0 : Vertex shader uniform buffer initWriteDescriptorSet( descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &static_cast(uniformData.descriptor)), #if 0 // Binding 1 : Fragment shader texture sampler initWriteDescriptorSet( descriptorSet, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &texDescriptor) #endif }; vkUpdateDescriptorSets(vkctx.device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); }