/****************************************************************************** * The MIT License (MIT) * * Copyright (c) 2020-2026 Baldur Karlsson * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. ******************************************************************************/ #include "vk_test.h" RD_TEST(VK_Pixel_History, VulkanGraphicsTest) { static constexpr const char *Description = "Tests pixel history"; VkPhysicalDeviceNestedCommandBufferFeaturesEXT nestedFeats = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NESTED_COMMAND_BUFFER_FEATURES_EXT, }; VkPhysicalDeviceNestedCommandBufferPropertiesEXT nestedProps = { VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_NESTED_COMMAND_BUFFER_PROPERTIES_EXT, }; std::string common = R"EOSHADER( #version 460 core )EOSHADER"; const std::string vertex = R"EOSHADER( layout(location = 0) in vec3 Position; layout(location = 1) in vec4 Color; layout(location = 0) INTERP out COL_TYPE outCol; void main() { gl_Position = vec4(Position.xyz, 1); outCol = ProcessColor(Color); } )EOSHADER"; const std::string pixel = R"EOSHADER( layout(location = 0) INTERP in COL_TYPE inCol; #if COLOR == 1 layout(location = 0, index = 0) out COL_TYPE Color; #endif void main() { if (gl_FragCoord.x < DISCARD_X+1 && gl_FragCoord.x > DISCARD_X) discard; if (inCol.x > 10000 && inCol.y > 10000 && inCol.z > 10000) discard; #if COLOR == 1 Color = inCol + COL_TYPE(0, 0, 0, ProcessColor(vec4(ALPHA_ADD)).x); #endif } )EOSHADER"; std::string mspixel = R"EOSHADER( #if COLOR == 1 layout(location = 0, index = 0) out COL_TYPE Color; #endif void main() { #if COLOR == 1 vec4 col; if (gl_SampleID == 0) col = vec4(1, 0, 0, 1+ALPHA_ADD); else if (gl_SampleID == 1) col = vec4(0, 0, 1, 1+ALPHA_ADD); else if (gl_SampleID == 2) col = vec4(0, 1, 1, 1+ALPHA_ADD); else if (gl_SampleID == 3) col = vec4(1, 1, 1, 1+ALPHA_ADD); Color = ProcessColor(col); #endif } )EOSHADER"; std::string comp = R"EOSHADER( layout(set = 0, binding = 0) writeonly uniform IMAGE_TYPE Output; layout(local_size_x = 1, local_size_y = 1, local_size_z = 1) in; void main() { vec4 data = vec4(3, 3, 3, 9); for(int x=0; x < 10; x++) for(int y=0; y < 10; y++) imageStore(Output, ivec3(STORE_X+x, STORE_Y+y, STORE_Z) STORE_SAMPLE, ProcessColor(data)); } )EOSHADER"; void Prepare(int argc, char **argv) { features.depthBounds = true; features.geometryShader = true; features.sampleRateShading = true; features.shaderStorageImageMultisample = true; features.shaderStorageImageReadWithoutFormat = true; features.shaderStorageImageWriteWithoutFormat = true; devExts.push_back(VK_KHR_MAINTENANCE1_EXTENSION_NAME); optDevExts.push_back(VK_EXT_NESTED_COMMAND_BUFFER_EXTENSION_NAME); VulkanGraphicsTest::Prepare(argc, argv); if(!Avail.empty()) return; VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(phys, VK_FORMAT_D32_SFLOAT_S8_UINT, &props); if((props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) == 0) { Avail = "D32S8 not supported"; return; } if(hasExt(VK_EXT_NESTED_COMMAND_BUFFER_EXTENSION_NAME)) { getPhysFeatures2(&nestedFeats); if((nestedFeats.nestedCommandBuffer) && (nestedFeats.nestedCommandBufferRendering)) { nestedFeats.pNext = (void *)devInfoNext; devInfoNext = &nestedFeats; } } } struct VKTestBatch { std::string name; bool secondary = false; bool nested = false; bool uint = false; AllocatedImage colImg; AllocatedImage depthImg; VkFormat depthFormat; VkDescriptorSet compset; VkPipelineLayout compLayout; VkPipeline comppipe; VkFramebuffer fb; VkRenderPass rp; VkExtent2D extent; VkPipeline depthWritePipe; VkPipeline fixedScissorPassPipe; VkPipeline fixedScissorFailPipe; VkPipeline dynamicStencilRefPipe; VkPipeline dynamicStencilMaskPipe; VkPipeline depthEqualPipe; VkPipeline depthPipe; VkPipeline stencilWritePipe; VkPipeline backgroundPipe; VkPipeline noFsPipe; VkPipeline basicPipe; VkPipeline colorMaskPipe; VkPipeline cullFrontPipe; VkPipeline depthBoundsPipe1; VkPipeline depthBoundsPipe2; VkPipeline sampleColourPipe; }; std::vector batches; void BuildTestBatch(const std::string &name, VkSampleCountFlagBits sampleCount, VkFormat colourFormat, VkFormat depthStencilFormat, int targetMip = -1, int targetSlice = -1, int targetDepthSlice = -1) { batches.push_back({}); VKTestBatch &batch = batches.back(); batch.name = name; batch.extent = mainWindow->scissor.extent; uint32_t arraySize = targetSlice >= 0 ? 5 : 1; uint32_t mips = targetMip >= 0 ? 4 : 1; uint32_t depth = targetDepthSlice >= 0 ? 14 : 0; uint32_t mip = (uint32_t)std::max(0, targetMip); uint32_t slice = (uint32_t)std::max(0, targetSlice); if(depth > 1) slice = (uint32_t)targetDepthSlice; VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo()); std::string defines; defines += "#define COLOR " + std::string(colourFormat == VK_FORMAT_UNDEFINED ? "0" : "1") + "\n"; std::string imgprefix; if(colourFormat == VK_FORMAT_R8G8B8A8_UINT || colourFormat == VK_FORMAT_R16G16B16A16_UINT || colourFormat == VK_FORMAT_R32G32B32A32_UINT) { batch.uint = true; imgprefix = "u"; defines += R"( #define COL_TYPE uvec4 #define ALPHA_ADD 3 #define INTERP flat uvec4 ProcessColor(vec4 col) { uvec4 ret = uvec4(16*col); if(col.x < 0.0f) ret.x = 0xfffffff0; if(col.y < 0.0f) ret.y = 0xfffffff0; if(col.z < 0.0f) ret.z = 0xfffffff0; return ret; } )"; } else { defines += R"( #define COL_TYPE vec4 #define ALPHA_ADD 1.75 #define INTERP vec4 ProcessColor(vec4 col) { vec4 ret = col; // this obviously won't overflow F32 but it will overflow anything 16-bit and under if(col.x < 0.0f) ret.x = 100000.0f; if(col.y < 0.0f) ret.y = 100000.0f; if(col.z < 0.0f) ret.z = 100000.0f; return ret; } )"; } defines += "#define DISCARD_X " + std::to_string(150 >> mip) + "\n"; defines += "#define IMAGE_TYPE " + std::string(depth > 1 ? imgprefix + "image3D" : sampleCount != VK_SAMPLE_COUNT_1_BIT ? imgprefix + "image2DMSArray" : imgprefix + "image2DArray") + "\n"; defines += "#define STORE_X " + std::to_string(220 >> mip) + "\n"; defines += "#define STORE_Y " + std::to_string(80 >> mip) + "\n"; defines += "#define STORE_Z " + std::to_string(slice) + "\n"; defines += "#define STORE_SAMPLE " + std::string(sampleCount != VK_SAMPLE_COUNT_1_BIT ? ", 0" : "") + "\n"; defines += "\n\n"; VkPipelineShaderStageCreateInfo vertexShader = CompileShaderModule(common + defines + vertex, ShaderLang::glsl, ShaderStage::vert, "main"); VkPipelineShaderStageCreateInfo fragmentShader = CompileShaderModule(common + defines + pixel, ShaderLang::glsl, ShaderStage::frag, "main"); vkh::RenderPassCreator renderPassCreateInfo; std::vector cols; std::vector atts; VkImageView colView = VK_NULL_HANDLE; if(colourFormat != VK_FORMAT_UNDEFINED) { batch.colImg = AllocatedImage( this, vkh::ImageCreateInfo(batch.extent.width, batch.extent.height, depth, colourFormat, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_STORAGE_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT, mips, arraySize, sampleCount, depth > 1 ? VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT : 0), VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); setName(batch.colImg.image, batch.name + " colImg"); colView = createImageView(vkh::ImageViewCreateInfo( batch.colImg.image, VK_IMAGE_VIEW_TYPE_2D_ARRAY, colourFormat, {}, vkh::ImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, mip, 1, slice, 1))); setName(colView, batch.name + " colView"); VkImageView storeView = createImageView(vkh::ImageViewCreateInfo( batch.colImg.image, depth > 1 ? VK_IMAGE_VIEW_TYPE_3D : VK_IMAGE_VIEW_TYPE_2D_ARRAY, colourFormat, {}, vkh::ImageSubresourceRange(VK_IMAGE_ASPECT_COLOR_BIT, mip, 1, 0, VK_REMAINING_ARRAY_LAYERS))); setName(storeView, batch.name + " storeView"); atts.push_back(colView); renderPassCreateInfo.attachments.push_back(vkh::AttachmentDescription( colourFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE, sampleCount)); cols = {VkAttachmentReference({0, VK_IMAGE_LAYOUT_GENERAL})}; VkDescriptorSetLayout compsetlayout = createDescriptorSetLayout(vkh::DescriptorSetLayoutCreateInfo({ {0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1, VK_SHADER_STAGE_COMPUTE_BIT}, })); batch.compLayout = createPipelineLayout(vkh::PipelineLayoutCreateInfo({compsetlayout})); batch.comppipe = createComputePipeline(vkh::ComputePipelineCreateInfo( batch.compLayout, CompileShaderModule(common + defines + comp, ShaderLang::glsl, ShaderStage::comp, "main"))); batch.compset = allocateDescriptorSet(compsetlayout); vkh::updateDescriptorSets( device, { vkh::WriteDescriptorSet( batch.compset, 0, VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, {vkh::DescriptorImageInfo(storeView, VK_IMAGE_LAYOUT_GENERAL, VK_NULL_HANDLE)}), }); } batch.depthFormat = depthStencilFormat; VkImageView depthView = VK_NULL_HANDLE; if(depthStencilFormat != VK_FORMAT_UNDEFINED) { // can't create 2D views of 3D with depth images, so we just use a normal 2D array image if(depth > 1) { arraySize = depth; depth = 0; } batch.depthImg = AllocatedImage( this, vkh::ImageCreateInfo(batch.extent.width, batch.extent.height, 0, depthStencilFormat, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, mips, arraySize, sampleCount), VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); setName(batch.depthImg.image, batch.name + " depthImg"); VkImageAspectFlags depthAspects = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT; if(depthStencilFormat == VK_FORMAT_D16_UNORM || depthStencilFormat == VK_FORMAT_D32_SFLOAT) depthAspects = VK_IMAGE_ASPECT_DEPTH_BIT; depthView = createImageView( vkh::ImageViewCreateInfo(batch.depthImg.image, VK_IMAGE_VIEW_TYPE_2D, depthStencilFormat, {}, vkh::ImageSubresourceRange(depthAspects, mip, 1, slice, 1))); setName(depthView, batch.name + " depthView"); atts.push_back(depthView); renderPassCreateInfo.attachments.push_back(vkh::AttachmentDescription( depthStencilFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE, sampleCount, VK_ATTACHMENT_LOAD_OP_CLEAR, VK_ATTACHMENT_STORE_OP_STORE)); } renderPassCreateInfo.addSubpass(cols, depthView == VK_NULL_HANDLE ? VK_ATTACHMENT_UNUSED : (uint32_t)renderPassCreateInfo.attachments.size() - 1, VK_IMAGE_LAYOUT_GENERAL); batch.rp = createRenderPass(renderPassCreateInfo); batch.extent.width >>= mip; batch.extent.height >>= mip; batch.fb = createFramebuffer(vkh::FramebufferCreateInfo(batch.rp, atts, batch.extent)); vkh::GraphicsPipelineCreateInfo pipeCreateInfo; pipeCreateInfo.renderPass = batch.rp; pipeCreateInfo.layout = layout; pipeCreateInfo.vertexInputState.vertexBindingDescriptions = {vkh::vertexBind(0, DefaultA2V)}; pipeCreateInfo.vertexInputState.vertexAttributeDescriptions = { vkh::vertexAttr(0, 0, DefaultA2V, pos), vkh::vertexAttr(1, 0, DefaultA2V, col), vkh::vertexAttr(2, 0, DefaultA2V, uv), }; pipeCreateInfo.stages = {vertexShader, fragmentShader}; pipeCreateInfo.rasterizationState.depthClampEnable = VK_FALSE; pipeCreateInfo.rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT; pipeCreateInfo.multisampleState.rasterizationSamples = sampleCount; pipeCreateInfo.depthStencilState.depthTestEnable = VK_TRUE; pipeCreateInfo.depthStencilState.depthWriteEnable = VK_TRUE; pipeCreateInfo.depthStencilState.stencilTestEnable = VK_FALSE; pipeCreateInfo.depthStencilState.front.compareOp = VK_COMPARE_OP_ALWAYS; pipeCreateInfo.depthStencilState.front.passOp = VK_STENCIL_OP_REPLACE; pipeCreateInfo.depthStencilState.front.reference = 0x55; pipeCreateInfo.depthStencilState.front.compareMask = 0xff; pipeCreateInfo.depthStencilState.front.writeMask = 0xff; pipeCreateInfo.depthStencilState.back = pipeCreateInfo.depthStencilState.front; pipeCreateInfo.depthStencilState.depthCompareOp = VK_COMPARE_OP_ALWAYS; batch.depthWritePipe = createGraphicsPipeline(pipeCreateInfo); setName(batch.depthWritePipe, batch.name + " depthWritePipe"); { vkh::GraphicsPipelineCreateInfo depthPipeInfo = pipeCreateInfo; depthPipeInfo.depthStencilState.depthWriteEnable = VK_FALSE; depthPipeInfo.depthStencilState.depthTestEnable = VK_TRUE; depthPipeInfo.depthStencilState.depthCompareOp = VK_COMPARE_OP_EQUAL; batch.depthEqualPipe = createGraphicsPipeline(depthPipeInfo); setName(batch.depthEqualPipe, batch.name + " depthEqualPipe"); } { vkh::GraphicsPipelineCreateInfo dynamicPipe = pipeCreateInfo; dynamicPipe.depthStencilState.depthWriteEnable = VK_FALSE; dynamicPipe.depthStencilState.depthTestEnable = VK_FALSE; dynamicPipe.dynamicState.dynamicStates = {VK_DYNAMIC_STATE_VIEWPORT}; dynamicPipe.viewportState.scissors = {{{95 >> mip, 245 >> mip}, {20U >> mip, 8U >> mip}}}; batch.fixedScissorPassPipe = createGraphicsPipeline(dynamicPipe); setName(batch.fixedScissorPassPipe, batch.name + " fixedScissorPassPipe"); dynamicPipe.viewportState.scissors = {{{95 >> mip, 245 >> mip}, {8U >> mip, 8U >> mip}}}; batch.fixedScissorFailPipe = createGraphicsPipeline(dynamicPipe); setName(batch.fixedScissorFailPipe, batch.name + " fixedScissorFailPipe"); dynamicPipe.dynamicState.dynamicStates.push_back(VK_DYNAMIC_STATE_SCISSOR); dynamicPipe.dynamicState.dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_REFERENCE); batch.dynamicStencilRefPipe = createGraphicsPipeline(dynamicPipe); setName(batch.dynamicStencilRefPipe, batch.name + " dynamicStencilRefPipe"); dynamicPipe.dynamicState.dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK); dynamicPipe.dynamicState.dynamicStates.push_back(VK_DYNAMIC_STATE_STENCIL_WRITE_MASK); batch.dynamicStencilMaskPipe = createGraphicsPipeline(dynamicPipe); setName(batch.dynamicStencilMaskPipe, batch.name + " dynamicStencilMaskPipe"); } pipeCreateInfo.depthStencilState.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL; { vkh::GraphicsPipelineCreateInfo depthPipeInfo = pipeCreateInfo; depthPipeInfo.dynamicState.dynamicStates.push_back(VK_DYNAMIC_STATE_DEPTH_BOUNDS); depthPipeInfo.depthStencilState.depthBoundsTestEnable = VK_TRUE; batch.depthPipe = createGraphicsPipeline(depthPipeInfo); setName(batch.depthPipe, batch.name + " depthPipe"); } pipeCreateInfo.depthStencilState.stencilTestEnable = VK_TRUE; batch.stencilWritePipe = createGraphicsPipeline(pipeCreateInfo); setName(batch.stencilWritePipe, batch.name + " stencilWritePipe"); pipeCreateInfo.depthStencilState.stencilTestEnable = VK_FALSE; batch.backgroundPipe = createGraphicsPipeline(pipeCreateInfo); setName(batch.backgroundPipe, batch.name + " backgroundPipe"); pipeCreateInfo.stages = {vertexShader}; pipeCreateInfo.depthStencilState.stencilTestEnable = VK_TRUE; pipeCreateInfo.depthStencilState.front.reference = 0x33; batch.noFsPipe = createGraphicsPipeline(pipeCreateInfo); setName(batch.noFsPipe, batch.name + " noFsPipe"); pipeCreateInfo.stages = {vertexShader, fragmentShader}; pipeCreateInfo.depthStencilState.stencilTestEnable = VK_FALSE; pipeCreateInfo.depthStencilState.front.reference = 0x55; pipeCreateInfo.depthStencilState.stencilTestEnable = VK_TRUE; pipeCreateInfo.depthStencilState.front.compareOp = VK_COMPARE_OP_GREATER; batch.basicPipe = createGraphicsPipeline(pipeCreateInfo); setName(batch.basicPipe, batch.name + " basicPipe"); pipeCreateInfo.depthStencilState.stencilTestEnable = VK_FALSE; { vkh::GraphicsPipelineCreateInfo maskPipeInfo = pipeCreateInfo; maskPipeInfo.colorBlendState.attachments[0].colorWriteMask = 0x3; batch.colorMaskPipe = createGraphicsPipeline(maskPipeInfo); setName(batch.colorMaskPipe, batch.name + " colorMaskPipe"); } pipeCreateInfo.rasterizationState.cullMode = VK_CULL_MODE_FRONT_BIT; batch.cullFrontPipe = createGraphicsPipeline(pipeCreateInfo); setName(batch.cullFrontPipe, batch.name + " cullFrontPipe"); pipeCreateInfo.rasterizationState.cullMode = VK_CULL_MODE_BACK_BIT; pipeCreateInfo.depthStencilState.depthBoundsTestEnable = VK_TRUE; pipeCreateInfo.depthStencilState.minDepthBounds = 0.0f; pipeCreateInfo.depthStencilState.maxDepthBounds = 1.0f; batch.depthBoundsPipe1 = createGraphicsPipeline(pipeCreateInfo); setName(batch.depthBoundsPipe1, batch.name + " depthBoundsPipe1"); pipeCreateInfo.depthStencilState.minDepthBounds = 0.4f; pipeCreateInfo.depthStencilState.maxDepthBounds = 0.6f; batch.depthBoundsPipe2 = createGraphicsPipeline(pipeCreateInfo); setName(batch.depthBoundsPipe2, batch.name + " depthBoundsPipe2"); pipeCreateInfo.depthStencilState.depthBoundsTestEnable = VK_FALSE; pipeCreateInfo.stages[1] = CompileShaderModule(common + defines + mspixel, ShaderLang::glsl, ShaderStage::frag, "main"); pipeCreateInfo.depthStencilState.depthWriteEnable = VK_TRUE; VkSampleMask not3 = 0x7; if(sampleCount == VK_SAMPLE_COUNT_4_BIT) pipeCreateInfo.multisampleState.pSampleMask = ¬3; batch.sampleColourPipe = createGraphicsPipeline(pipeCreateInfo); setName(batch.sampleColourPipe, batch.name + " sampleColourPipe"); } void RunDraw(VkCommandBuffer cmd, const PixelHistory::draw &draw, uint32_t numInstances = 1) { vkCmdDraw(cmd, draw.count, numInstances, draw.first, 0); } void RunBatch(const VKTestBatch &b, VkCommandBuffer cmd) { float factor = float(b.extent.width) / float(mainWindow->scissor.extent.width); VkViewport v = {}; v.width = (float)b.extent.width - (20.0f * factor); v.height = -(float)b.extent.height + (20.0f * factor); v.x = floorf(10.0f * factor); v.y = (float)b.extent.height - ceilf(10.0f * factor); v.maxDepth = 1.0f; VkRect2D scissor = {{0, 0}, b.extent}; vkCmdSetViewport(cmd, 0, 1, &v); vkCmdSetScissor(cmd, 0, 1, &scissor); // draw the setup triangles pushMarker(cmd, "Setup"); { setMarker(cmd, "Depth Write"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.depthWritePipe); RunDraw(cmd, PixelHistory::DepthWrite); setMarker(cmd, "Depth Equal Setup"); RunDraw(cmd, PixelHistory::DepthEqualSetup); setMarker(cmd, "Unbound Shader"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.noFsPipe); RunDraw(cmd, PixelHistory::UnboundPS); setMarker(cmd, "Stencil Write"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.stencilWritePipe); RunDraw(cmd, PixelHistory::StencilWrite); setMarker(cmd, "Background"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.backgroundPipe); RunDraw(cmd, PixelHistory::Background); setMarker(cmd, "Cull Front"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.cullFrontPipe); RunDraw(cmd, PixelHistory::CullFront); setMarker(cmd, "Depth Bounds Prep"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.depthBoundsPipe1); RunDraw(cmd, PixelHistory::DepthBoundsPrep); setMarker(cmd, "Depth Bounds Clip"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.depthBoundsPipe2); RunDraw(cmd, PixelHistory::DepthBoundsClip); } popMarker(cmd); pushMarker(cmd, "Stress Test"); { pushMarker(cmd, "Lots of Drawcalls"); { setMarker(cmd, "300 Draws"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.depthWritePipe); for(int d = 0; d < 300; ++d) RunDraw(cmd, PixelHistory::Draws300); } popMarker(cmd); setMarker(cmd, "300 Instances"); RunDraw(cmd, PixelHistory::Instances300, 300); } popMarker(cmd); setMarker(cmd, "Simple Test"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.basicPipe); RunDraw(cmd, PixelHistory::MainTest); setMarker(cmd, "Scissor Fail"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.fixedScissorFailPipe); RunDraw(cmd, PixelHistory::ScissorFail); setMarker(cmd, "Scissor Pass"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.fixedScissorPassPipe); RunDraw(cmd, PixelHistory::ScissorPass); setMarker(cmd, "Stencil Ref"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.dynamicStencilRefPipe); vkCmdSetScissor(cmd, 0, 1, &scissor); vkCmdSetStencilReference(cmd, VK_STENCIL_FACE_FRONT_AND_BACK, 0x67); RunDraw(cmd, PixelHistory::StencilRef); setMarker(cmd, "Stencil Mask"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.dynamicStencilMaskPipe); vkCmdSetStencilCompareMask(cmd, VK_STENCIL_FACE_FRONT_AND_BACK, 0); vkCmdSetStencilWriteMask(cmd, VK_STENCIL_FACE_FRONT_AND_BACK, 0); RunDraw(cmd, PixelHistory::StencilMask); setMarker(cmd, "Depth Test"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.depthPipe); vkCmdSetDepthBounds(cmd, 0.15f, 1.0f); RunDraw(cmd, PixelHistory::DepthTest); setMarker(cmd, "Sample Colouring"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.sampleColourPipe); RunDraw(cmd, PixelHistory::SampleColour); setMarker(cmd, "Depth Equal Fail"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.depthEqualPipe); RunDraw(cmd, PixelHistory::DepthEqualFail); setMarker(cmd, "Depth Equal Pass"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.depthEqualPipe); if(b.depthFormat == VK_FORMAT_D24_UNORM_S8_UINT) RunDraw(cmd, PixelHistory::DepthEqualPass24); else if(b.depthFormat == VK_FORMAT_D16_UNORM_S8_UINT || b.depthFormat == VK_FORMAT_D16_UNORM) RunDraw(cmd, PixelHistory::DepthEqualPass16); else RunDraw(cmd, PixelHistory::DepthEqualPass32); setMarker(cmd, "Colour Masked"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.colorMaskPipe); RunDraw(cmd, PixelHistory::ColourMask); setMarker(cmd, "Overflowing"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.backgroundPipe); RunDraw(cmd, PixelHistory::OverflowingDraw); setMarker(cmd, "Per-Fragment discarding"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.backgroundPipe); RunDraw(cmd, PixelHistory::PerFragDiscard); setMarker(cmd, "No Output Shader"); vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, b.noFsPipe); RunDraw(cmd, PixelHistory::UnboundPS); } int main() { // initialise, create window, create context, etc if(!Init()) return 3; bool nestedSecondaries = hasExt(VK_EXT_NESTED_COMMAND_BUFFER_EXTENSION_NAME); if(nestedSecondaries) { getPhysFeatures2(&nestedFeats); if(!nestedFeats.nestedCommandBuffer) nestedSecondaries = false; if(!nestedFeats.nestedCommandBufferRendering) nestedSecondaries = false; getPhysProperties2(&nestedProps); if(nestedProps.maxCommandBufferNestingLevel < 5) nestedSecondaries = false; } if(nestedSecondaries) TEST_LOG("Running tests with nested secondaries"); PixelHistory::init(); AllocatedBuffer vb( this, vkh::BufferCreateInfo(sizeof(DefaultA2V) * PixelHistory::vb.size(), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT), VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); vb.upload(PixelHistory::vb.data(), PixelHistory::vb.size() * sizeof(DefaultA2V)); VkImage bbBlitSource = VK_NULL_HANDLE; struct DepthVariant { VkFormat fmt; std::string name; bool supported; }; std::vector depthFormats = {{VK_FORMAT_D24_UNORM_S8_UINT, "D24S8", false}, {VK_FORMAT_D16_UNORM_S8_UINT, "D16S8", false}, {VK_FORMAT_D32_SFLOAT, "D32", false}, {VK_FORMAT_D16_UNORM, "D16", false}}; for(DepthVariant &fmt : depthFormats) { VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(phys, fmt.fmt, &props); if(props.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) fmt.supported = true; } // work around an NV bug with D32S8 when rendering to 3D textures is happening, prefer D24S8 VkFormat defaultDepthFormat = depthFormats[0].supported ? VK_FORMAT_D24_UNORM_S8_UINT : VK_FORMAT_D32_SFLOAT_S8_UINT; BuildTestBatch("Basic", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, defaultDepthFormat); bbBlitSource = batches.back().colImg.image; BuildTestBatch("MSAA", VK_SAMPLE_COUNT_4_BIT, VK_FORMAT_R8G8B8A8_UNORM, defaultDepthFormat); BuildTestBatch("Colour Only", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_UNDEFINED); BuildTestBatch("Depth Only", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_UNDEFINED, defaultDepthFormat); BuildTestBatch("Mip & Slice", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, defaultDepthFormat, 2, 3); BuildTestBatch("Slice MSAA", VK_SAMPLE_COUNT_4_BIT, VK_FORMAT_R8G8B8A8_UNORM, defaultDepthFormat, -1, 3); BuildTestBatch("Secondary", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, defaultDepthFormat); batches.back().secondary = true; if(nestedSecondaries) { BuildTestBatch("Secondary Nested", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, defaultDepthFormat); batches.back().nested = true; } BuildTestBatch("3D texture", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, defaultDepthFormat, -1, -1, 8); for(DepthVariant &fmt : depthFormats) { if(fmt.supported) BuildTestBatch(fmt.name, VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UNORM, fmt.fmt); } BuildTestBatch("F16 UNORM", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R16G16B16A16_UNORM, defaultDepthFormat); BuildTestBatch("F16 FLOAT", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R16G16B16A16_SFLOAT, defaultDepthFormat); BuildTestBatch("F32 FLOAT", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R32G32B32A32_SFLOAT, defaultDepthFormat); BuildTestBatch("8-bit uint", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R8G8B8A8_UINT, defaultDepthFormat); BuildTestBatch("16-bit uint", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R16G16B16A16_UINT, defaultDepthFormat); BuildTestBatch("32-bit uint", VK_SAMPLE_COUNT_1_BIT, VK_FORMAT_R32G32B32A32_UINT, defaultDepthFormat); // for dispatch access, ensure all color images are in GENERAL as render passes for slice tests // will only transition that one slice for(const VKTestBatch &b : batches) { if(b.colImg.image == VK_NULL_HANDLE) continue; VkCommandBuffer cmd = GetCommandBuffer(); vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); vkh::cmdPipelineBarrier( cmd, {vkh::ImageMemoryBarrier(VK_ACCESS_NONE, VK_ACCESS_MEMORY_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_GENERAL, b.colImg.image)}); vkEndCommandBuffer(cmd); Submit(99, 99, {cmd}); } while(Running()) { std::vector primaries; std::vector secondaries; for(const VKTestBatch &b : batches) { VkCommandBuffer cmd = GetCommandBuffer(); vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); // set clears correctly for depth-only batches, the extra clear value on colour only is ignored std::vector clears; if(b.colImg.image == VK_NULL_HANDLE) { clears = {vkh::ClearValue(1.0f, 0)}; } else { clears = {vkh::ClearValue(0.2f, 0.2f, 0.2f, 1.0f), vkh::ClearValue(1.0f, 0)}; if(b.uint) { clears[0].color.uint32[0] = 80; clears[0].color.uint32[1] = 80; clears[0].color.uint32[2] = 80; clears[0].color.uint32[3] = 16; } } if(b.secondary) { pushMarker(cmd, "Batch: " + b.name); { setMarker(cmd, "Begin RenderPass"); vkCmdBeginRenderPass(cmd, vkh::RenderPassBeginInfo(b.rp, b.fb, {{0, 0}, b.extent}, clears), VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS); VkCommandBuffer cmd2 = GetCommandBuffer(VK_COMMAND_BUFFER_LEVEL_SECONDARY); secondaries.push_back(cmd2); vkBeginCommandBuffer( cmd2, vkh::CommandBufferBeginInfo(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, vkh::CommandBufferInheritanceInfo(b.rp, 0, b.fb))); vkh::cmdBindVertexBuffers(cmd2, 0, {vb.buffer}, {0}); RunBatch(b, cmd2); vkEndCommandBuffer(cmd2); vkCmdExecuteCommands(cmd, 1, &cmd2); vkCmdEndRenderPass(cmd); } popMarker(cmd); } else if(b.nested) { pushMarker(cmd, "Batch: " + b.name); { setMarker(cmd, "Begin RenderPass"); vkCmdBeginRenderPass(cmd, vkh::RenderPassBeginInfo(b.rp, b.fb, {{0, 0}, b.extent}, clears), VK_SUBPASS_CONTENTS_SECONDARY_COMMAND_BUFFERS); VkCommandBuffer cmd3 = GetCommandBuffer(VK_COMMAND_BUFFER_LEVEL_SECONDARY); secondaries.push_back(cmd3); vkBeginCommandBuffer( cmd3, vkh::CommandBufferBeginInfo(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, vkh::CommandBufferInheritanceInfo(b.rp, 0, b.fb))); vkh::cmdBindVertexBuffers(cmd3, 0, {vb.buffer}, {0}); RunBatch(b, cmd3); vkEndCommandBuffer(cmd3); VkCommandBuffer cmd2 = GetCommandBuffer(VK_COMMAND_BUFFER_LEVEL_SECONDARY); secondaries.push_back(cmd2); vkBeginCommandBuffer( cmd2, vkh::CommandBufferBeginInfo(VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT, vkh::CommandBufferInheritanceInfo(b.rp, 0, b.fb))); vkCmdExecuteCommands(cmd2, 1, &cmd3); vkEndCommandBuffer(cmd2); vkCmdExecuteCommands(cmd, 1, &cmd2); vkCmdEndRenderPass(cmd); } popMarker(cmd); } else { pushMarker(cmd, "Batch: " + b.name); { setMarker(cmd, "Begin RenderPass"); vkCmdBeginRenderPass(cmd, vkh::RenderPassBeginInfo(b.rp, b.fb, {{0, 0}, b.extent}, clears), VK_SUBPASS_CONTENTS_INLINE); RunBatch(b, cmd); vkCmdEndRenderPass(cmd); setMarker(cmd, "Compute write"); if(b.comppipe != VK_NULL_HANDLE) { vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, b.comppipe); vkh::cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_COMPUTE, b.compLayout, 0, {b.compset}, {}); vkCmdDispatch(cmd, 1, 1, 1); } } popMarker(cmd); } vkEndCommandBuffer(cmd); primaries.push_back(cmd); } { VkCommandBuffer cmd = GetCommandBuffer(); vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); if(bbBlitSource != VK_NULL_HANDLE) { vkh::cmdPipelineBarrier( cmd, {vkh::ImageMemoryBarrier( VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT, VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL, bbBlitSource)}); blitToSwap(cmd, bbBlitSource, VK_IMAGE_LAYOUT_GENERAL, mainWindow->GetImage(mainWindow->imgIndex), VK_IMAGE_LAYOUT_GENERAL); } FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); vkEndCommandBuffer(cmd); primaries.push_back(cmd); } Submit(0, 1, primaries, secondaries); Present(); } return 0; } }; REGISTER_TEST();