diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt
index 108bf6d0838..901c5961a5a 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
diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj
index 19b54ca8fd2..7cb494289dc 100644
--- a/util/test/demos/demos.vcxproj
+++ b/util/test/demos/demos.vcxproj
@@ -296,6 +296,7 @@
+
diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters
index d3cc6524c04..84f4636bd50 100644
--- a/util/test/demos/demos.vcxproj.filters
+++ b/util/test/demos/demos.vcxproj.filters
@@ -673,6 +673,9 @@
D3D12\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 00000000000..814052a8f6f
--- /dev/null
+++ b/util/test/demos/vk/vk_blend.cpp
@@ -0,0 +1,219 @@
+/******************************************************************************
+ * 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
+ if(!Init())
+ return 3;
+
+ VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo());
+
+ 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 img(
+ this,
+ vkh::ImageCreateInfo(mainWindow->scissor.extent.width, mainWindow->scissor.extent.height, 0,
+ VK_FORMAT_R32G32B32A32_SFLOAT,
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT |
+ VK_IMAGE_USAGE_TRANSFER_DST_BIT),
+ VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY}));
+
+ VkImageView imgview = createImageView(
+ vkh::ImageViewCreateInfo(img.image, VK_IMAGE_VIEW_TYPE_2D, VK_FORMAT_R32G32B32A32_SFLOAT));
+
+ vkh::RenderPassCreator renderPassCreateInfo;
+
+ renderPassCreateInfo.attachments.push_back(vkh::AttachmentDescription(
+ VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_LAYOUT_GENERAL, VK_IMAGE_LAYOUT_GENERAL));
+
+ renderPassCreateInfo.addSubpass({VkAttachmentReference({0, VK_IMAGE_LAYOUT_GENERAL})});
+
+ VkRenderPass renderPass = createRenderPass(renderPassCreateInfo);
+
+ VkFramebuffer framebuffer = createFramebuffer(
+ vkh::FramebufferCreateInfo(renderPass, {imgview}, mainWindow->scissor.extent));
+
+ vkh::GraphicsPipelineCreateInfo pipeCreateInfo;
+
+ pipeCreateInfo.layout = layout;
+ pipeCreateInfo.renderPass = renderPass;
+
+ 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);
+
+ while(Running())
+ {
+ VkCommandBuffer cmd = GetCommandBuffer();
+
+ vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
+
+ StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);
+
+ vkh::cmdPipelineBarrier(cmd, {
+ vkh::ImageMemoryBarrier(0, VK_ACCESS_TRANSFER_WRITE_BIT,
+ VK_IMAGE_LAYOUT_UNDEFINED,
+ VK_IMAGE_LAYOUT_GENERAL, img.image),
+ });
+
+ pushMarker(cmd, "Clear");
+ vkCmdClearColorImage(cmd, img.image, VK_IMAGE_LAYOUT_GENERAL,
+ vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1,
+ vkh::ImageSubresourceRange());
+
+ popMarker(cmd);
+
+ vkCmdBeginRenderPass(cmd,
+ vkh::RenderPassBeginInfo(renderPass, framebuffer, 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);
+ 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, img.image, VK_IMAGE_LAYOUT_GENERAL,
+ vkh::ClearColorValue(0.0f, 0.0f, 0.0f, 1.0f), 1,
+ vkh::ImageSubresourceRange());
+ popMarker(cmd);
+
+ vkCmdBeginRenderPass(cmd,
+ vkh::RenderPassBeginInfo(renderPass, framebuffer, mainWindow->scissor),
+ VK_SUBPASS_CONTENTS_INLINE);
+
+ pushMarker(cmd, "All of the above in a single drawcall");
+ 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);
+
+ SubmitAndPresent({cmd});
+ }
+
+ return 0;
+ }
+};
+
+REGISTER_TEST();
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 00000000000..253556ff6b0
--- /dev/null
+++ b/util/test/tests/Vulkan/VK_Blend_Pixel_History.py
@@ -0,0 +1,161 @@
+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'
+
+ 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: ").next.eventId
+ red_last_eid = self.find_action("End of red").next.eventId
+ green_eid = self.find_action("Green: ").next.eventId
+ blue_eid = self.find_action("Blue: ").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 i == 254:
+ if not rdtest.value_compare(modif.postMod.col.floatValue, (NUM_TRIANGLES_RED_REAL/255.0, 1.0, NUM_TRIANGLES_BLUE/255.0, 1.0), eps=1.0/256.0):
+ raise rdtest.TestFailureException("Wrong post mod for final blue triangle {}; got {}, wanted {}".format(i, modif.postMod.col.floatValue, (NUM_TRIANGLES_RED_REAL/255.0, 1.0, NUM_TRIANGLES_BLUE/255.0, 1.0)))
+ else:
+ 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, NUM_TRIANGLES_BLUE/255.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, NUM_TRIANGLES_BLUE/255.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))