diff --git a/renderdoc/api/replay/data_types.h b/renderdoc/api/replay/data_types.h index 3dbb1f1b7f..2423a486d1 100644 --- a/renderdoc/api/replay/data_types.h +++ b/renderdoc/api/replay/data_types.h @@ -2100,12 +2100,13 @@ struct PixelModification { return eventId == o.eventId && directShaderWrite == o.directShaderWrite && unboundPS == o.unboundPS && fragIndex == o.fragIndex && primitiveID == o.primitiveID && - preMod == o.preMod && shaderOut == o.shaderOut && postMod == o.postMod && - sampleMasked == o.sampleMasked && backfaceCulled == o.backfaceCulled && - depthClipped == o.depthClipped && depthBoundsFailed == o.depthBoundsFailed && - viewClipped == o.viewClipped && scissorClipped == o.scissorClipped && - shaderDiscarded == o.shaderDiscarded && depthTestFailed == o.depthTestFailed && - stencilTestFailed == o.stencilTestFailed; + preMod == o.preMod && shaderOut == o.shaderOut && + shaderOutDualSrc == o.shaderOutDualSrc && blendSrc == o.blendSrc && + blendDst == o.blendDst && postMod == o.postMod && sampleMasked == o.sampleMasked && + backfaceCulled == o.backfaceCulled && depthClipped == o.depthClipped && + depthBoundsFailed == o.depthBoundsFailed && viewClipped == o.viewClipped && + scissorClipped == o.scissorClipped && shaderDiscarded == o.shaderDiscarded && + depthTestFailed == o.depthTestFailed && stencilTestFailed == o.stencilTestFailed; } bool operator<(const PixelModification &o) const { @@ -2123,6 +2124,12 @@ struct PixelModification return preMod < o.preMod; if(!(shaderOut == o.shaderOut)) return shaderOut < o.shaderOut; + if(!(shaderOutDualSrc == o.shaderOutDualSrc)) + return shaderOutDualSrc < o.shaderOutDualSrc; + if(!(blendSrc == o.blendSrc)) + return blendSrc < o.blendSrc; + if(!(blendDst == o.blendDst)) + return blendDst < o.blendDst; if(!(postMod == o.postMod)) return postMod < o.postMod; if(!(sampleMasked == o.sampleMasked)) @@ -2175,6 +2182,21 @@ pixel. :type: ModificationValue )"); ModificationValue shaderOut; + DOCUMENT(R"(The value that this fragment wrote from the pixel shader to the second output. + +:type: ModificationValue +)"); + ModificationValue shaderOutDualSrc; + DOCUMENT(R"(The source component in the blend equation for this fragment. + +:type: ModificationValue +)"); + ModificationValue blendSrc; + DOCUMENT(R"(The destination component in the blend equation for this fragment. + +:type: ModificationValue +)"); + ModificationValue blendDst; DOCUMENT(R"(The value of the texture after this fragment ran. :type: ModificationValue diff --git a/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp b/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp index 77f436b73f..efa940f10b 100644 --- a/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp +++ b/renderdoc/driver/d3d11/d3d11_pixelhistory.cpp @@ -1940,6 +1940,9 @@ rdcarray D3D11Replay::PixelHistory(rdcarray event // data[3].x (depth) unused // fragments writing to the pixel in this event with original shader mod.shaderOut.col.intValue[1] = int32_t(data[3].y); + mod.shaderOutDualSrc.SetInvalid(); + mod.blendSrc.SetInvalid(); + mod.blendDst.SetInvalid(); } } @@ -2316,6 +2319,9 @@ rdcarray D3D11Replay::PixelHistory(rdcarray event byte *data = shadoutStoreData + sizeof(Vec4f) * pixstoreStride * offsettedSlot; memcpy(&history[h].shaderOut.col.uintValue[0], data, 4 * sizeof(float)); + history[h].shaderOutDualSrc.SetInvalid(); + history[h].blendSrc.SetInvalid(); + history[h].blendDst.SetInvalid(); // primitive ID is in the next slot after that memcpy(&history[h].primitiveID, data + sizeof(Vec4f), sizeof(uint32_t)); diff --git a/renderdoc/driver/gl/gl_pixelhistory.cpp b/renderdoc/driver/gl/gl_pixelhistory.cpp index 515608be3e..648f81805a 100644 --- a/renderdoc/driver/gl/gl_pixelhistory.cpp +++ b/renderdoc/driver/gl/gl_pixelhistory.cpp @@ -1303,6 +1303,9 @@ std::map QueryNumFragmentsByEvent( numFragments = history[i].shaderOut.stencil; history[i].shaderOut.stencil = history[i].postMod.stencil; } + history[i].shaderOutDualSrc.SetInvalid(); + history[i].blendSrc.SetInvalid(); + history[i].blendDst.SetInvalid(); eventFragments.emplace(modEvents[i].eventId, numFragments); @@ -1587,6 +1590,9 @@ void QueryShaderOutPerFragment(WrappedOpenGL *driver, GLReplay *replay, int(historyIndex - history.begin())); historyIndex->shaderOut.stencil = oldStencil; } + historyIndex->shaderOutDualSrc.SetInvalid(); + historyIndex->blendSrc.SetInvalid(); + historyIndex->blendDst.SetInvalid(); historyIndex++; } diff --git a/renderdoc/driver/vulkan/vk_pixelhistory.cpp b/renderdoc/driver/vulkan/vk_pixelhistory.cpp index 500ef7b28a..dfbe367477 100644 --- a/renderdoc/driver/vulkan/vk_pixelhistory.cpp +++ b/renderdoc/driver/vulkan/vk_pixelhistory.cpp @@ -3972,6 +3972,9 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even mod.preMod.SetInvalid(); mod.postMod.SetInvalid(); mod.shaderOut.SetInvalid(); + mod.shaderOutDualSrc.SetInvalid(); + mod.blendSrc.SetInvalid(); + mod.blendDst.SetInvalid(); h++; continue; } @@ -3999,6 +4002,9 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even int32_t fragsClipped = int32_t(ei.dsWithShaderDiscard[4]); mod.shaderOut.col.intValue[0] = frags; mod.shaderOut.col.intValue[1] = fragsClipped; + mod.shaderOutDualSrc.SetInvalid(); + mod.blendSrc.SetInvalid(); + mod.blendDst.SetInvalid(); bool someFragsClipped = (fragsClipped < frags); mod.primitiveID = someFragsClipped; // Draws in secondary command buffers will fail this check, @@ -4145,6 +4151,11 @@ rdcarray VulkanReplay::PixelHistory(rdcarray even { history[h].preMod = history[h - 1].postMod; } + + history[h].shaderOutDualSrc.SetInvalid(); + + history[h].blendSrc.SetInvalid(); + history[h].blendDst.SetInvalid(); } // check the depth value between premod/shaderout against the known test if we have valid diff --git a/renderdoc/replay/renderdoc_serialise.inl b/renderdoc/replay/renderdoc_serialise.inl index 058149e3bb..4fd593c975 100644 --- a/renderdoc/replay/renderdoc_serialise.inl +++ b/renderdoc/replay/renderdoc_serialise.inl @@ -893,6 +893,9 @@ void DoSerialise(SerialiserType &ser, PixelModification &el) SERIALISE_MEMBER(preMod); SERIALISE_MEMBER(shaderOut); + SERIALISE_MEMBER(shaderOutDualSrc); + SERIALISE_MEMBER(blendSrc); + SERIALISE_MEMBER(blendDst); SERIALISE_MEMBER(postMod); SERIALISE_MEMBER(sampleMasked); @@ -906,7 +909,7 @@ void DoSerialise(SerialiserType &ser, PixelModification &el) SERIALISE_MEMBER(stencilTestFailed); SERIALISE_MEMBER(predicationSkipped); - SIZE_CHECK(100); + SIZE_CHECK(172); } template diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt index 108bf6d083..80fd454306 100644 --- a/util/test/demos/CMakeLists.txt +++ b/util/test/demos/CMakeLists.txt @@ -87,6 +87,7 @@ set(VULKAN_SRC vk/vk_test.cpp vk/vk_test.h vk/vk_adv_cbuffer_zoo.cpp + vk/vk_blend.cpp vk/vk_buffer_truncation.cpp vk/vk_cbuffer_zoo.cpp vk/vk_compute_only.cpp @@ -98,6 +99,7 @@ set(VULKAN_SRC vk/vk_discard_rects.cpp vk/vk_discard_zoo.cpp vk/vk_draw_zoo.cpp + vk/vk_dual_source.cpp vk/vk_dynamic_rendering.cpp vk/vk_empty_capture.cpp vk/vk_ext_buffer_address.cpp diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index 19b54ca8fd..5ef7b36956 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -296,12 +296,14 @@ + + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index d3cc6524c0..798331d145 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -673,6 +673,12 @@ D3D12\demos + + Vulkan\demos + + + Vulkan\demos + diff --git a/util/test/demos/vk/vk_blend.cpp b/util/test/demos/vk/vk_blend.cpp new file mode 100644 index 0000000000..232343c7af --- /dev/null +++ b/util/test/demos/vk/vk_blend.cpp @@ -0,0 +1,246 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2023 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_Blend, VulkanGraphicsTest) +{ + static constexpr const char *Description = + "Draws a triangle repeatedly to test blending within a single drawcall"; + + const DefaultA2V TemplateTriangleRed[3] = { + {Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(1 / 255.f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(1 / 255.f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, -0.5f, 0.0f), Vec4f(1 / 255.f, 0.0f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + const int TRIANGLES_RED_INDEX = 0; + const int NUM_TRIANGLES_RED = 16; + const DefaultA2V TemplateTriangleGreen[3] = { + {Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(0.0f, 1 / 255.f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(0.0f, 1 / 255.f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, -0.5f, 0.0f), Vec4f(0.0f, 1 / 255.f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + const int TRIANGLES_GREEN_INDEX = TRIANGLES_RED_INDEX + NUM_TRIANGLES_RED; + const int NUM_TRIANGLES_GREEN = 255; + const DefaultA2V TemplateTriangleBlue[3] = { + {Vec3f(-0.5f, -0.5f, 0.0f), Vec4f(0.0f, 0.0f, 1 / 255.f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(0.0f, 0.5f, 0.0f), Vec4f(0.0f, 0.0f, 1 / 255.f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(0.5f, -0.5f, 0.0f), Vec4f(0.0f, 0.0f, 1 / 255.f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + const int TRIANGLES_BLUE_INDEX = TRIANGLES_GREEN_INDEX + NUM_TRIANGLES_GREEN; + const int NUM_TRIANGLES_BLUE = 512; + + const int NUM_TRIANGLES_TOTAL = TRIANGLES_BLUE_INDEX + NUM_TRIANGLES_BLUE; + + int main() + { + // initialise, create window, create context, etc + requestedSwapChainFormat = VK_FORMAT_B8G8R8A8_UNORM; + if(!Init()) + return 3; + + VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo()); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = mainWindow->rp; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + pipeCreateInfo.colorBlendState.attachments = {colorBlendAttachment}; + + 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 = { + CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(VKDefaultPixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo); + + std::vector triangles; + triangles.reserve(3 * NUM_TRIANGLES_TOTAL); + for(int i = 0; i < NUM_TRIANGLES_RED; i++) + { + triangles.push_back(TemplateTriangleRed[0]); + triangles.push_back(TemplateTriangleRed[1]); + triangles.push_back(TemplateTriangleRed[2]); + } + for(int i = 0; i < NUM_TRIANGLES_GREEN; i++) + { + triangles.push_back(TemplateTriangleGreen[0]); + triangles.push_back(TemplateTriangleGreen[1]); + triangles.push_back(TemplateTriangleGreen[2]); + } + for(int i = 0; i < NUM_TRIANGLES_BLUE; i++) + { + triangles.push_back(TemplateTriangleBlue[0]); + triangles.push_back(TemplateTriangleBlue[1]); + triangles.push_back(TemplateTriangleBlue[2]); + } + + AllocatedBuffer vb(this, vkh::BufferCreateInfo(sizeof(DefaultA2V) * 3 * NUM_TRIANGLES_TOTAL, + VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + vb.upload(triangles.data(), sizeof(DefaultA2V) * 3 * NUM_TRIANGLES_TOTAL); + + AllocatedImage offimg(this, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R32G32B32A32_SFLOAT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + AllocatedImage offimgMS( + this, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R16G16B16A16_SFLOAT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, 1, 1, VK_SAMPLE_COUNT_4_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + while(Running()) + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + VkImage swapimg = + StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + pushMarker(cmd, "Clear"); + vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimg.image), + }); + + vkCmdClearColorImage(cmd, offimg.image, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimgMS.image), + }); + + vkCmdClearColorImage(cmd, offimgMS.image, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + popMarker(cmd); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport); + vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor); + vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); + pushMarker(cmd, "Red: groups of repeated draws"); + for(int i = 1; i <= NUM_TRIANGLES_RED; i *= 2) + { + vkCmdDraw(cmd, 3 * i, 1, TRIANGLES_RED_INDEX, 0); + } + setMarker(cmd, "End of red"); + popMarker(cmd); + pushMarker(cmd, "Green: 255 (the maximum we can handle) in a single drawcall"); + vkCmdDraw(cmd, 3 * NUM_TRIANGLES_GREEN, 1, 3 * TRIANGLES_GREEN_INDEX, 0); + popMarker(cmd); + // Note: this is indistinguishable from 255 triangles since 255 * 1/255 is 1, + // and 512 * 1/255 > 1 but gets clamped to 1 + pushMarker(cmd, "Blue: 512 (more than the maximum) in a single drawcall"); + vkCmdDraw(cmd, 3 * NUM_TRIANGLES_BLUE, 1, 3 * TRIANGLES_BLUE_INDEX, 0); + popMarker(cmd); + + vkCmdEndRenderPass(cmd); + + pushMarker(cmd, "Clear"); + vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimg.image), + }); + + vkCmdClearColorImage(cmd, offimg.image, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimgMS.image), + }); + + vkCmdClearColorImage(cmd, offimgMS.image, VK_IMAGE_LAYOUT_GENERAL, + vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1, + vkh::ImageSubresourceRange()); + popMarker(cmd); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + + pushMarker(cmd, "All of the above in a single drawcall"); + // This _is_ distinguishable from 512 blue triangles since we have a mix of red, green, and + // blue + vkCmdDraw(cmd, 3 * NUM_TRIANGLES_TOTAL, 1, 3 * TRIANGLES_RED_INDEX, 0); + popMarker(cmd); + + setMarker(cmd, "Test End"); + + vkCmdEndRenderPass(cmd); + + FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkEndCommandBuffer(cmd); + + Submit(0, 1, {cmd}); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/demos/vk/vk_dual_source.cpp b/util/test/demos/vk/vk_dual_source.cpp new file mode 100644 index 0000000000..ee5116032f --- /dev/null +++ b/util/test/demos/vk/vk_dual_source.cpp @@ -0,0 +1,266 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2023 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" + +static std::string common = R"EOSHADER( + +#version 460 core + +#define v2f v2f_block \ +{ \ + vec4 pos; \ + vec4 col; \ + vec4 uv; \ +} + +)EOSHADER"; + +const std::string pixel = common + R"EOSHADER( + +layout(location = 0) in v2f vertIn; + +layout(location = 0, index = 0) out vec4 outColor0; +layout(location = 0, index = 1) out vec4 outColor1; + +void main() { + vec3 fragColor = vertIn.col.rgb; + + outColor0 = vec4(fragColor, 1.0); + outColor1 = vec4(fragColor.brg, 1.0); +} +)EOSHADER"; + +// This shader is equivalent to the other one, just with a much more complicated-looking layout +const std::string pixel_complicated = common + R"EOSHADER( + +layout(location = 0) in v2f vertIn; + +layout(location = 0, index = 0, component = 0) out float outColor0_r; +layout(location = 0, /* index = 0, */ component = 1) out float outColor0_g; +layout(location = 0, /* index = 0, */ component = 2) out vec2 outColor0_ba; +layout(location = 0, index = 1/* , component = 0 */) out float outColor1_r; +layout(location = 0, index = 1, component = 1) out vec2 outColor1_gb; +layout(location = 0, index = 1, component = 3) out float outColor1_a; +// Test so that we have an output builtin. depth_unchanged means nothing interesting +// actually happens (not sure why it exists, but it's nice here) +layout(depth_unchanged) out float gl_FragDepth; + +void main() { + vec4 outColor0, outColor1; + + vec3 fragColor = vertIn.col.rgb; + + outColor0 = vec4(fragColor, 1.0); + outColor1 = vec4(fragColor.brg, 1.0); + + outColor0_r = outColor0.r; + outColor0_g = outColor0.g; + outColor0_ba = outColor0.ba; + outColor1_r = outColor1.r; + outColor1_gb = outColor1.gb; + outColor1_a = outColor1.a; + gl_FragDepth = gl_FragCoord.z; +} +)EOSHADER"; + +RD_TEST(VK_Dual_Source, VulkanGraphicsTest) +{ + static constexpr const char *Description = "Draws a pair of triangles using dual source blending"; + + const DefaultA2V Triangles[12] = { + // Two partially overlapping triangles + {Vec3f(+0.0f, -0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.0f, 0.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(+0.5f, +0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.0f, 0.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(-0.5f, +0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.0f, 0.0f), Vec2f(1.0f, 0.0f)}, + {Vec3f(-0.25f, -0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.5f, 0.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(+0.75f, -0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.5f, 0.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(+0.25f, +0.5f, 0.0f), Vec4f(0.5f, 0.0f, 0.5f, 0.0f), Vec2f(1.0f, 0.0f)}, + }; + + void Prepare(int argc, char **argv) + { + features.dualSrcBlend = VK_TRUE; + VulkanGraphicsTest::Prepare(argc, argv); + } + + void clear(VkCommandBuffer cmd, VkImage swapimg, const AllocatedImage &offimg, + const AllocatedImage &offimgMS, const VkClearColorValue *pColor) + { + pushMarker(cmd, "Clear"); + vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL, pColor, 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimg.image), + }); + + vkCmdClearColorImage(cmd, offimg.image, VK_IMAGE_LAYOUT_GENERAL, pColor, 1, + vkh::ImageSubresourceRange()); + + vkh::cmdPipelineBarrier( + cmd, { + vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_UNDEFINED, + VK_IMAGE_LAYOUT_GENERAL, offimgMS.image), + }); + + vkCmdClearColorImage(cmd, offimgMS.image, VK_IMAGE_LAYOUT_GENERAL, pColor, 1, + vkh::ImageSubresourceRange()); + popMarker(cmd); + } + + int main() + { + // initialise, create window, create context, etc + requestedSwapChainFormat = VK_FORMAT_B8G8R8A8_UNORM; + if(!Init()) + return 3; + + VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo()); + + vkh::GraphicsPipelineCreateInfo pipeCreateInfo; + + pipeCreateInfo.layout = layout; + pipeCreateInfo.renderPass = mainWindow->rp; + + VkPipelineColorBlendAttachmentState colorBlendAttachment{}; + colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | + VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; + colorBlendAttachment.blendEnable = VK_TRUE; + colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_SRC1_COLOR; + colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; + colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; + colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; + colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; + pipeCreateInfo.colorBlendState.attachments = {colorBlendAttachment}; + + 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 = { + CompileShaderModule(VKDefaultVertex, ShaderLang::glsl, ShaderStage::vert, "main"), + CompileShaderModule(pixel, ShaderLang::glsl, ShaderStage::frag, "main"), + }; + + VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo); + + pipeCreateInfo.stages[1] = + CompileShaderModule(pixel_complicated, ShaderLang::glsl, ShaderStage::frag, "main"); + + VkPipeline complicatedPipe = createGraphicsPipeline(pipeCreateInfo); + + AllocatedBuffer vb( + this, vkh::BufferCreateInfo(sizeof(Triangles), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | + VK_BUFFER_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU})); + + vb.upload(Triangles); + + AllocatedImage offimg(this, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R32G32B32A32_SFLOAT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + AllocatedImage offimgMS( + this, vkh::ImageCreateInfo(4, 4, 0, VK_FORMAT_R16G16B16A16_SFLOAT, + VK_IMAGE_USAGE_TRANSFER_DST_BIT, 1, 1, VK_SAMPLE_COUNT_4_BIT), + VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY})); + + while(Running()) + { + VkCommandBuffer cmd = GetCommandBuffer(); + + vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo()); + + VkImage swapimg = + StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + clear(cmd, swapimg, offimg, offimgMS, vkh::ClearColorValue(0.0f, 1.0f, 1.0f, 1.0f)); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe); + vkCmdSetViewport(cmd, 0, 1, &mainWindow->viewport); + vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor); + vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0}); + + setMarker(cmd, "Begin test A1"); + vkCmdDraw(cmd, 6, 1, 0, 0); + setMarker(cmd, "End test A1"); + + vkCmdEndRenderPass(cmd); + + clear(cmd, swapimg, offimg, offimgMS, vkh::ClearColorValue(0.0f, 1.0f, 1.0f, 1.0f)); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + setMarker(cmd, "Begin test A2"); + vkCmdDraw(cmd, 3, 1, 0, 0); + vkCmdDraw(cmd, 3, 1, 3, 0); + setMarker(cmd, "End test A2"); + vkCmdEndRenderPass(cmd); + + clear(cmd, swapimg, offimg, offimgMS, vkh::ClearColorValue(0.0f, 1.0f, 1.0f, 1.0f)); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, complicatedPipe); + setMarker(cmd, "Begin test B1"); + vkCmdDraw(cmd, 6, 1, 0, 0); + setMarker(cmd, "End test B1"); + vkCmdEndRenderPass(cmd); + + clear(cmd, swapimg, offimg, offimgMS, vkh::ClearColorValue(0.0f, 1.0f, 1.0f, 1.0f)); + + vkCmdBeginRenderPass( + cmd, vkh::RenderPassBeginInfo(mainWindow->rp, mainWindow->GetFB(), mainWindow->scissor), + VK_SUBPASS_CONTENTS_INLINE); + setMarker(cmd, "Begin test B2"); + vkCmdDraw(cmd, 3, 1, 0, 0); + vkCmdDraw(cmd, 3, 1, 3, 0); + setMarker(cmd, "End test B2"); + vkCmdEndRenderPass(cmd); + + FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL); + + vkEndCommandBuffer(cmd); + + Submit(0, 1, {cmd}); + + Present(); + } + + return 0; + } +}; + +REGISTER_TEST(); diff --git a/util/test/demos/vk/vk_test.cpp b/util/test/demos/vk/vk_test.cpp index 1ed14b92cc..2267f43cea 100644 --- a/util/test/demos/vk/vk_test.cpp +++ b/util/test/demos/vk/vk_test.cpp @@ -706,7 +706,7 @@ bool VulkanGraphicsTest::Init() if(!headless) { - mainWindow = MakeWindow(screenWidth, screenHeight, "Autotesting"); + mainWindow = MakeWindow(screenWidth, screenHeight, "Autotesting", requestedSwapChainFormat); if(!mainWindow->Initialised()) { @@ -784,7 +784,8 @@ bool VulkanGraphicsTest::Init() return true; } -VulkanWindow *VulkanGraphicsTest::MakeWindow(int width, int height, const char *title) +VulkanWindow *VulkanGraphicsTest::MakeWindow(int width, int height, const char *title, + VkFormat requestedFormat) { #if defined(WIN32) GraphicsWindow *platWin = new Win32Window(width, height, title); @@ -798,7 +799,7 @@ VulkanWindow *VulkanGraphicsTest::MakeWindow(int width, int height, const char * #error UNKNOWN PLATFORM #endif - return new VulkanWindow(this, platWin); + return new VulkanWindow(this, platWin, requestedFormat); } void VulkanGraphicsTest::Shutdown() @@ -1350,11 +1351,12 @@ void VulkanCommands::Submit(const std::vector &cmds, pendingCommandBuffers[1].push_back(std::make_pair(cmd, fence)); } -VulkanWindow::VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win) +VulkanWindow::VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win, VkFormat requestedFormat) : GraphicsWindow(win->title), VulkanCommands(test) { m_Test = test; m_Win = win; + m_requestedFormat = requestedFormat; { std::lock_guard lock(m_Test->mutex); @@ -1464,7 +1466,7 @@ bool VulkanWindow::CreateSwapchain() for(const VkSurfaceFormatKHR &f : formats) { - if(f.format == VK_FORMAT_B8G8R8A8_SRGB && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) + if(f.format == m_requestedFormat && f.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { surfaceFormat = f; break; @@ -1478,6 +1480,10 @@ bool VulkanWindow::CreateSwapchain() } format = surfaceFormat.format; + if(format != m_requestedFormat) + { + TEST_WARN("Wanted surface format %d but got %d", m_requestedFormat, format); + } std::vector modes; CHECK_VKR(vkh::getSurfacePresentModesKHR(modes, m_Test->phys, surface)); diff --git a/util/test/demos/vk/vk_test.h b/util/test/demos/vk/vk_test.h index e5fbc815b1..83271de777 100644 --- a/util/test/demos/vk/vk_test.h +++ b/util/test/demos/vk/vk_test.h @@ -129,13 +129,14 @@ struct VulkanCommands struct VulkanWindow : public GraphicsWindow, public VulkanCommands { + VkFormat m_requestedFormat; VkFormat format; uint32_t imgIndex = 0; VkRenderPass rp = VK_NULL_HANDLE; VkViewport viewport; VkRect2D scissor; - VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win); + VulkanWindow(VulkanGraphicsTest *test, GraphicsWindow *win, VkFormat requestedFormat); virtual ~VulkanWindow(); void Shutdown(); @@ -197,7 +198,8 @@ struct VulkanGraphicsTest : public GraphicsTest void Prepare(int argc, char **argv); bool Init(); void Shutdown(); - VulkanWindow *MakeWindow(int width, int height, const char *title); + VulkanWindow *MakeWindow(int width, int height, const char *title, + VkFormat requestedFormat = VK_FORMAT_B8G8R8A8_SRGB); bool Running(); VkImage StartUsingBackbuffer(VkCommandBuffer cmd, @@ -288,6 +290,8 @@ struct VulkanGraphicsTest : public GraphicsTest uint32_t computeQueueFamilyIndex = ~0U; uint32_t transferQueueFamilyIndex = ~0U; + VkFormat requestedSwapChainFormat = VK_FORMAT_B8G8R8A8_SRGB; + bool hasExt(const char *ext); // a custom struct to pass to vkDeviceCreateInfo::pNext diff --git a/util/test/tests/Vulkan/VK_Blend_Pixel_History.py b/util/test/tests/Vulkan/VK_Blend_Pixel_History.py new file mode 100644 index 0000000000..55b0cf260f --- /dev/null +++ b/util/test/tests/Vulkan/VK_Blend_Pixel_History.py @@ -0,0 +1,208 @@ +import renderdoc as rd +import rdtest +from typing import List + +def value_selector(x): return x.floatValue +def passed(x): return x.Passed() +def event_id(x): return x.eventId +def culled(x): return x.backfaceCulled +def depth_test_failed(x): return x.depthTestFailed +def depth_clipped(x): return x.depthClipped +def depth_bounds_failed(x): return x.depthBoundsFailed +def scissor_clipped(x): return x.scissorClipped +def stencil_test_failed(x): return x.stencilTestFailed +def shader_discarded(x): return x.shaderDiscarded +def shader_out_col(x): return value_selector(x.shaderOut.col) +def shader_out_depth(x): return x.shaderOut.depth +def pre_mod_col(x): return value_selector(x.preMod.col) +def post_mod_col(x): return value_selector(x.postMod.col) +def shader_out_depth(x): return x.shaderOut.depth +def pre_mod_depth(x): return x.preMod.depth +def post_mod_depth(x): return x.postMod.depth +def primitive_id(x): return x.primitiveID +def unboundPS(x): return x.unboundPS + +NUM_TRIANGLES_RED = 16 +NUM_TRIANGLES_RED_REAL = NUM_TRIANGLES_RED * 2 - 1 +NUM_TRIANGLES_GREEN = 255 +NUM_TRIANGLES_BLUE = 512 + +class VK_Blend_Pixel_History(rdtest.TestCase): + demos_test_name = 'VK_Blend' + demos_frame_cap = 5 + + def check_capture(self): + apiprops: rd.APIProperties = self.controller.GetAPIProperties() + + if not apiprops.pixelHistory: + rdtest.log.print("Vulkan pixel history not tested") + return + + self.primary_test() + + def primary_test(self): + test_marker: rd.ActionDescription = self.find_action("Test End") + self.controller.SetFrameEvent(test_marker.eventId, True) + + pipe: rd.PipeState = self.controller.GetPipelineState() + + rt: rd.BoundResource = pipe.GetOutputTargets()[0] + + tex = rt.resourceId + tex_details = self.get_texture(tex) + + sub = rd.Subresource() + if tex_details.arraysize > 1: + sub.slice = rt.firstSlice + if tex_details.mips > 1: + sub.mip = rt.firstMip + + red_eid = self.find_action("Red: groups of repeated draws").next.eventId + red_last_eid = self.find_action("End of red").next.eventId + green_eid = self.find_action("Green: 255 (the maximum we can handle) in a single drawcall").next.eventId + blue_eid = self.find_action("Blue: 512 (more than the maximum) in a single drawcall").next.eventId + all_eid = self.find_action("All of the above in a single drawcall").next.eventId + + # Pixel inside of all of the triangles + x, y = 200, 150 + rdtest.log.print("Testing pixel {}, {}".format(x, y)) + modifs: List[rd.PixelModification] = self.controller.PixelHistory(tex, x, y, sub, rt.typeCast) + self.check_modifs_consistent(modifs) + red_modifs = [m for m in modifs if m.eventId >= red_eid and m.eventId < red_last_eid] + green_modifs = [m for m in modifs if m.eventId == green_eid] + blue_modifs = [m for m in modifs if m.eventId == blue_eid] + all_modifs = [m for m in modifs if m.eventId == all_eid] + + if len(red_modifs) != NUM_TRIANGLES_RED_REAL: + raise rdtest.TestFailureException("Expected {} modifications for red triangles (EIDS {} until {}) but got {}".format(NUM_TRIANGLES_RED_REAL, red_eid, red_last_eid, len(red_modifs))) + + for i, modif in enumerate(red_modifs): + if not rdtest.value_compare(modif.shaderOut.col.floatValue, (1.0/255.0, 0.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong shader output for red triangle {}; got {}, wanted {}".format(i, modif.shaderOut.col.floatValue, (1.0/255.0, 0.0, 0.0, 1.0))) + if not rdtest.value_compare(modif.postMod.col.floatValue, ((i+1)/255.0, 0.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for red triangle {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, ((i+1)/255.0, 0.0, 0.0, 1.0))) + + i = 1 + eid_counter = 0 + modif_counter = 0 + while i <= NUM_TRIANGLES_RED: + for primitive_id in range(i): + if red_modifs[modif_counter].eventId != red_eid + eid_counter: + raise rdtest.TestFailureException("Expected red triangle {} to be part of EID {} but was {}".format(modif_counter, red_eid + eid_counter, red_modifs[modif_counter].eventId)) + if red_modifs[modif_counter].primitiveID != primitive_id: + raise rdtest.TestFailureException("Expected red triangle {} to have primitive ID {} but was {}".format(modif_counter, primitive_id, red_modifs[modif_counter].primitiveID)) + modif_counter += 1 + eid_counter += 1 + i *= 2 + + if len(green_modifs) != NUM_TRIANGLES_GREEN: + raise rdtest.TestFailureException("Expected {} modifications for green triangles (EID {}) but got {}".format(NUM_TRIANGLES_GREEN, green_eid, len(gren_modifs))) + + for i, modif in enumerate(green_modifs): + if modif.primitiveID != i: + raise rdtest.TestFailureException("Expected green triangle {} to have primitive ID {} but was {}".format(i, primitive_id, modif.primitiveID)) + if not rdtest.value_compare(modif.shaderOut.col.floatValue, (0.0, 1.0/255.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong shader output for green triangle {}; got {}, wanted {}".format(i, modif.shaderOut.col.floatValue, (0.0, 1.0/255.0, 0.0, 1.0))) + if not rdtest.value_compare(modif.postMod.col.floatValue, (NUM_TRIANGLES_RED_REAL/255.0, (i+1)/255.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for green triangle {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, (NUM_TRIANGLES_RED_REAL/255.0, (i+1)/255.0, 0.0, 1.0))) + + # We can only record 255 modifications due to the stencil format + if len(blue_modifs) != 255: + raise rdtest.TestFailureException("Expected {} modifications for blue triangles (EID {}) but got {}".format(255, blue_eid, len(blue_modifs))) + + for i, modif in enumerate(blue_modifs): + if modif.primitiveID != i: + raise rdtest.TestFailureException("Expected blue triangle {} to have primitive ID {} but was {}".format(i, primitive_id, modif.primitiveID)) + if not rdtest.value_compare(modif.shaderOut.col.floatValue, (0.0, 0.0, 1.0/255.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong shader output for blue triangle {}; got {}, wanted {}".format(i, modif.shaderOut.col.floatValue, (0.0, 0.0, 1.0/255.0, 1.0))) + if not rdtest.value_compare(modif.postMod.col.floatValue, (NUM_TRIANGLES_RED_REAL/255.0, 1.0, (i+1)/255.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for blue triangle {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, (NUM_TRIANGLES_RED_REAL/255.0, 1.0, (i+1)/255.0, 1.0))) + + # Once again, we can only record 255 modifications due to the stencil format + if len(all_modifs) != 255: + raise rdtest.TestFailureException("Expected {} modifications for all triangles (EID {}) but got {}".format(255, all_eid, len(all_modifs))) + + for i, modif in enumerate(all_modifs): + if modif.primitiveID != i: + raise rdtest.TestFailureException("Expected triangle {} in all to have primitive ID {} but was {}".format(i, primitive_id, modif.primitiveID)) + + if i < NUM_TRIANGLES_RED: + if not rdtest.value_compare(modif.shaderOut.col.floatValue, (1.0/255.0, 0.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong shader output for red triangle in all {}; got {}, wanted {}".format(i, modif.shaderOut.col.floatValue, (1.0/255.0, 0.0, 0.0, 1.0))) + if not rdtest.value_compare(modif.postMod.col.floatValue, ((i+1)/255.0, 0.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for red triangle in all {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, ((i+1)/255.0, 0.0, 0.0, 1.0))) + else: + if not rdtest.value_compare(modif.shaderOut.col.floatValue, (0.0, 1.0/255.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong shader output for green triangle in all {}; got {}, wanted {}".format(i, modif.shaderOut.col.floatValue, (0.0, 1.0/255.0, 0.0, 1.0))) + if i != 254: + if not rdtest.value_compare(modif.postMod.col.floatValue, (NUM_TRIANGLES_RED/255.0, (i+1-NUM_TRIANGLES_RED)/255.0, 0.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for green triangle in all {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, (NUM_TRIANGLES_RED/255.0, (i+1-NUM_TRIANGLES_RED)/255.0, 0.0, 1.0))) + else: + # For i = 254 (the last triangle), the post-mod value is always set to the final post-mod value, but everything else is correctly set to the 255th modification + if not rdtest.value_compare(modif.postMod.col.floatValue, (NUM_TRIANGLES_RED/255.0, 1.0, 1.0, 1.0), eps=1.0/256.0): + raise rdtest.TestFailureException("Wrong post mod for final (blue) triangle in all {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, (NUM_TRIANGLES_RED/255.0, 1.0, 1.0, 1.0))) + + def check_modifs_consistent(self, modifs): + # postmod of each should match premod of the next + for i in range(len(modifs) - 1): + a = value_selector(modifs[i].postMod.col) + b = value_selector(modifs[i + 1].preMod.col) + + if a != b: + raise rdtest.TestFailureException( + "postmod at {} primitive {}: {} doesn't match premod at {} primitive {}: {}".format(modifs[i].eventId, + modifs[i].primitiveID, + a, + modifs[i + 1].eventId, + modifs[i + 1].primitiveID, + b)) + + + # With this test's blend configuration, shaderOut and blendSrc should match + for i in range(len(modifs)): + if not modifs[i].blendSrc.IsValid(): # Applies to clears + continue + + a = value_selector(modifs[i].shaderOut.col) + b = value_selector(modifs[i].blendSrc.col) + + if not rdtest.value_compare(a, b, eps=1.0/256.0): + raise rdtest.TestFailureException( + "shaderOut at {} primitive {}: {} doesn't match blendSrc: {}".format(modifs[i].eventId, + modifs[i].primitiveID, + a, b)) + + # With this test's blend configuration, preMod and blendDst should match + for i in range(len(modifs)): + if not modifs[i].blendDst.IsValid(): # Applies to clears + continue + + a = value_selector(modifs[i].preMod.col)[:3] + b = value_selector(modifs[i].blendDst.col)[:3] + + if not rdtest.value_compare(a, b, eps=1.0/256.0): + raise rdtest.TestFailureException( + "preMod at {} primitive {}: {} doesn't match blendDst: {}".format(modifs[i].eventId, + modifs[i].primitiveID, + a, b)) + + # With this test's blend configuration, and good driver behavior, blendSrc + blendDst should be about equal to postMod + for i in range(len(modifs)): + if not modifs[i].blendDst.IsValid(): # Applies to clears + continue + + if i == len(modifs) - 1: + # Final modifs entry, doesn't behave nicely because postMod is wrong + # This doesn't affect the blue trianges since everything after 255 has blue of 1.0 + continue + + a = value_selector(modifs[i].blendSrc.col) + b = value_selector(modifs[i].blendDst.col) + sum_a_b = [sum(x) for x in zip(a, b)] + c = value_selector(modifs[i].postMod.col) + + if not rdtest.value_compare(sum_a_b, c, eps=1.0/256.0): + raise rdtest.TestFailureException( + "blendSrc at {} primitive {}: {} plus blendDst: {} (sum: {}) should equal postMod: {}".format(modifs[i].eventId, + modifs[i].primitiveID, + a, b, sum_a_b, c)) diff --git a/util/test/tests/Vulkan/VK_Dual_Source.py b/util/test/tests/Vulkan/VK_Dual_Source.py new file mode 100644 index 0000000000..2b3fce03bb --- /dev/null +++ b/util/test/tests/Vulkan/VK_Dual_Source.py @@ -0,0 +1,128 @@ +import renderdoc as rd +import rdtest +from typing import List + +def value_selector(x): return x.floatValue +def passed(x): return x.Passed() +def event_id(x): return x.eventId +def culled(x): return x.backfaceCulled +def depth_test_failed(x): return x.depthTestFailed +def depth_clipped(x): return x.depthClipped +def depth_bounds_failed(x): return x.depthBoundsFailed +def scissor_clipped(x): return x.scissorClipped +def stencil_test_failed(x): return x.stencilTestFailed +def shader_discarded(x): return x.shaderDiscarded +def shader_out_col(x): return value_selector(x.shaderOut.col) +def shader_out_depth(x): return x.shaderOut.depth +def pre_mod_col(x): return value_selector(x.preMod.col) +def post_mod_col(x): return value_selector(x.postMod.col) +def shader_out_depth(x): return x.shaderOut.depth +def pre_mod_depth(x): return x.preMod.depth +def post_mod_depth(x): return x.postMod.depth +def primitive_id(x): return x.primitiveID +def unboundPS(x): return x.unboundPS + +NUM_TRIANGLES_RED = 16 +NUM_TRIANGLES_RED_REAL = NUM_TRIANGLES_RED * 2 - 1 +NUM_TRIANGLES_GREEN = 255 +NUM_TRIANGLES_BLUE = 512 + +class VK_Dual_Source(rdtest.TestCase): + demos_test_name = 'VK_Dual_Source' + demos_frame_cap = 5 + + def check_capture(self): + apiprops: rd.APIProperties = self.controller.GetAPIProperties() + + if not apiprops.pixelHistory: + rdtest.log.print("Vulkan pixel history not tested") + return + + self.primary_test() + + def primary_test(self): + test_marker: rd.ActionDescription = self.find_action("End test B2") + self.controller.SetFrameEvent(test_marker.eventId, True) + + pipe: rd.PipeState = self.controller.GetPipelineState() + + rt: rd.BoundResource = pipe.GetOutputTargets()[0] + + tex = rt.resourceId + tex_details = self.get_texture(tex) + + sub = rd.Subresource() + if tex_details.arraysize > 1: + sub.slice = rt.firstSlice + if tex_details.mips > 1: + sub.mip = rt.firstMip + + # Pixel inside first triangle + modifs_1: List[rd.PixelModification] = self.controller.PixelHistory(tex, 175, 125, sub, rt.typeCast) + # Pixel inside second triangle + modifs_2: List[rd.PixelModification] = self.controller.PixelHistory(tex, 275, 175, sub, rt.typeCast) + # Pixel inside both triangles + modifs_both: List[rd.PixelModification] = self.controller.PixelHistory(tex, 225, 150, sub, rt.typeCast) + + self.check_range(modifs_1, modifs_2, modifs_both, "A1") + self.check_range(modifs_1, modifs_2, modifs_both, "A2") + self.check_range(modifs_1, modifs_2, modifs_both, "B1") + self.check_range(modifs_1, modifs_2, modifs_both, "B2") + + def check_range(self, orig_modifs_1, orig_modifs_2, orig_modifs_both, group): + start_eid = self.find_action("Begin test " + group).eventId + end_eid = self.find_action("End test " + group).eventId + modifs_1 = [m for m in orig_modifs_1 if m.eventId > start_eid and m.eventId < end_eid] + modifs_2 = [m for m in orig_modifs_2 if m.eventId > start_eid and m.eventId < end_eid] + modifs_both = [m for m in orig_modifs_both if m.eventId > start_eid and m.eventId < end_eid] + if not len(modifs_1) == 1: + raise rdtest.TestFailureException( + "group {}: expected 1 modification but found {}".format(group, len(modifs_1))) + if not len(modifs_2) == 1: + raise rdtest.TestFailureException( + "group {}: expected 1 modification but found {}".format(group, len(modifs_2))) + if not len(modifs_both) == 2: + raise rdtest.TestFailureException( + "group {}: expected 2 modifications but found {}".format(group, len(modifs_both))) + modif_1 = modifs_1[0] + modif_2 = modifs_2[0] + modif_both_1 = modifs_both[0] + modif_both_2 = modifs_both[1] + # modif_both_1 should match modif_1 as both are the same triangle on the same background + assert(value_selector(modif_1.preMod.col) == value_selector(modif_both_1.preMod.col)) + assert(value_selector(modif_1.shaderOut.col) == value_selector(modif_both_1.shaderOut.col)) + assert(value_selector(modif_1.shaderOutDualSrc.col) == value_selector(modif_both_1.shaderOutDualSrc.col)) + assert(value_selector(modif_1.blendSrc.col) == value_selector(modif_both_1.blendSrc.col)) + assert(value_selector(modif_1.blendDst.col) == value_selector(modif_both_1.blendDst.col)) + assert(value_selector(modif_1.postMod.col) == value_selector(modif_both_1.postMod.col)) + + # Triangle 1 outputs (0.5, 0.0, 0.0, 1.0) to source 0, (0.0, 0.5, 0.0, 1.0) to source 1 + # Background is (0.0, 1.0, 1.0, 1.0) + # Blend is source0*1 + dest*source1, so the final output should be (0.5, 0.5, 0.0, 1.0) + # eps=.01 because different GPUs handle blending differently + epsilon = .01 + assert(rdtest.value_compare(value_selector(modif_1.preMod.col), (0.0, 1.0, 1.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_1.shaderOut.col), (0.5, 0.0, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_1.shaderOutDualSrc.col), (0.0, 0.5, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_1.blendSrc.col), (0.5, 0.0, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_1.blendDst.col), (0.0, 0.5, 0.0, 0.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_1.postMod.col), (0.5, 0.5, 0.0, 1.0), eps=epsilon)) + + # Triangle 2 outputs (0.5, 0.0, 0.5, 1.0) to source 0, (0.5, 0.5, 0.0, 1.0) to source 1 + # On the (0.0, 1.0, 1.0, 1.0) background the final output is (0.5, 0.0, 0.5, 1.0) added + # to (0.0, 0.5, 0.0, 0.0) = (0.5, 0.5, 0.5, 0.0) + assert(rdtest.value_compare(value_selector(modif_2.preMod.col), (0.0, 1.0, 1.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_2.shaderOut.col), (0.5, 0.0, 0.5, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_2.shaderOutDualSrc.col), (0.5, 0.5, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_2.blendSrc.col), (0.5, 0.0, 0.5, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_2.blendDst.col), (0.0, 0.5, 0.0, 0.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_2.postMod.col), (0.5, 0.5, 0.5, 1.0), eps=epsilon)) + + # Triangle 2 on top of triangle 1's output of (0.5, 0.5, 0.0, 1.0) is (0.5, 0.0, 0.5, 1.0) + # added to (0.25, 0.25, 0.0, 0.0) = (0.75, 0.25, 0.5, 1.0) + assert(rdtest.value_compare(value_selector(modif_both_2.preMod.col), (0.5, 0.5, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_both_2.shaderOut.col), (0.5, 0.0, 0.5, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_both_2.shaderOutDualSrc.col), (0.5, 0.5, 0.0, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_both_2.blendSrc.col), (0.5, 0.0, 0.5, 1.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_both_2.blendDst.col), (0.25, 0.25, 0.0, 0.0), eps=epsilon)) + assert(rdtest.value_compare(value_selector(modif_both_2.postMod.col), (0.75, 0.25, 0.5, 1.0), eps=epsilon))