diff --git a/util/test/demos/CMakeLists.txt b/util/test/demos/CMakeLists.txt
index d481a1c89..89b47466b 100644
--- a/util/test/demos/CMakeLists.txt
+++ b/util/test/demos/CMakeLists.txt
@@ -23,6 +23,7 @@ set(VULKAN_SRC
vk/vk_resource_lifetimes.cpp
vk/vk_sample_locations.cpp
vk/vk_secondary_cmdbuf.cpp
+ vk/vk_shader_editing.cpp
vk/vk_simple_triangle.cpp
vk/vk_spec_constants.cpp
vk/vk_spirv_13_shaders.cpp
@@ -51,6 +52,7 @@ set(OPENGL_SRC
gl/gl_resource_lifetimes.cpp
gl/gl_runtime_bind_prog_to_pipe.cpp
gl/gl_separable_geometry_shader.cpp
+ gl/gl_shader_editing.cpp
gl/gl_simple_triangle.cpp
gl/gl_spirv_shader.cpp
gl/gl_unsized_ms_fbo_attachment.cpp
diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj
index c87f61a0e..f23674f83 100644
--- a/util/test/demos/demos.vcxproj
+++ b/util/test/demos/demos.vcxproj
@@ -188,6 +188,7 @@
+
@@ -214,6 +215,7 @@
+
diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters
index 9f85efa25..79c65916f 100644
--- a/util/test/demos/demos.vcxproj.filters
+++ b/util/test/demos/demos.vcxproj.filters
@@ -330,6 +330,12 @@
Vulkan\demos
+
+ OpenGL\demos
+
+
+ Vulkan\demos
+
diff --git a/util/test/demos/gl/gl_shader_editing.cpp b/util/test/demos/gl/gl_shader_editing.cpp
new file mode 100644
index 000000000..99a6fcf77
--- /dev/null
+++ b/util/test/demos/gl/gl_shader_editing.cpp
@@ -0,0 +1,238 @@
+/******************************************************************************
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2015-2019 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 "gl_test.h"
+
+TEST(GL_Shader_Editing, OpenGLGraphicsTest)
+{
+ static constexpr const char *Description =
+ "Ensures that shader editing works with different combinations of shader re-use and handles "
+ "locations that change between the pre-edit and post-edit shaders.";
+
+ const char *vertex = R"EOSHADER(
+#version 430 core
+
+layout(location = 0) in vec3 Position;
+layout(location = 1) in vec4 Color;
+layout(location = 2) in vec2 UV;
+
+#define v2f v2f_block \
+{ \
+ vec4 pos; \
+ vec4 col; \
+ vec4 uv; \
+}
+
+out v2f vertOut;
+
+out gl_PerVertex { vec4 gl_Position; };
+
+void main()
+{
+ vertOut.pos = vec4(Position.xyz, 1);
+ gl_Position = vertOut.pos;
+ vertOut.col = Color;
+ vertOut.uv = vec4(UV.xy, 0, 1);
+}
+
+)EOSHADER";
+
+ const char *pixel = R"EOSHADER(
+#version 430 core
+
+layout(location = 0, index = 0) out vec4 Color;
+
+layout(location = 9) uniform vec4 col;
+
+void main()
+{
+ Color = col.rgba;
+}
+
+)EOSHADER";
+
+ const char *pixel2 = R"EOSHADER(
+#version 430 core
+
+layout(location = 0, index = 0) out vec4 Color;
+
+// we hope that having these uniforms be first both alphabetically, by use, and by declaration, that
+// they'll be assigned earlier locations.
+// Then when we remove the declration and use it should force zcol to get a lower location value
+// after the edit.
+#if 1
+uniform vec4 acol;
+uniform vec4 bcol;
+uniform vec4 ccol;
+#endif
+uniform vec4 zcol;
+
+void main()
+{
+#if 1
+ Color = acol + bcol + ccol;
+#endif
+ Color += zcol.rgba;
+}
+
+)EOSHADER";
+
+ int main()
+ {
+ // initialise, create window, create context, etc
+ if(!Init())
+ return 3;
+
+ GLuint vao = MakeVAO();
+ glBindVertexArray(vao);
+
+ GLuint vb = MakeBuffer();
+ glBindBuffer(GL_ARRAY_BUFFER, vb);
+ glBufferStorage(GL_ARRAY_BUFFER, sizeof(DefaultTri), DefaultTri, 0);
+
+ glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(0));
+ glVertexAttribPointer(1, 4, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V), (void *)(sizeof(Vec3f)));
+ glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, sizeof(DefaultA2V),
+ (void *)(sizeof(Vec3f) + sizeof(Vec4f)));
+
+ glEnableVertexAttribArray(0);
+ glEnableVertexAttribArray(1);
+ glEnableVertexAttribArray(2);
+
+ GLuint fixedprog = MakeProgram();
+ GLuint dynamicprog = MakeProgram();
+
+ GLuint vs = glCreateShader(GL_VERTEX_SHADER);
+ glShaderSource(vs, 1, &vertex, NULL);
+ glCompileShader(vs);
+
+ GLuint fs1 = glCreateShader(GL_FRAGMENT_SHADER);
+ glShaderSource(fs1, 1, &pixel, NULL);
+ glCompileShader(fs1);
+
+ GLuint fs2 = glCreateShader(GL_FRAGMENT_SHADER);
+ glShaderSource(fs2, 1, &pixel2, NULL);
+ glCompileShader(fs2);
+
+ glAttachShader(fixedprog, vs);
+ glAttachShader(fixedprog, fs1);
+ glLinkProgram(fixedprog);
+ glDetachShader(fixedprog, vs);
+ glDetachShader(fixedprog, fs1);
+
+ glAttachShader(dynamicprog, vs);
+ glAttachShader(dynamicprog, fs2);
+ glLinkProgram(dynamicprog);
+ glDetachShader(dynamicprog, vs);
+ glDetachShader(dynamicprog, fs2);
+
+ glDeleteShader(vs);
+ glDeleteShader(fs1);
+ glDeleteShader(fs2);
+
+ GLuint pipe = MakePipeline();
+
+ GLuint vssepprog = glCreateShaderProgramv(GL_VERTEX_SHADER, 1, &vertex);
+ GLuint fssepprog = glCreateShaderProgramv(GL_FRAGMENT_SHADER, 1, &pixel);
+
+ glUseProgramStages(pipe, GL_VERTEX_SHADER_BIT, vssepprog);
+ glUseProgramStages(pipe, GL_FRAGMENT_SHADER_BIT, fssepprog);
+
+ glProgramUniform4f(fssepprog, 9, 0.0f, 1.0f, 0.0f, 1.0f);
+
+ // render offscreen to make picked values accurate
+ GLuint fbo = MakeFBO();
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+
+ // Color render texture
+ GLuint colattach = MakeTexture();
+
+ glBindTexture(GL_TEXTURE_2D, colattach);
+ glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA32F, screenWidth, screenHeight);
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, colattach, 0);
+
+ GLuint zcol = glGetUniformLocation(dynamicprog, "zcol");
+
+ while(Running())
+ {
+ float col[] = {0.4f, 0.5f, 0.6f, 1.0f};
+ glClearBufferfv(GL_COLOR, 0, col);
+
+ glBindVertexArray(vao);
+
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
+ glUseProgram(fixedprog);
+
+ GLsizei hw = GLsizei(screenWidth) / 2;
+ GLsizei hh = GLsizei(screenHeight) / 2;
+
+ glViewport(0, hh, hw, hh);
+
+ glUniform4f(9, 0.0f, 1.0f, 0.0f, 1.0f);
+ glUniform4f(10, 1.0f, 0.0f, 0.0f, 1.0f);
+
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+
+ glViewport(hw, hh, hw, hh);
+
+ glUniform4f(9, 0.0f, 0.5f, 0.0f, 1.0f);
+ glUniform4f(10, 0.5f, 0.0f, 0.0f, 1.0f);
+
+ setMarker("fixedprog");
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+
+ glViewport(0, 0, hw, hh);
+
+ glUseProgram(dynamicprog);
+ glUniform4f(zcol, 0.0f, 1.0f, 0.0f, 1.0f);
+ setMarker("dynamicprog");
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+
+ glViewport(hw, 0, hw, hh);
+
+ // finally draw with the separable pipeline to ensure we can edit that
+ glBindProgramPipeline(pipe);
+ glUseProgram(0);
+ setMarker("sepprog");
+ glDrawArrays(GL_TRIANGLES, 0, 3);
+ glBindProgramPipeline(0);
+
+ // give us a point to select where all uniforms are trashed
+ glUseProgram(fixedprog);
+ glUniform4f(9, 0.0f, 0.0f, 0.0f, 1.0f);
+ glUniform4f(10, 0.0f, 0.0f, 0.0f, 1.0f);
+ glUseProgram(dynamicprog);
+ glUniform4f(zcol, 0.0f, 0.0f, 0.0f, 1.0f);
+
+ glBlitNamedFramebuffer(fbo, 0, 0, 0, screenWidth, screenHeight, 0, 0, screenWidth,
+ screenHeight, GL_COLOR_BUFFER_BIT, GL_NEAREST);
+
+ Present();
+ }
+
+ return 0;
+ }
+};
+
+REGISTER_TEST();
diff --git a/util/test/demos/vk/vk_cbuffer_zoo.cpp b/util/test/demos/vk/vk_cbuffer_zoo.cpp
index 1833332b9..bc47a3cf5 100644
--- a/util/test/demos/vk/vk_cbuffer_zoo.cpp
+++ b/util/test/demos/vk/vk_cbuffer_zoo.cpp
@@ -504,7 +504,8 @@ float4 main() : SV_Target0
AllocatedImage img(
allocator,
vkh::ImageCreateInfo(mainWindow->scissor.extent.width, mainWindow->scissor.extent.height, 0,
- VK_FORMAT_R32G32B32A32_SFLOAT, VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT),
+ VK_FORMAT_R32G32B32A32_SFLOAT,
+ VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT),
VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_GPU_ONLY}));
VkImageView imgview = createImageView(
@@ -600,12 +601,8 @@ float4 main() : SV_Target0
VkImage swapimg =
StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);
- vkCmdClearColorImage(cmd, swapimg, VK_IMAGE_LAYOUT_GENERAL,
- vkh::ClearColorValue(0.4f, 0.5f, 0.6f, 1.0f), 1,
- vkh::ImageSubresourceRange());
-
vkCmdBeginRenderPass(cmd, vkh::RenderPassBeginInfo(renderPass, framebuffer, mainWindow->scissor,
- {vkh::ClearValue(0.0f, 0.0f, 0.0f, 1.0f)}),
+ {vkh::ClearValue(0.4f, 0.5f, 0.6f, 1.0f)}),
VK_SUBPASS_CONTENTS_INLINE);
vkh::cmdBindDescriptorSets(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, layout, 0, {descset}, {});
@@ -621,6 +618,28 @@ float4 main() : SV_Target0
vkCmdEndRenderPass(cmd);
+ vkh::cmdPipelineBarrier(
+ cmd, {
+ vkh::ImageMemoryBarrier(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL,
+ VK_IMAGE_LAYOUT_GENERAL, img.image),
+ });
+
+ VkImageBlit region = {};
+ region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.srcSubresource.layerCount = 1;
+ region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.dstSubresource.layerCount = 1;
+ region.srcOffsets[1].x = mainWindow->scissor.extent.width;
+ region.srcOffsets[1].y = mainWindow->scissor.extent.height;
+ region.srcOffsets[1].z = 1;
+ region.dstOffsets[1].x = mainWindow->scissor.extent.width;
+ region.dstOffsets[1].y = mainWindow->scissor.extent.height;
+ region.dstOffsets[1].z = 1;
+
+ vkCmdBlitImage(cmd, img.image, VK_IMAGE_LAYOUT_GENERAL, swapimg, VK_IMAGE_LAYOUT_GENERAL, 1,
+ ®ion, VK_FILTER_LINEAR);
+
FinishUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);
vkEndCommandBuffer(cmd);
diff --git a/util/test/demos/vk/vk_shader_editing.cpp b/util/test/demos/vk/vk_shader_editing.cpp
new file mode 100644
index 000000000..e3f200f0e
--- /dev/null
+++ b/util/test/demos/vk/vk_shader_editing.cpp
@@ -0,0 +1,189 @@
+/******************************************************************************
+ * The MIT License (MIT)
+ *
+ * Copyright (c) 2018-2019 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"
+
+TEST(VK_Shader_Editing, VulkanGraphicsTest)
+{
+ static constexpr const char *Description =
+ "Ensures that shader editing works with different combinations of shader re-use.";
+
+ const char *vertex = R"EOSHADER(
+#version 430 core
+
+layout(location = 0) in vec3 Position;
+
+void main()
+{
+ gl_Position = vec4(Position.xyz, 1);
+}
+
+)EOSHADER";
+
+ const char *pixel = R"EOSHADER(
+#version 430 core
+
+layout(location = 0, index = 0) out vec4 Color;
+
+void main()
+{
+#if 1
+ Color = vec4(0.0, 1.0, 0.0, 1.0);
+#else
+ Color = vec4(0.0, 1.0, 1.0, 1.0);
+#endif
+}
+
+)EOSHADER";
+
+ int main()
+ {
+ // initialise, create window, create context, etc
+ if(!Init())
+ return 3;
+
+ VkPipelineLayout layout = createPipelineLayout(vkh::PipelineLayoutCreateInfo());
+
+ AllocatedImage img(
+ allocator,
+ 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),
+ 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_UNDEFINED,
+ VK_IMAGE_LAYOUT_GENERAL, VK_ATTACHMENT_LOAD_OP_CLEAR));
+
+ 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;
+
+ 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(vertex, ShaderLang::glsl, ShaderStage::vert, "main"),
+ CompileShaderModule(pixel, ShaderLang::glsl, ShaderStage::frag, "main"),
+ };
+
+ VkPipeline pipe = createGraphicsPipeline(pipeCreateInfo);
+
+ // use the same source but make a distinct shader module so we can edit it separately
+ pipeCreateInfo.stages[1] =
+ CompileShaderModule(pixel, ShaderLang::glsl, ShaderStage::frag, "main");
+
+ VkPipeline pipe2 = createGraphicsPipeline(pipeCreateInfo);
+
+ AllocatedBuffer vb(
+ allocator, vkh::BufferCreateInfo(sizeof(DefaultTri), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
+ VK_BUFFER_USAGE_TRANSFER_DST_BIT),
+ VmaAllocationCreateInfo({0, VMA_MEMORY_USAGE_CPU_TO_GPU}));
+
+ vb.upload(DefaultTri);
+
+ while(Running())
+ {
+ VkCommandBuffer cmd = GetCommandBuffer();
+
+ vkBeginCommandBuffer(cmd, vkh::CommandBufferBeginInfo());
+
+ VkImage swapimg =
+ StartUsingBackbuffer(cmd, VK_ACCESS_TRANSFER_WRITE_BIT, VK_IMAGE_LAYOUT_GENERAL);
+
+ vkCmdBeginRenderPass(cmd, vkh::RenderPassBeginInfo(renderPass, framebuffer, mainWindow->scissor,
+ {vkh::ClearValue(0.4f, 0.5f, 0.6f, 1.0f)}),
+ VK_SUBPASS_CONTENTS_INLINE);
+
+ VkViewport v = mainWindow->viewport;
+ v.width /= 2.0f;
+
+ vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe);
+ vkCmdSetViewport(cmd, 0, 1, &v);
+ vkCmdSetScissor(cmd, 0, 1, &mainWindow->scissor);
+ vkh::cmdBindVertexBuffers(cmd, 0, {vb.buffer}, {0});
+ setMarker(cmd, "Draw 1");
+ vkCmdDraw(cmd, 3, 1, 0, 0);
+
+ v.x += v.width;
+
+ vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipe2);
+ vkCmdSetViewport(cmd, 0, 1, &v);
+ setMarker(cmd, "Draw 2");
+ vkCmdDraw(cmd, 3, 1, 0, 0);
+
+ vkCmdEndRenderPass(cmd);
+
+ vkh::cmdPipelineBarrier(
+ cmd, {
+ vkh::ImageMemoryBarrier(VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
+ VK_ACCESS_TRANSFER_READ_BIT, VK_IMAGE_LAYOUT_GENERAL,
+ VK_IMAGE_LAYOUT_GENERAL, img.image),
+ });
+
+ VkImageBlit region = {};
+ region.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.srcSubresource.layerCount = 1;
+ region.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ region.dstSubresource.layerCount = 1;
+ region.srcOffsets[1].x = mainWindow->scissor.extent.width;
+ region.srcOffsets[1].y = mainWindow->scissor.extent.height;
+ region.srcOffsets[1].z = 1;
+ region.dstOffsets[1].x = mainWindow->scissor.extent.width;
+ region.dstOffsets[1].y = mainWindow->scissor.extent.height;
+ region.dstOffsets[1].z = 1;
+
+ vkCmdBlitImage(cmd, img.image, VK_IMAGE_LAYOUT_GENERAL, swapimg, VK_IMAGE_LAYOUT_GENERAL, 1,
+ ®ion, VK_FILTER_LINEAR);
+
+ 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/tests/GL/GL_Shader_Editing.py b/util/test/tests/GL/GL_Shader_Editing.py
new file mode 100644
index 000000000..7461f31be
--- /dev/null
+++ b/util/test/tests/GL/GL_Shader_Editing.py
@@ -0,0 +1,236 @@
+import copy
+import rdtest
+import renderdoc as rd
+from typing import Tuple
+
+
+class GL_Shader_Editing(rdtest.TestCase):
+ demos_test_name = 'GL_Shader_Editing'
+
+ def check_capture(self):
+ eid = self.find_draw("fixedprog").eventId
+ self.controller.SetFrameEvent(eid, False)
+
+ pipe: rd.PipeState = self.controller.GetPipelineState()
+
+ fixedrefl: rd.ShaderReflection = pipe.GetShaderReflection(rd.ShaderStage.Fragment)
+
+ eid = self.find_draw("dynamicprog").eventId
+ self.controller.SetFrameEvent(eid, False)
+
+ pipe: rd.PipeState = self.controller.GetPipelineState()
+
+ dynamicrefl: rd.ShaderReflection = pipe.GetShaderReflection(rd.ShaderStage.Fragment)
+ vsrefl: rd.ShaderReflection = pipe.GetShaderReflection(rd.ShaderStage.Vertex)
+
+ eid = self.find_draw("sepprog").eventId
+ self.controller.SetFrameEvent(eid, False)
+
+ vsseprefl: rd.ShaderReflection = pipe.GetShaderReflection(rd.ShaderStage.Vertex)
+ fsseprefl: rd.ShaderReflection = pipe.GetShaderReflection(rd.ShaderStage.Fragment)
+
+ # Work at the last draw, where the uniforms have been trashed
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, False)
+
+ tex: rd.ResourceId = pipe.GetOutputTargets()[0].resourceId
+
+ # On upper row: Left triangle is fully green, right triangle is half-green
+ # On lower row: Left triangle is fully green
+ self.check_pixel_value(tex, 0.25, 0.25, [0.0, 1.0, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.25, [0.0, 0.5, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.25, 0.75, [0.0, 1.0, 0.0, 1.0])
+
+ rdtest.log.success("Values are as expected initially")
+
+ source: bytes = fixedrefl.rawBytes.replace(b'.rgba', b'.rgga').replace(b'location = 9', b'location = 10')
+
+ newShader: Tuple[rd.ResourceId, str] = self.controller.BuildTargetShader(fixedrefl.entryPoint,
+ fixedrefl.encoding, source,
+ rd.ShaderCompileFlags(),
+ rd.ShaderStage.Fragment)
+
+ if len(newShader[1]) != 0:
+ raise rdtest.TestFailureException("Failed to compile edited shader: {}".format(newShader[1]))
+
+ fixedFS = newShader[0]
+
+ source: bytes = dynamicrefl.rawBytes.replace(b'.rgba', b'.rgga').replace(b'#if 1', b'#if 0')
+
+ newShader: Tuple[rd.ResourceId, str] = self.controller.BuildTargetShader(dynamicrefl.entryPoint,
+ dynamicrefl.encoding, source,
+ rd.ShaderCompileFlags(),
+ rd.ShaderStage.Fragment)
+
+ if len(newShader[1]) != 0:
+ raise rdtest.TestFailureException("Failed to compile edited shader: {}".format(newShader[1]))
+
+ dynamicFS = newShader[0]
+
+ source: bytes = vsrefl.rawBytes.replace(b'Position.xyz', b'Position.xyz+vec3(1.0)')
+
+ newShader: Tuple[rd.ResourceId, str] = self.controller.BuildTargetShader(vsrefl.entryPoint,
+ vsrefl.encoding, source,
+ rd.ShaderCompileFlags(),
+ rd.ShaderStage.Vertex)
+
+ if len(newShader[1]) != 0:
+ raise rdtest.TestFailureException("Failed to compile edited shader: {}".format(newShader[1]))
+
+ offsetVS = newShader[0]
+
+ source: bytes = vsrefl.rawBytes
+
+ newShader: Tuple[rd.ResourceId, str] = self.controller.BuildTargetShader(vsrefl.entryPoint,
+ vsrefl.encoding, source,
+ rd.ShaderCompileFlags(),
+ rd.ShaderStage.Vertex)
+
+ if len(newShader[1]) != 0:
+ raise rdtest.TestFailureException("Failed to compile edited shader: {}".format(newShader[1]))
+
+ nochangeVS = newShader[0]
+
+ source: bytes = vsseprefl.rawBytes.replace(b'Position.xyz', b'Position.xyz+vec3(1.0)')
+
+ newShader: Tuple[rd.ResourceId, str] = self.controller.BuildTargetShader(vsseprefl.entryPoint,
+ vsseprefl.encoding, source,
+ rd.ShaderCompileFlags(),
+ rd.ShaderStage.Vertex)
+
+ if len(newShader[1]) != 0:
+ raise rdtest.TestFailureException("Failed to compile edited shader: {}".format(newShader[1]))
+
+ sepVS = newShader[0]
+
+ source: bytes = fsseprefl.rawBytes.replace(b'.rgba', b'.rgga')
+
+ newShader: Tuple[rd.ResourceId, str] = self.controller.BuildTargetShader(fsseprefl.entryPoint,
+ fsseprefl.encoding, source,
+ rd.ShaderCompileFlags(),
+ rd.ShaderStage.Fragment)
+
+ if len(newShader[1]) != 0:
+ raise rdtest.TestFailureException("Failed to compile edited shader: {}".format(newShader[1]))
+
+ sepFS = newShader[0]
+
+ # Edit both fragment shaders
+ self.controller.ReplaceResource(fixedrefl.resourceId, fixedFS)
+ self.controller.ReplaceResource(dynamicrefl.resourceId, dynamicFS)
+
+ # Refresh the replay if it didn't happen already
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, True)
+
+ # Triangles have green propagated across to the blue channel
+ self.check_pixel_value(tex, 0.25, 0.25, [0.0, 1.0, 1.0, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.25, [0.0, 0.5, 0.5, 1.0])
+ self.check_pixel_value(tex, 0.25, 0.75, [0.0, 1.0, 1.0, 1.0])
+
+ rdtest.log.success("Values are as expected after fragment editing")
+
+ # Now "edit" the VS but don't change it. We should still get the same values
+ self.controller.ReplaceResource(vsrefl.resourceId, nochangeVS)
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, True)
+
+ # Triangles have green propagated across to the blue channel
+ self.check_pixel_value(tex, 0.25, 0.25, [0.0, 1.0, 1.0, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.25, [0.0, 0.5, 0.5, 1.0])
+ self.check_pixel_value(tex, 0.25, 0.75, [0.0, 1.0, 1.0, 1.0])
+
+ rdtest.log.success("Values are as expected after no-op vertex editing")
+
+ # Change the VS to one that has offset the triangles off-centre
+ self.controller.ReplaceResource(vsrefl.resourceId, offsetVS)
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, True)
+
+ # Original sample positions are now the clear color
+ self.check_pixel_value(tex, 0.25, 0.25, [0.4, 0.5, 0.6, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.25, [0.4, 0.5, 0.6, 1.0])
+ self.check_pixel_value(tex, 0.25, 0.75, [0.4, 0.5, 0.6, 1.0])
+
+ # The triangles are still the same colour but up and to the right
+ self.check_pixel_value(tex, 0.45, 0.05, [0.0, 1.0, 1.0, 1.0])
+ self.check_pixel_value(tex, 0.95, 0.05, [0.0, 0.5, 0.5, 1.0])
+ self.check_pixel_value(tex, 0.45, 0.55, [0.0, 1.0, 1.0, 1.0])
+
+ rdtest.log.success("Values are as expected after offset vertex editing")
+
+ # Now undo the first FS edit
+ self.controller.RemoveReplacement(fixedrefl.resourceId)
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, True)
+
+ # Original sample positions are still the clear color
+ self.check_pixel_value(tex, 0.25, 0.25, [0.4, 0.5, 0.6, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.25, [0.4, 0.5, 0.6, 1.0])
+ self.check_pixel_value(tex, 0.25, 0.75, [0.4, 0.5, 0.6, 1.0])
+
+ # The lower triangle is the edited colour, the other two have reverted to green channel only
+ self.check_pixel_value(tex, 0.45, 0.05, [0.0, 1.0, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.95, 0.05, [0.0, 0.5, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.45, 0.55, [0.0, 1.0, 1.0, 1.0])
+
+ rdtest.log.success("Values are as expected after removing first fragment edit")
+
+ # Now undo the first VS edit
+ self.controller.RemoveReplacement(vsrefl.resourceId)
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, True)
+
+ # Only the lower triangle is the edited colour, but they are back in the original positions
+ self.check_pixel_value(tex, 0.25, 0.25, [0.0, 1.0, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.25, [0.0, 0.5, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.25, 0.75, [0.0, 1.0, 1.0, 1.0])
+
+ rdtest.log.success("Values are as expected after removing vertex edit")
+
+ # finally undo the second FS edit
+ self.controller.RemoveReplacement(dynamicrefl.resourceId)
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, True)
+
+ # We should be back to where we started
+ self.check_pixel_value(tex, 0.25, 0.25, [0.0, 1.0, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.25, [0.0, 0.5, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.25, 0.75, [0.0, 1.0, 0.0, 1.0])
+
+ rdtest.log.success("Values are as expected after removing all edits")
+
+ rdtest.log.success("Linked program editing succeeded")
+
+ # Check that we can edit separable shaders
+
+ # Only looking at bottom left triangle, it should be green
+ self.check_pixel_value(tex, 0.75, 0.75, [0.0, 1.0, 0.0, 1.0])
+
+ self.controller.ReplaceResource(fsseprefl.resourceId, sepFS)
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, True)
+
+ # Now it should be green-blue
+ self.check_pixel_value(tex, 0.75, 0.75, [0.0, 1.0, 1.0, 1.0])
+
+ self.controller.ReplaceResource(vsseprefl.resourceId, sepVS)
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, True)
+
+ # Now it should be green-blue and offset
+ self.check_pixel_value(tex, 0.75, 0.75, [0.4, 0.5, 0.6, 1.0])
+ self.check_pixel_value(tex, 0.95, 0.55, [0.0, 1.0, 1.0, 1.0])
+
+ self.controller.RemoveReplacement(fsseprefl.resourceId)
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, True)
+
+ # Now it should be back to green and offset
+ self.check_pixel_value(tex, 0.75, 0.75, [0.4, 0.5, 0.6, 1.0])
+ self.check_pixel_value(tex, 0.95, 0.55, [0.0, 1.0, 0.0, 1.0])
+
+ self.controller.RemoveReplacement(vsseprefl.resourceId)
+ self.controller.SetFrameEvent(self.get_last_draw().eventId, True)
+
+ # We should be back to where we started
+ self.check_pixel_value(tex, 0.75, 0.75, [0.0, 1.0, 0.0, 1.0])
+
+ rdtest.log.success("Separable program editing succeeded")
+
+ self.controller.FreeTargetResource(nochangeVS)
+ self.controller.FreeTargetResource(offsetVS)
+ self.controller.FreeTargetResource(fixedFS)
+ self.controller.FreeTargetResource(dynamicFS)
+ self.controller.FreeTargetResource(sepVS)
+ self.controller.FreeTargetResource(sepFS)
diff --git a/util/test/tests/Vulkan/VK_Shader_Editing.py b/util/test/tests/Vulkan/VK_Shader_Editing.py
new file mode 100644
index 000000000..be97179ab
--- /dev/null
+++ b/util/test/tests/Vulkan/VK_Shader_Editing.py
@@ -0,0 +1,159 @@
+import copy
+import rdtest
+import renderdoc as rd
+from typing import Tuple
+
+
+class VK_Shader_Editing(rdtest.TestCase):
+ demos_test_name = 'VK_Shader_Editing'
+
+ def check_capture(self):
+ eid = self.find_draw("Draw 1").next.eventId
+ self.controller.SetFrameEvent(eid, False)
+
+ pipe: rd.PipeState = self.controller.GetPipelineState()
+
+ fsrefl1: rd.ShaderReflection = pipe.GetShaderReflection(rd.ShaderStage.Fragment)
+
+ eid = self.find_draw("Draw 2").next.eventId
+ self.controller.SetFrameEvent(eid, False)
+
+ pipe: rd.PipeState = self.controller.GetPipelineState()
+
+ fsrefl2: rd.ShaderReflection = pipe.GetShaderReflection(rd.ShaderStage.Fragment)
+ vsrefl: rd.ShaderReflection = pipe.GetShaderReflection(rd.ShaderStage.Vertex)
+
+ tex: rd.ResourceId = pipe.GetOutputTargets()[0].resourceId
+
+ # Both triangles should be green
+ self.check_pixel_value(tex, 0.25, 0.5, [0.0, 1.0, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.5, [0.0, 1.0, 0.0, 1.0])
+
+ rdtest.log.success("Values are as expected initially")
+
+ source: str = fsrefl1.debugInfo.files[0].contents.replace('#if 1', '#if 0')
+
+ newShader: Tuple[rd.ResourceId, str] = self.controller.BuildTargetShader(fsrefl1.entryPoint,
+ rd.ShaderEncoding.GLSL,
+ bytes(source, 'UTF-8'),
+ rd.ShaderCompileFlags(),
+ rd.ShaderStage.Fragment)
+
+ if len(newShader[1]) != 0:
+ raise rdtest.TestFailureException("Failed to compile edited shader: {}".format(newShader[1]))
+
+ FS1 = newShader[0]
+
+ source: str = fsrefl2.debugInfo.files[0].contents.replace('#if 1', '#if 0')
+
+ newShader: Tuple[rd.ResourceId, str] = self.controller.BuildTargetShader(fsrefl2.entryPoint,
+ rd.ShaderEncoding.GLSL,
+ bytes(source, 'UTF-8'),
+ rd.ShaderCompileFlags(),
+ rd.ShaderStage.Fragment)
+
+ if len(newShader[1]) != 0:
+ raise rdtest.TestFailureException("Failed to compile edited shader: {}".format(newShader[1]))
+
+ FS2 = newShader[0]
+
+ source: str = vsrefl.debugInfo.files[0].contents.replace('Position.xyz', 'Position.xyz+vec3(1.0)')
+
+ newShader: Tuple[rd.ResourceId, str] = self.controller.BuildTargetShader(vsrefl.entryPoint,
+ rd.ShaderEncoding.GLSL,
+ bytes(source, 'UTF-8'),
+ rd.ShaderCompileFlags(),
+ rd.ShaderStage.Vertex)
+
+ if len(newShader[1]) != 0:
+ raise rdtest.TestFailureException("Failed to compile edited shader: {}".format(newShader[1]))
+
+ offsetVS = newShader[0]
+
+ source: bytes = vsrefl.rawBytes
+
+ newShader: Tuple[rd.ResourceId, str] = self.controller.BuildTargetShader(vsrefl.entryPoint,
+ vsrefl.encoding, source,
+ rd.ShaderCompileFlags(),
+ rd.ShaderStage.Vertex)
+
+ if len(newShader[1]) != 0:
+ raise rdtest.TestFailureException("Failed to compile edited shader: {}".format(newShader[1]))
+
+ nochangeVS = newShader[0]
+
+ # Edit both fragment shaders
+ self.controller.ReplaceResource(fsrefl1.resourceId, FS1)
+ self.controller.ReplaceResource(fsrefl2.resourceId, FS2)
+
+ # Refresh the replay if it didn't happen already
+ self.controller.SetFrameEvent(eid, True)
+
+ # Triangles have green and blue channel
+ self.check_pixel_value(tex, 0.25, 0.5, [0.0, 1.0, 1.0, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.5, [0.0, 1.0, 1.0, 1.0])
+
+ rdtest.log.success("Values are as expected after fragment editing")
+
+ # Now "edit" the VS but don't change it. We should still get the same values
+ self.controller.ReplaceResource(vsrefl.resourceId, nochangeVS)
+ self.controller.SetFrameEvent(eid, True)
+
+ # Triangles have green and blue channel
+ self.check_pixel_value(tex, 0.25, 0.5, [0.0, 1.0, 1.0, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.5, [0.0, 1.0, 1.0, 1.0])
+
+ rdtest.log.success("Values are as expected after no-op vertex editing")
+
+ # Change the VS to one that has offset the triangles off-centre
+ self.controller.ReplaceResource(vsrefl.resourceId, offsetVS)
+ self.controller.SetFrameEvent(eid, True)
+
+ # Original sample positions are now the clear color
+ self.check_pixel_value(tex, 0.25, 0.5, [0.4, 0.5, 0.6, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.5, [0.4, 0.5, 0.6, 1.0])
+
+ # Triangles have green and blue channel
+ self.check_pixel_value(tex, 0.45, 0.95, [0.0, 1.0, 1.0, 1.0])
+ self.check_pixel_value(tex, 0.95, 0.95, [0.0, 1.0, 1.0, 1.0])
+
+ rdtest.log.success("Values are as expected after offset vertex editing")
+
+ # Now undo the first FS edit
+ self.controller.RemoveReplacement(fsrefl1.resourceId)
+ self.controller.SetFrameEvent(eid, True)
+
+ # Original sample positions are still the clear color
+ self.check_pixel_value(tex, 0.25, 0.5, [0.4, 0.5, 0.6, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.5, [0.4, 0.5, 0.6, 1.0])
+
+ # The right triangle is the edited colour, the other two have reverted to green channel only
+ self.check_pixel_value(tex, 0.45, 0.95, [0.0, 1.0, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.95, 0.95, [0.0, 1.0, 1.0, 1.0])
+
+ rdtest.log.success("Values are as expected after removing first fragment edit")
+
+ # Now undo the first VS edit
+ self.controller.RemoveReplacement(vsrefl.resourceId)
+ self.controller.SetFrameEvent(eid, True)
+
+ # The right triangle is the edited colour, but they are back in the original positions
+ self.check_pixel_value(tex, 0.25, 0.5, [0.0, 1.0, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.5, [0.0, 1.0, 1.0, 1.0])
+
+ rdtest.log.success("Values are as expected after removing vertex edit")
+
+ # finally undo the second FS edit
+ self.controller.RemoveReplacement(fsrefl2.resourceId)
+ self.controller.SetFrameEvent(eid, True)
+
+ # We should be back to where we started
+ self.check_pixel_value(tex, 0.25, 0.5, [0.0, 1.0, 0.0, 1.0])
+ self.check_pixel_value(tex, 0.75, 0.5, [0.0, 1.0, 0.0, 1.0])
+
+ rdtest.log.success("Values are as expected after removing all edits")
+
+ self.controller.FreeTargetResource(nochangeVS)
+ self.controller.FreeTargetResource(offsetVS)
+ self.controller.FreeTargetResource(FS1)
+ self.controller.FreeTargetResource(FS2)