From 7ad9ee1dc39f11b47b86469894cae851cff895d4 Mon Sep 17 00:00:00 2001 From: Sascha Willems Date: Sun, 14 Jan 2024 10:24:55 +0100 Subject: [PATCH] Code cleanup, fixed HLSL shaders --- .../terraintessellation.cpp | 481 +++++++----------- examples/tessellation/tessellation.cpp | 167 +++--- shaders/glsl/tessellation/passthrough.tese | 3 +- .../glsl/tessellation/passthrough.tese.spv | Bin 3036 -> 3084 bytes shaders/glsl/tessellation/pntriangles.tesc | 5 +- .../glsl/tessellation/pntriangles.tesc.spv | Bin 8276 -> 8504 bytes shaders/glsl/tessellation/pntriangles.tese | 3 +- .../glsl/tessellation/pntriangles.tese.spv | Bin 8824 -> 8872 bytes shaders/hlsl/tessellation/base.frag | 4 +- shaders/hlsl/tessellation/base.frag.spv | Bin 1832 -> 1312 bytes shaders/hlsl/tessellation/passthrough.tese | 9 +- .../hlsl/tessellation/passthrough.tese.spv | Bin 4092 -> 2052 bytes shaders/hlsl/tessellation/pntriangles.tesc | 7 +- .../hlsl/tessellation/pntriangles.tesc.spv | Bin 12556 -> 6300 bytes shaders/hlsl/tessellation/pntriangles.tese | 3 +- .../hlsl/tessellation/pntriangles.tese.spv | Bin 12488 -> 4716 bytes 16 files changed, 274 insertions(+), 408 deletions(-) diff --git a/examples/terraintessellation/terraintessellation.cpp b/examples/terraintessellation/terraintessellation.cpp index 021bc4e0..adc5ba32 100644 --- a/examples/terraintessellation/terraintessellation.cpp +++ b/examples/terraintessellation/terraintessellation.cpp @@ -1,5 +1,8 @@ /* * Vulkan Example - Dynamic terrain tessellation +* +* This samples draw a terrain from a heightmap texture and uses tessellation to add in details based on camera distance +* The height level is generated in the vertex shader by reading from the heightmap image * * Copyright (C) 2016-2023 by Sascha Willems - www.saschawillems.de * @@ -21,13 +24,13 @@ public: // Holds the buffers for rendering the tessellated terrain struct { struct Vertices { - VkBuffer buffer; - VkDeviceMemory memory; + VkBuffer buffer{ VK_NULL_HANDLE }; + VkDeviceMemory memory{ VK_NULL_HANDLE }; } vertices; struct Indices { int count; - VkBuffer buffer; - VkDeviceMemory memory; + VkBuffer buffer{ VK_NULL_HANDLE }; + VkDeviceMemory memory{ VK_NULL_HANDLE }; } indices; } terrain; @@ -47,7 +50,7 @@ public: } uniformBuffers; // Shared values for tessellation control and evaluation stages - struct { + struct UniformDataTessellation { glm::mat4 projection; glm::mat4 modelview; glm::vec4 lightPos = glm::vec4(-48.0f, -40.0f, 46.0f, 0.0f); @@ -57,40 +60,40 @@ public: glm::vec2 viewportDim; // Desired size of tessellated quad patch edge float tessellatedEdgeSize = 20.0f; - } uboTess; + } uniformDataTessellation; // Skysphere vertex shader stage - struct { + struct UniformDataVertex { glm::mat4 mvp; - } uboVS; + } uniformDataVertex; struct Pipelines { - VkPipeline terrain; - VkPipeline wireframe = VK_NULL_HANDLE; - VkPipeline skysphere; + VkPipeline terrain{ VK_NULL_HANDLE }; + VkPipeline wireframe{ VK_NULL_HANDLE }; + VkPipeline skysphere{ VK_NULL_HANDLE }; } pipelines; struct { - VkDescriptorSetLayout terrain; - VkDescriptorSetLayout skysphere; + VkDescriptorSetLayout terrain{ VK_NULL_HANDLE }; + VkDescriptorSetLayout skysphere{ VK_NULL_HANDLE }; } descriptorSetLayouts; struct { - VkPipelineLayout terrain; - VkPipelineLayout skysphere; + VkPipelineLayout terrain{ VK_NULL_HANDLE }; + VkPipelineLayout skysphere{ VK_NULL_HANDLE }; } pipelineLayouts; struct { - VkDescriptorSet terrain; - VkDescriptorSet skysphere; + VkDescriptorSet terrain{ VK_NULL_HANDLE }; + VkDescriptorSet skysphere{ VK_NULL_HANDLE }; } descriptorSets; - // Pipeline statistics + // If supported, this sample will gather pipeline statistics to show e.g. tessellation related information struct { - VkBuffer buffer; - VkDeviceMemory memory; + VkBuffer buffer{ VK_NULL_HANDLE }; + VkDeviceMemory memory{ VK_NULL_HANDLE }; } queryResult; - VkQueryPool queryPool = VK_NULL_HANDLE; + VkQueryPool queryPool{ VK_NULL_HANDLE }; uint64_t pipelineStats[2] = { 0 }; // View frustum passed to tessellation control shader for culling @@ -108,36 +111,36 @@ public: ~VulkanExample() { - // Clean up used Vulkan resources - // Note : Inherited destructor cleans up resources stored in base class - vkDestroyPipeline(device, pipelines.terrain, nullptr); - if (pipelines.wireframe != VK_NULL_HANDLE) { - vkDestroyPipeline(device, pipelines.wireframe, nullptr); - } - vkDestroyPipeline(device, pipelines.skysphere, nullptr); + if (device) { + vkDestroyPipeline(device, pipelines.terrain, nullptr); + if (pipelines.wireframe != VK_NULL_HANDLE) { + vkDestroyPipeline(device, pipelines.wireframe, nullptr); + } + vkDestroyPipeline(device, pipelines.skysphere, nullptr); - vkDestroyPipelineLayout(device, pipelineLayouts.skysphere, nullptr); - vkDestroyPipelineLayout(device, pipelineLayouts.terrain, nullptr); + vkDestroyPipelineLayout(device, pipelineLayouts.skysphere, nullptr); + vkDestroyPipelineLayout(device, pipelineLayouts.terrain, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.terrain, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.skysphere, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.terrain, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayouts.skysphere, nullptr); - uniformBuffers.skysphereVertex.destroy(); - uniformBuffers.terrainTessellation.destroy(); + uniformBuffers.skysphereVertex.destroy(); + uniformBuffers.terrainTessellation.destroy(); - textures.heightMap.destroy(); - textures.skySphere.destroy(); - textures.terrainArray.destroy(); + textures.heightMap.destroy(); + textures.skySphere.destroy(); + textures.terrainArray.destroy(); - vkDestroyBuffer(device, terrain.vertices.buffer, nullptr); - vkFreeMemory(device, terrain.vertices.memory, nullptr); - vkDestroyBuffer(device, terrain.indices.buffer, nullptr); - vkFreeMemory(device, terrain.indices.memory, nullptr); + vkDestroyBuffer(device, terrain.vertices.buffer, nullptr); + vkFreeMemory(device, terrain.vertices.memory, nullptr); + vkDestroyBuffer(device, terrain.indices.buffer, nullptr); + vkFreeMemory(device, terrain.indices.memory, nullptr); - if (queryPool != VK_NULL_HANDLE) { - vkDestroyQueryPool(device, queryPool, nullptr); - vkDestroyBuffer(device, queryResult.buffer, nullptr); - vkFreeMemory(device, queryResult.memory, nullptr); + if (queryPool != VK_NULL_HANDLE) { + vkDestroyQueryPool(device, queryPool, nullptr); + vkDestroyBuffer(device, queryResult.buffer, nullptr); + vkFreeMemory(device, queryResult.memory, nullptr); + } } } @@ -147,15 +150,14 @@ public: // Tessellation shader support is required for this example if (deviceFeatures.tessellationShader) { enabledFeatures.tessellationShader = VK_TRUE; - } - else { + } else { vks::tools::exitFatal("Selected GPU does not support tessellation shaders!", VK_ERROR_FEATURE_NOT_PRESENT); } // Fill mode non solid is required for wireframe display if (deviceFeatures.fillModeNonSolid) { enabledFeatures.fillModeNonSolid = VK_TRUE; }; - // Pipeline statistics + // Enable pipeline statistics if supported (to display them in the UI) if (deviceFeatures.pipelineStatisticsQuery) { enabledFeatures.pipelineStatisticsQuery = VK_TRUE; }; @@ -163,19 +165,9 @@ public: if (deviceFeatures.samplerAnisotropy) { enabledFeatures.samplerAnisotropy = VK_TRUE; } - // Enable texture compression - if (deviceFeatures.textureCompressionBC) { - enabledFeatures.textureCompressionBC = VK_TRUE; - } - else if (deviceFeatures.textureCompressionASTC_LDR) { - enabledFeatures.textureCompressionASTC_LDR = VK_TRUE; - } - else if (deviceFeatures.textureCompressionETC2) { - enabledFeatures.textureCompressionETC2 = VK_TRUE; - } } - // Setup pool and buffer for storing pipeline statistics results + // Setup a pool and a buffer for storing pipeline statistics results void setupQueryResultBuffer() { uint32_t bufSize = 2 * sizeof(uint64_t); @@ -265,8 +257,7 @@ public: samplerInfo.minLod = 0.0f; samplerInfo.maxLod = (float)textures.terrainArray.mipLevels; samplerInfo.borderColor = VK_BORDER_COLOR_FLOAT_OPAQUE_WHITE; - if (deviceFeatures.samplerAnisotropy) - { + if (deviceFeatures.samplerAnisotropy) { samplerInfo.maxAnisotropy = 4.0f; samplerInfo.anisotropyEnable = VK_TRUE; } @@ -342,107 +333,78 @@ public: } } - // Encapsulate height map data for easy sampling - struct HeightMap - { - private: - uint16_t *heightdata; - uint32_t dim; - uint32_t scale; - public: -#if defined(__ANDROID__) - HeightMap(std::string filename, uint32_t patchsize, AAssetManager* assetManager) -#else - HeightMap(std::string filename, uint32_t patchsize) -#endif - { - ktxResult result; - ktxTexture* ktxTexture; -#if defined(__ANDROID__) - AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING); - assert(asset); - size_t size = AAsset_getLength(asset); - assert(size > 0); - ktx_uint8_t* textureData = new ktx_uint8_t[size]; - AAsset_read(asset, textureData, size); - AAsset_close(asset); - result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture); - delete[] textureData; - -#else - result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture); -#endif - assert(result == KTX_SUCCESS); - ktx_size_t ktxSize = ktxTexture_GetImageSize(ktxTexture, 0); - ktx_uint8_t* ktxImage = ktxTexture_GetData(ktxTexture); - dim = ktxTexture->baseWidth; - heightdata = new uint16_t[dim * dim]; - memcpy(heightdata, ktxImage, ktxSize); - this->scale = dim / patchsize; - ktxTexture_Destroy(ktxTexture); - }; - - ~HeightMap() - { - delete[] heightdata; - } - - float getHeight(uint32_t x, uint32_t y) - { - glm::ivec2 rpos = glm::ivec2(x, y) * glm::ivec2(scale); - rpos.x = std::max(0, std::min(rpos.x, (int)dim-1)); - rpos.y = std::max(0, std::min(rpos.y, (int)dim-1)); - rpos /= glm::ivec2(scale); - return *(heightdata + (rpos.x + rpos.y * dim) * scale) / 65535.0f; - } - }; - - // Generate a terrain quad patch for feeding to the tessellation control shader + // Generate a terrain quad patch with normals based on heightmap data void generateTerrain() { - #define PATCH_SIZE 64 - #define UV_SCALE 1.0f + const uint32_t patchSize{ 64 }; + const float uvScale{ 1.0f }; - const uint32_t vertexCount = PATCH_SIZE * PATCH_SIZE; + uint16_t* heightdata; + uint32_t dim; + uint32_t scale; + + ktxResult result; + ktxTexture* ktxTexture; + + // We load the heightmap from an un-compressed ktx image with one channel that contains heights + std::string filename = getAssetPath() + "textures/terrain_heightmap_r16.ktx"; +#if defined(__ANDROID__) + // On Android we need to load the file using the asset manager + AAsset* asset = AAssetManager_open(androidApp->activity->assetManager, filename.c_str(), AASSET_MODE_STREAMING); + assert(asset); + size_t size = AAsset_getLength(asset); + assert(size > 0); + ktx_uint8_t* textureData = new ktx_uint8_t[size]; + AAsset_read(asset, textureData, size); + AAsset_close(asset); + result = ktxTexture_CreateFromMemory(textureData, size, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture); + delete[] textureData; + +#else + result = ktxTexture_CreateFromNamedFile(filename.c_str(), KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTexture); +#endif + assert(result == KTX_SUCCESS); + ktx_size_t ktxSize = ktxTexture_GetImageSize(ktxTexture, 0); + ktx_uint8_t* ktxImage = ktxTexture_GetData(ktxTexture); + dim = ktxTexture->baseWidth; + heightdata = new uint16_t[dim * dim]; + memcpy(heightdata, ktxImage, ktxSize); + scale = dim / patchSize; + ktxTexture_Destroy(ktxTexture); + + const uint32_t vertexCount = patchSize * patchSize; // We use the Vertex definition from the glTF model loader, so we can re-use the vertex input state vkglTF::Vertex *vertices = new vkglTF::Vertex[vertexCount]; const float wx = 2.0f; const float wy = 2.0f; - for (auto x = 0; x < PATCH_SIZE; x++) - { - for (auto y = 0; y < PATCH_SIZE; y++) - { - uint32_t index = (x + y * PATCH_SIZE); - vertices[index].pos[0] = x * wx + wx / 2.0f - (float)PATCH_SIZE * wx / 2.0f; + // Generate a two-dimensional vertex patch + for (auto x = 0; x < patchSize; x++) { + for (auto y = 0; y < patchSize; y++) { + uint32_t index = (x + y * patchSize); + vertices[index].pos[0] = x * wx + wx / 2.0f - (float)patchSize * wx / 2.0f; vertices[index].pos[1] = 0.0f; - vertices[index].pos[2] = y * wy + wy / 2.0f - (float)PATCH_SIZE * wy / 2.0f; - vertices[index].uv = glm::vec2((float)x / (PATCH_SIZE - 1), (float)y / (PATCH_SIZE - 1)) * UV_SCALE; + vertices[index].pos[2] = y * wy + wy / 2.0f - (float)patchSize * wy / 2.0f; + vertices[index].uv = glm::vec2((float)x / (patchSize - 1), (float)y / (patchSize - 1)) * uvScale; } } - // Calculate normals from height map using a sobel filter -#if defined(__ANDROID__) - HeightMap heightMap(getAssetPath() + "textures/terrain_heightmap_r16.ktx", PATCH_SIZE, androidApp->activity->assetManager); -#else - HeightMap heightMap(getAssetPath() + "textures/terrain_heightmap_r16.ktx", PATCH_SIZE); -#endif - for (auto x = 0; x < PATCH_SIZE; x++) - { - for (auto y = 0; y < PATCH_SIZE; y++) - { - // Get height samples centered around current position + // Calculate normals from the height map using a sobel filter + for (auto x = 0; x < patchSize; x++) { + for (auto y = 0; y < patchSize; y++) { + // We get float heights[3][3]; - for (auto hx = -1; hx <= 1; hx++) - { - for (auto hy = -1; hy <= 1; hy++) - { - heights[hx+1][hy+1] = heightMap.getHeight(x + hx, y + hy); + for (auto sx = -1; sx <= 1; sx++) { + for (auto sy = -1; sy <= 1; sy++) { + // Get height at sampled position from heightmap + glm::ivec2 rpos = glm::ivec2(x + sx, y + sy) * glm::ivec2(scale); + rpos.x = std::max(0, std::min(rpos.x, (int)dim - 1)); + rpos.y = std::max(0, std::min(rpos.y, (int)dim - 1)); + rpos /= glm::ivec2(scale); + heights[sx + 1][sy + 1] = *(heightdata + (rpos.x + rpos.y * dim) * scale) / 65535.0f; } } - - // Calculate the normal glm::vec3 normal; // Gx sobel filter normal.x = heights[0][0] - heights[2][0] + 2.0f * heights[0][1] - 2.0f * heights[2][1] + heights[0][2] - heights[2][2]; @@ -452,12 +414,14 @@ public: // The first value controls the bump strength normal.y = 0.25f * sqrt( 1.0f - normal.x * normal.x - normal.z * normal.z); - vertices[x + y * PATCH_SIZE].normal = glm::normalize(normal * glm::vec3(2.0f, 1.0f, 2.0f)); + vertices[x + y * patchSize].normal = glm::normalize(normal * glm::vec3(2.0f, 1.0f, 2.0f)); } } - // Indices - const uint32_t w = (PATCH_SIZE - 1); + delete[] heightdata; + + // Generate indices + const uint32_t w = (patchSize - 1); const uint32_t indexCount = w * w * 4; uint32_t *indices = new uint32_t[indexCount]; for (auto x = 0; x < w; x++) @@ -465,14 +429,16 @@ public: for (auto y = 0; y < w; y++) { uint32_t index = (x + y * w) * 4; - indices[index] = (x + y * PATCH_SIZE); - indices[index + 1] = indices[index] + PATCH_SIZE; + indices[index] = (x + y * patchSize); + indices[index + 1] = indices[index] + patchSize; indices[index + 2] = indices[index + 1] + 1; indices[index + 3] = indices[index] + 1; } } terrain.indices.count = indexCount; + // Upload vertices and indices to device + uint32_t vertexBufferSize = vertexCount * sizeof(vkglTF::Vertex); uint32_t indexBufferSize = indexCount * sizeof(uint32_t); @@ -481,7 +447,7 @@ public: VkDeviceMemory memory; } vertexStaging, indexStaging; - // Create staging buffers + // Stage the terrain vertex data to the device VK_CHECK_RESULT(vulkanDevice->createBuffer( VK_BUFFER_USAGE_TRANSFER_SRC_BIT, @@ -545,77 +511,43 @@ public: delete[] indices; } - void setupDescriptorPool() + void setupDescriptors() { - std::vector poolSizes = - { + // Pool + std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 3), vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 3) }; - - VkDescriptorPoolCreateInfo descriptorPoolInfo = - vks::initializers::descriptorPoolCreateInfo( - static_cast(poolSizes.size()), - poolSizes.data(), - 2); - + VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 2); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - } - void setupDescriptorSetLayouts() - { + // Layouts VkDescriptorSetLayoutCreateInfo descriptorLayout; - VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo; std::vector setLayoutBindings; // Terrain - setLayoutBindings = - { + setLayoutBindings = { // Binding 0 : Shared Tessellation shader ubo - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, - 0), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, 0), // Binding 1 : Height map - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, - 1), - // Binding 3 : Terrain texture array layers - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - VK_SHADER_STAGE_FRAGMENT_BIT, - 2), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT | VK_SHADER_STAGE_FRAGMENT_BIT, 1), + // Binding 2 : Terrain texture array layers + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 2), }; - descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(setLayoutBindings.size())); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.terrain)); - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.terrain, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.terrain)); // Skysphere - setLayoutBindings = - { + setLayoutBindings = { // Binding 0 : Vertex shader ubo - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - VK_SHADER_STAGE_VERTEX_BIT, - 0), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_VERTEX_BIT, 0), // Binding 1 : Color map - vks::initializers::descriptorSetLayoutBinding( - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - VK_SHADER_STAGE_FRAGMENT_BIT, - 1), + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, VK_SHADER_STAGE_FRAGMENT_BIT, 1), }; - descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings.data(), static_cast(setLayoutBindings.size())); + VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayouts.skysphere)); - pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.skysphere, 1); - VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.skysphere)); - } - - void setupDescriptorSets() - { + // Sets VkDescriptorSetAllocateInfo allocInfo; std::vector writeDescriptorSets; @@ -623,53 +555,40 @@ public: allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.terrain, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.terrain)); - writeDescriptorSets = - { + writeDescriptorSets = { // Binding 0 : Shared tessellation shader ubo - vks::initializers::writeDescriptorSet( - descriptorSets.terrain, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - 0, - &uniformBuffers.terrainTessellation.descriptor), - // Binding 1 : Displacement map - vks::initializers::writeDescriptorSet( - descriptorSets.terrain, - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - 1, - &textures.heightMap.descriptor), - // Binding 2 : Color map (alpha channel) - vks::initializers::writeDescriptorSet( - descriptorSets.terrain, - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - 2, - &textures.terrainArray.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.terrain, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.terrainTessellation.descriptor), + // Binding 1 : Height map + vks::initializers::writeDescriptorSet(descriptorSets.terrain, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.heightMap.descriptor), + // Binding 2 : Terrain texture array layers + vks::initializers::writeDescriptorSet(descriptorSets.terrain, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 2, &textures.terrainArray.descriptor), }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); // Skysphere allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayouts.skysphere, 1); VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSets.skysphere)); - - writeDescriptorSets = - { + writeDescriptorSets = { // Binding 0 : Vertex shader ubo - vks::initializers::writeDescriptorSet( - descriptorSets.skysphere, - VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, - 0, - &uniformBuffers.skysphereVertex.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.skysphere, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.skysphereVertex.descriptor), // Binding 1 : Fragment shader color map - vks::initializers::writeDescriptorSet( - descriptorSets.skysphere, - VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, - 1, - &textures.skySphere.descriptor), + vks::initializers::writeDescriptorSet(descriptorSets.skysphere, VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1, &textures.skySphere.descriptor), }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, NULL); + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); } - void preparePipelines() + void preparePipelines() { + // Layouts + VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo; + + pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.terrain, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.terrain)); + + pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(&descriptorSetLayouts.skysphere, 1); + VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayouts.skysphere)); + + // Pipelines VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); VkPipelineColorBlendStateCreateInfo colorBlendState = vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState); @@ -703,7 +622,7 @@ public: pipelineCI.pVertexInputState = vkglTF::Vertex::getPipelineVertexInputState({ vkglTF::VertexComponent::Position, vkglTF::VertexComponent::Normal, vkglTF::VertexComponent::UV }); VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.terrain)); - // Terrain wireframe pipeline + // Terrain wireframe pipeline (if devie supports it) if (deviceFeatures.fillModeNonSolid) { rasterizationState.polygonMode = VK_POLYGON_MODE_LINE; VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCI, nullptr, &pipelines.wireframe)); @@ -729,74 +648,44 @@ public: void prepareUniformBuffers() { // Shared tessellation shader stages uniform buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &uniformBuffers.terrainTessellation, - sizeof(uboTess))); + VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers.terrainTessellation, sizeof(UniformDataTessellation))); // Skysphere vertex shader uniform buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &uniformBuffers.skysphereVertex, - sizeof(uboVS))); + VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers.skysphereVertex, sizeof(UniformDataVertex))); // Map persistent VK_CHECK_RESULT(uniformBuffers.terrainTessellation.map()); VK_CHECK_RESULT(uniformBuffers.skysphereVertex.map()); - - updateUniformBuffers(); } void updateUniformBuffers() { // Tessellation + uniformDataTessellation.projection = camera.matrices.perspective; + uniformDataTessellation.modelview = camera.matrices.view * glm::mat4(1.0f); + uniformDataTessellation.lightPos.y = -0.5f - uniformDataTessellation.displacementFactor; // todo: Not uesed yet + uniformDataTessellation.viewportDim = glm::vec2((float)width, (float)height); - uboTess.projection = camera.matrices.perspective; - uboTess.modelview = camera.matrices.view * glm::mat4(1.0f); - uboTess.lightPos.y = -0.5f - uboTess.displacementFactor; // todo: Not uesed yet - uboTess.viewportDim = glm::vec2((float)width, (float)height); + frustum.update(uniformDataTessellation.projection * uniformDataTessellation.modelview); + memcpy(uniformDataTessellation.frustumPlanes, frustum.planes.data(), sizeof(glm::vec4) * 6); - frustum.update(uboTess.projection * uboTess.modelview); - memcpy(uboTess.frustumPlanes, frustum.planes.data(), sizeof(glm::vec4) * 6); - - float savedFactor = uboTess.tessellationFactor; + float savedFactor = uniformDataTessellation.tessellationFactor; if (!tessellation) { // Setting this to zero sets all tessellation factors to 1.0 in the shader - uboTess.tessellationFactor = 0.0f; + uniformDataTessellation.tessellationFactor = 0.0f; } - memcpy(uniformBuffers.terrainTessellation.mapped, &uboTess, sizeof(uboTess)); + memcpy(uniformBuffers.terrainTessellation.mapped, &uniformDataTessellation, sizeof(UniformDataTessellation)); if (!tessellation) { - uboTess.tessellationFactor = savedFactor; + uniformDataTessellation.tessellationFactor = savedFactor; } - // Skysphere vertex shader - uboVS.mvp = camera.matrices.perspective * glm::mat4(glm::mat3(camera.matrices.view)); - memcpy(uniformBuffers.skysphereVertex.mapped, &uboVS, sizeof(uboVS)); - } - - void draw() - { - VulkanExampleBase::prepareFrame(); - - // Command buffer to be submitted to the queue - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; - - // Submit to queue - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); - - if (deviceFeatures.pipelineStatisticsQuery) { - // Read query results for displaying in next frame - getQueryResults(); - } - - VulkanExampleBase::submitFrame(); + // Vertex shader + uniformDataVertex.mvp = camera.matrices.perspective * glm::mat4(glm::mat3(camera.matrices.view)); + memcpy(uniformBuffers.skysphereVertex.mapped, &uniformDataVertex, sizeof(UniformDataVertex)); } void prepare() @@ -808,27 +697,31 @@ public: setupQueryResultBuffer(); } prepareUniformBuffers(); - setupDescriptorSetLayouts(); + setupDescriptors(); preparePipelines(); - setupDescriptorPool(); - setupDescriptorSets(); buildCommandBuffers(); prepared = true; } + void draw() + { + VulkanExampleBase::prepareFrame(); + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + // Read query results for displaying in next frame (if the device supports pipeline statistics) + if (deviceFeatures.pipelineStatisticsQuery) { + getQueryResults(); + } + VulkanExampleBase::submitFrame(); + } + virtual void render() { if (!prepared) return; - draw(); - if (camera.updated) { - updateUniformBuffers(); - } - } - - virtual void viewChanged() - { updateUniformBuffers(); + draw(); } virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) @@ -838,7 +731,7 @@ public: if (overlay->checkBox("Tessellation", &tessellation)) { updateUniformBuffers(); } - if (overlay->inputFloat("Factor", &uboTess.tessellationFactor, 0.05f, 2)) { + if (overlay->inputFloat("Factor", &uniformDataTessellation.tessellationFactor, 0.05f, 2)) { updateUniformBuffers(); } if (deviceFeatures.fillModeNonSolid) { diff --git a/examples/tessellation/tessellation.cpp b/examples/tessellation/tessellation.cpp index c039c75d..b3aa2040 100644 --- a/examples/tessellation/tessellation.cpp +++ b/examples/tessellation/tessellation.cpp @@ -20,30 +20,25 @@ public: vkglTF::Model model; - struct { - vks::Buffer tessControl, tessEval; - } uniformBuffers; - - struct UBOTessControl { - float tessLevel = 3.0f; - } uboTessControl; - - struct UBOTessEval { + // One uniform data block is used by both tessellation shader stages + struct UniformData { glm::mat4 projection; glm::mat4 modelView; float tessAlpha = 1.0f; - } uboTessEval; + float tessLevel = 3.0f; + } uniformData; + vks::Buffer uniformBuffer; struct Pipelines { - VkPipeline solid; - VkPipeline wire = VK_NULL_HANDLE; - VkPipeline solidPassThrough; - VkPipeline wirePassThrough = VK_NULL_HANDLE; + VkPipeline solid{ VK_NULL_HANDLE }; + VkPipeline wire{ VK_NULL_HANDLE }; + VkPipeline solidPassThrough{ VK_NULL_HANDLE }; + VkPipeline wirePassThrough{ VK_NULL_HANDLE }; } pipelines; - VkPipelineLayout pipelineLayout; - VkDescriptorSet descriptorSet; - VkDescriptorSetLayout descriptorSetLayout; + VkPipelineLayout pipelineLayout{ VK_NULL_HANDLE }; + VkDescriptorSet descriptorSet{ VK_NULL_HANDLE }; + VkDescriptorSetLayout descriptorSetLayout{ VK_NULL_HANDLE }; VulkanExample() : VulkanExampleBase() { @@ -56,28 +51,29 @@ public: ~VulkanExample() { - // Clean up used Vulkan resources - // Note : Inherited destructor cleans up resources stored in base class - vkDestroyPipeline(device, pipelines.solid, nullptr); - if (pipelines.wire != VK_NULL_HANDLE) { - vkDestroyPipeline(device, pipelines.wire, nullptr); - }; - vkDestroyPipeline(device, pipelines.solidPassThrough, nullptr); - if (pipelines.wirePassThrough != VK_NULL_HANDLE) { - vkDestroyPipeline(device, pipelines.wirePassThrough, nullptr); - }; + if (device) { + // Clean up used Vulkan resources + // Note : Inherited destructor cleans up resources stored in base class + vkDestroyPipeline(device, pipelines.solid, nullptr); + if (pipelines.wire != VK_NULL_HANDLE) { + vkDestroyPipeline(device, pipelines.wire, nullptr); + }; + vkDestroyPipeline(device, pipelines.solidPassThrough, nullptr); + if (pipelines.wirePassThrough != VK_NULL_HANDLE) { + vkDestroyPipeline(device, pipelines.wirePassThrough, nullptr); + }; - vkDestroyPipelineLayout(device, pipelineLayout, nullptr); - vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); + vkDestroyPipelineLayout(device, pipelineLayout, nullptr); + vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); - uniformBuffers.tessControl.destroy(); - uniformBuffers.tessEval.destroy(); + uniformBuffer.destroy(); + } } // Enable physical device features required for this example virtual void getEnabledFeatures() { - // Example uses tessellation shaders + // Example requires tessellation shaders if (deviceFeatures.tessellationShader) { enabledFeatures.tessellationShader = VK_TRUE; } @@ -156,26 +152,35 @@ public: model.loadFromFile(getAssetPath() + "models/deer.gltf", vulkanDevice, queue, vkglTF::FileLoadingFlags::PreTransformVertices | vkglTF::FileLoadingFlags::FlipY); } - void setupDescriptorPool() + void setupDescriptors() { + // Pool const std::vector poolSizes = { vks::initializers::descriptorPoolSize(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 2), }; VkDescriptorPoolCreateInfo descriptorPoolInfo = vks::initializers::descriptorPoolCreateInfo(poolSizes, 1); VK_CHECK_RESULT(vkCreateDescriptorPool(device, &descriptorPoolInfo, nullptr, &descriptorPool)); - } - void setupDescriptorSetLayout() - { + // Layout const std::vector setLayoutBindings = { - // Binding 0 : Tessellation control shader ubo - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT, 0), - // Binding 1 : Tessellation evaluation shader ubo - vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, 1), + // Binding 0 : Tessellation shader ubo + vks::initializers::descriptorSetLayoutBinding(VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT | VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT, 0), }; VkDescriptorSetLayoutCreateInfo descriptorLayout = vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings); VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout)); + // Sets + VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); + VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); + std::vector writeDescriptorSets = { + // Binding 0 : Tessellation shader ubo + vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffer.descriptor), + }; + vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); + } + + void preparePipelines() + { // Layout uses set 0 for passing tessellation shader ubos and set 1 for fragment shader images (taken from glTF model) const std::vector setLayouts = { descriptorSetLayout, @@ -183,23 +188,8 @@ public: }; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo = vks::initializers::pipelineLayoutCreateInfo(setLayouts.data(), 2); VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout)); - } - void setupDescriptorSet() - { - VkDescriptorSetAllocateInfo allocInfo = vks::initializers::descriptorSetAllocateInfo(descriptorPool, &descriptorSetLayout, 1); - VK_CHECK_RESULT(vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet)); - std::vector writeDescriptorSets = { - // Binding 0 : Tessellation control shader ubo - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 0, &uniformBuffers.tessControl.descriptor), - // Binding 1 : Tessellation evaluation shader ubo - vks::initializers::writeDescriptorSet(descriptorSet, VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1, &uniformBuffers.tessEval.descriptor), - }; - vkUpdateDescriptorSets(device, static_cast(writeDescriptorSets.size()), writeDescriptorSets.data(), 0, nullptr); - } - - void preparePipelines() - { + // Pipelines VkPipelineInputAssemblyStateCreateInfo inputAssemblyState = vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_PATCH_LIST, 0, VK_FALSE); VkPipelineRasterizationStateCreateInfo rasterizationState = vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_COUNTER_CLOCKWISE, 0); VkPipelineColorBlendAttachmentState blendAttachmentState = vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE); @@ -260,45 +250,19 @@ public: void prepareUniformBuffers() { // Tessellation evaluation shader uniform buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &uniformBuffers.tessEval, - sizeof(uboTessEval))); - - // Tessellation control shader uniform buffer - VK_CHECK_RESULT(vulkanDevice->createBuffer( - VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, - VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, - &uniformBuffers.tessControl, - sizeof(uboTessControl))); - + VK_CHECK_RESULT(vulkanDevice->createBuffer(VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffer, sizeof(UniformData))); // Map persistent - VK_CHECK_RESULT(uniformBuffers.tessControl.map()); - VK_CHECK_RESULT(uniformBuffers.tessEval.map()); - - updateUniformBuffers(); + VK_CHECK_RESULT(uniformBuffer.map()); } void updateUniformBuffers() { - uboTessEval.projection = camera.matrices.perspective; - uboTessEval.modelView = camera.matrices.view; + // Adjust camera perspective if split screen is enabled + camera.setPerspective(45.0f, (float)(width * ((splitScreen) ? 0.5f : 1.0f)) / (float)height, 0.1f, 256.0f); + uniformData.projection = camera.matrices.perspective; + uniformData.modelView = camera.matrices.view; // Tessellation evaluation uniform block - memcpy(uniformBuffers.tessEval.mapped, &uboTessEval, sizeof(uboTessEval)); - // Tessellation control uniform block - memcpy(uniformBuffers.tessControl.mapped, &uboTessControl, sizeof(uboTessControl)); - } - - void draw() - { - VulkanExampleBase::prepareFrame(); - - submitInfo.commandBufferCount = 1; - submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; - VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); - - VulkanExampleBase::submitFrame(); + memcpy(uniformBuffer.mapped, &uniformData, sizeof(UniformData)); } void prepare() @@ -306,34 +270,33 @@ public: VulkanExampleBase::prepare(); loadAssets(); prepareUniformBuffers(); - setupDescriptorSetLayout(); + setupDescriptors(); preparePipelines(); - setupDescriptorPool(); - setupDescriptorSet(); buildCommandBuffers(); prepared = true; } + void draw() + { + VulkanExampleBase::prepareFrame(); + submitInfo.commandBufferCount = 1; + submitInfo.pCommandBuffers = &drawCmdBuffers[currentBuffer]; + VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE)); + VulkanExampleBase::submitFrame(); + } + virtual void render() { if (!prepared) return; - draw(); - if (camera.updated) { - updateUniformBuffers(); - } - } - - virtual void viewChanged() - { - camera.setPerspective(45.0f, (float)(width * ((splitScreen) ? 0.5f : 1.0f)) / (float)height, 0.1f, 256.0f); updateUniformBuffers(); + draw(); } virtual void OnUpdateUIOverlay(vks::UIOverlay *overlay) { if (overlay->header("Settings")) { - if (overlay->inputFloat("Tessellation level", &uboTessControl.tessLevel, 0.25f, 2)) { + if (overlay->inputFloat("Tessellation level", &uniformData.tessLevel, 0.25f, 2)) { updateUniformBuffers(); } if (deviceFeatures.fillModeNonSolid) { diff --git a/shaders/glsl/tessellation/passthrough.tese b/shaders/glsl/tessellation/passthrough.tese index cf1df9a4..d8c34701 100644 --- a/shaders/glsl/tessellation/passthrough.tese +++ b/shaders/glsl/tessellation/passthrough.tese @@ -2,11 +2,12 @@ layout (triangles, fractional_odd_spacing, cw) in; -layout (binding = 1) uniform UBO +layout (binding = 0) uniform UBO { mat4 projection; mat4 model; float tessAlpha; + float tessLevel; } ubo; layout (location = 0) in vec3 inNormal[]; diff --git a/shaders/glsl/tessellation/passthrough.tese.spv b/shaders/glsl/tessellation/passthrough.tese.spv index 338d88d1d4ce70ad2edd534d530811fadb6a7b21..85340e2141b2b9fa7a77ee8862d287a14942d52e 100644 GIT binary patch delta 95 zcmca3-Xp=u%%sfDz`)4By^+(HNtBI&jlqb4fq@x_OHzxAeNxL(b2eKtEoKz-U|@wy hDFdZjCf{V%W@OmR%EHRT57A`^6b50o&7y3_Spmw<4od(4 delta 51 zcmeB?xFgQV%%sfDz`)4Bv60i5X>$nE0>;T4EEhbRV@NaSiaI{6T1U(}7gpAh`>QKP;`5pJFsLN*?Ia&z z0p87>FfkA_pJ5q3Pl=ZbnCEh?85Ik&X*)5~d6c=JXF;2=1+`{!v>G_tBBDMpTe0P; z!dx3U#y6GE{1xCi+#SU^z&r36Bggz*4Q)9O0~7=yVfI}QnklumVATIW${NC~-B!GZ qjlKW}!0WIL-Vh^Z4&&75!v1UY6klrW0dqAD^r!)>OD}tKo{b-!P(7Of delta 422 zcmZ8dyAr`b6urr2MInRHhVdx%8sio3cvXHtt57LazQL$e{DMiN_XTEje#SYm6tZ*Y z-t*YAcXA7!ZBN-1SHzMe&ivr5t?;_LJK44+vJ4iIz~L;%gU)#YrtNAYT3iXEtDad4 z{gL6Qp!f|%#he|_j_^`BR;Yb&>4%ks(9^~nMh|!6>W@OmBk+qhYAEN6TP#A>SHd}JqO9B9B^Ab=1 delta 51 zcmZ4C`oo2jnMs+Qfq{{MV-{0EQ5s4+2u1PM3AVj<|Asi84VC^V%Qz2gbI@_~2& zeIIX3{Jv?&)JgW4S!=KTH*h5JvpG8SA z3`Lz$V__tmWzS))dhEq4*11o%%$EL2mRvYpQ73*xP&dD^<2PQ315up_`d;^!*e!O#+ z*nZ4sO|kW@m9X`EV>Qn?StH$nHGhMyv%ezBnfw>jik)E|GhdWHheVWTYoaG0(yyLrA3A2|1 zRpsts>b5Zb{!0b&>AR2VTg24)Px&8xC!h5mVAfQZIJNTrU+?;LmpIG268Dg^ocR-% d^*MXqqCaPSgc+}1zx9`SfjqmuKV*1;{RMY=R#gB1 literal 1832 zcmZ9M+fI`~6orQ#5Ns_V0s@Mys4>C(F-D2T2ofNTTH4Z(G&|lV+iC86JM(v)>m2<&quO2*n zbRU1pMA@uw!i<@i?1<0`nnx@&ls1Ik-k+f^n0CtPp0}FXxRC7w-p|v z#cUimyjLCcdsB8N?1n9SO7nzt&VC}@-p62=zn79#4Q5#qSa4>5Qx{I(!+!3Nb6EJ{ z^veuz?rhi(_x>6A$HdbD^;aApm(7}0Hy5?11$@{Ef#rG9va@r~FAezfImM@Blfz8+ z)ERt2payxw`ZFhG`yR-d5`O;3t@*l_#f>>z_k7y&^YwW%Zr>L<d!pcBjDZ0-@A zI>a0ZXa)VExwqF&Lub)@L?9mj8-W=;e=D1LzN-)3^E-hU^rWUhdoNIj_lZ9tBy}FX z4+1{!oH%~J{ARzY9_Jkj^hBK3YO>M!E$_N>L)kTf<+UT(HG%qG`{*=gDyl#3pe~S4 aJbupSu6Sek{9WCVjfRi64gam`kA*)z`F{@p diff --git a/shaders/hlsl/tessellation/passthrough.tese b/shaders/hlsl/tessellation/passthrough.tese index 535a55ad..1c23d91b 100644 --- a/shaders/hlsl/tessellation/passthrough.tese +++ b/shaders/hlsl/tessellation/passthrough.tese @@ -2,12 +2,13 @@ struct UBO { - float4x4 projection; - float4x4 model; - float tessAlpha; + float4x4 projection; + float4x4 model; + float tessAlpha; + float tessLevel; }; -cbuffer ubo : register(b1) { UBO ubo; } +cbuffer ubo : register(b0) { UBO ubo; } struct HSOutput { diff --git a/shaders/hlsl/tessellation/passthrough.tese.spv b/shaders/hlsl/tessellation/passthrough.tese.spv index 12cdd336b304dca0163caea18400be1a16c025ee..974936331b1c9aeda4f60ce2451e92d32423a1a3 100644 GIT binary patch literal 2052 zcmZ9MSx-|@6hF^VSa4zjO`qHW^iE_646?Ix)wYIq zS8ltfwzD=aw;$Uxm4HjT7g+-KIg#6E(OqHpp&K6}ehz6(>y2<@&PJcGju^@NHC7_C zA8CEhpzU|#e)eh&dm04xJd8brw1z(O&m+y(Z~OvMZp1I58`p1q80onqehJ;Ue)sc@ zSj!sPmvh@awM)6}9({B^iX4S8mdY&0yYWn(dm4^s^nK~m_KfN+x$POO8MZw;!?5kq z_Z_x9`aQ^TKYR3Tg}F`0U$Q zaG#>fH&)?h$C1x~Z^rLUIQ#59WwFS6-jwIJ7z@Yp=e^AV z@6H*7uY&HK#Cgq|2Xnw!(Ln5QcA7R#}V$P>czvsAxnKL=hweBq$Je@r%U}rixfFlS(G3@|*kt%PM{Z zl)K`-;=b?uj{Eup{9={QbLVt&;Zkq9`|Z^>8rHOfCjPl@KSC}1n~uMJXa8tz zXtah)eRIy~Z>x?F*VkCTwK{ybbZBCYd)d9Gu5%uK?arZc_vrpAZmuKeBh^CR1^Bzy z^^fB%m4WX!eZ^Qn&3PAjs;nFU`gYGF3{-qP${yNtEH z)yhb%RH=>hO$@R%_NRDK{I;q5f=aC-7O4;T}E$0*c zJMpbwz~2k7)^+A+9={9n<`-hkyC?O!unj=o*fTJ8UZH<6zVo&IBCPwBbNz)_*UOu~ z1Z!>N7vr0kH-8z{bBO$<_~zx+TY|NhJ&Z3;v6{wLq*#qMCee-UqT))dzCEtS`i}PK zAYO(IPK~RGy`%ER%TsI*&%p12{f+IT=1Q!5?E6}LHRQYa(%gpiUa75~d9`oHy3U;O zz7)s%4W!upn)g2-&o|C{EXCdj^GC7jxvy2k-Uolv))@QS*KbI17uI=3ya?aB60zs! zHz?x8_^pN5^RZv#Js-bOYTD29@f#Ge=i@iXcm>LyiM2kS(ZB3&Db`wNa}1~-@$o|J zEd75V^3K)of5gt%Z?*Ai^t|J%fbYMZ-p=%f#9_v{)roUov2IP`tlh>0ye~Hb>s@c& z9t*K+f$tZ!J&D6m+mhd_b@V~>8vUPye@y-0j!-AOq4n3Z)e zfs-Ki^D=(yC(iU0IQe_h>|Vu}vo^Tb@a5e{XuXafTE3gH>zwy;>>I#&dxv6AGx6U9 z=G5^Hy#<_iysu~ccH*74HSYlHoSU!4wj&GfO^!x(fzUI_&j$Z=jY(M90 z>^k>!6#Es3IY+-)a9^if?CBd~_f(wox5V=9$(rwgb@HMAJ-+(wps4=?vANJcg|B|h z#X0{7PJz(>3Ew(--`So&1N)kbp1WJ={z_~vdj5uQ&sby6-+_Ge`~%-Q`RMs4 zzJ1N9 patch, uint InvocationID : [domain("tri")] [partitioning("fractional_odd")] -[outputtopology("triangle_ccw")] +[outputtopology("triangle_cw")] [outputcontrolpoints(3)] [patchconstantfunc("ConstantsHS")] [maxtessfactor(20.0f)] diff --git a/shaders/hlsl/tessellation/pntriangles.tesc.spv b/shaders/hlsl/tessellation/pntriangles.tesc.spv index 8d30af5c823c0318d041179eacef63ad1a4da516..1a8ab8f465b2d735b8ff58d31d3517138127f7c7 100644 GIT binary patch literal 6300 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 literal 12488 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