diff --git a/util/test/demos/demos.vcxproj b/util/test/demos/demos.vcxproj index ae75e5aad..b39934009 100644 --- a/util/test/demos/demos.vcxproj +++ b/util/test/demos/demos.vcxproj @@ -283,6 +283,7 @@ + diff --git a/util/test/demos/demos.vcxproj.filters b/util/test/demos/demos.vcxproj.filters index 433808646..8d1a84458 100644 --- a/util/test/demos/demos.vcxproj.filters +++ b/util/test/demos/demos.vcxproj.filters @@ -739,6 +739,9 @@ D3D11\demos + + OpenGL\demos + diff --git a/util/test/demos/gl/gl_shader_debug_zoo.cpp b/util/test/demos/gl/gl_shader_debug_zoo.cpp new file mode 100644 index 000000000..5a68a99a7 --- /dev/null +++ b/util/test/demos/gl/gl_shader_debug_zoo.cpp @@ -0,0 +1,675 @@ +/****************************************************************************** + * The MIT License (MIT) + * + * Copyright (c) 2019-2025 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" + +RD_TEST(GL_Shader_Debug_Zoo, OpenGLGraphicsTest) +{ + static constexpr const char *Description = + "Tests shader debugging on GL shaders, focussing on GL-specific functionality that is not " + "available or implemented separately from Vulkan."; + + std::string vertex = R"EOSHADER( + +#ifndef LOCATION +#define LOCATION layout +#endif + +out gl_PerVertex +{ + vec4 gl_Position; +}; + +LOCATION(location = 0) in vec3 inPosition; +LOCATION(location = 1) in vec4 inColor; +LOCATION(location = 2) in vec2 inUV; + +LOCATION(location = 1) out vec4 v2fColor; +LOCATION(location = 2) out vec2 v2fUV; + +#ifdef MIXED +// no location interpolators +in float a2v_a; +out float v2f_a; +#endif + +#ifdef BLOCK +LOCATION(location = 3) in float a2v_a; +LOCATION(location = 4) in int a2v_b; + +LOCATION(location = 3) out v2fBlock +{ + float a; + flat int b; +} v2f; +#endif + +#ifdef WEIRD_V2F +LOCATION(location = 3) in float a2v_a; +LOCATION(location = 4) in int a2v_b; + +LOCATION(location = 3) out mat3 v2fMat; +LOCATION(location = 6) out float v2fArr[2]; +#endif +#ifdef MULTI +LOCATION(location = 8) flat out int v2fInstData; +#endif + +void main() +{ + gl_Position = vec4(inPosition.xyz, 1); + v2fColor = inColor; + v2fUV = inUV; + +#ifdef BLOCK + v2f.a = a2v_a; + v2f.b = a2v_b; +#endif + +#ifdef MIXED + v2f_a = a2v_a; +#endif + +#ifdef WEIRD_V2F + v2fMat = mat3(a2v_a * inPosition.xyz, (a2v_a + 1) * inPosition.xyz, (a2v_a + 2) * inPosition.xyz); + v2fArr[0] = sqrt(a2v_a); + v2fArr[1] = cos(a2v_a); +#endif + +#ifdef MULTI + v2fInstData = a2v_b + gl_VertexID/3; + if(gl_InstanceID != 1 && a2v_a > 5.0) + gl_Position.y += 10.0f; +#endif +} + +)EOSHADER"; + + std::string pixel = R"EOSHADER( + +#ifndef LOCATION +#define LOCATION layout +#endif + +LOCATION(location = 1) in vec4 v2fColor; +LOCATION(location = 2) in vec2 v2fUV; + +LOCATION(location = 0) out vec4 outColor; + +#ifdef MIXED +// no location interpolators +in float v2f_a; +#endif + +#ifdef BLOCK +LOCATION(location = 3) in v2fBlock +{ + float a; + flat int b; +} v2f; +#endif + +#ifdef WEIRD_V2F +LOCATION(location = 3) in mat3 v2fMat; +LOCATION(location = 6) in float v2fArr[2]; +#endif +#ifdef MULTI +LOCATION(location = 8) flat in int v2fInstData; +#endif + +void main() +{ + outColor = v2fColor + v2fUV.xyxy; + +#ifdef BLOCK + outColor.r += v2f.a; + outColor.g += float(v2f.b); +#endif + +#ifdef MIXED + outColor.r += v2f_a; +#endif + +#ifdef WEIRD_V2F + outColor.rgb += v2fMat * outColor.rgb; + outColor.g += v2fArr[0] * v2fArr[1]; +#endif + +#ifdef MULTI + if(v2fInstData != 51) discard; +#endif +} + +)EOSHADER"; + + std::string bindingZooVertex = R"EOSHADER( + +#version 460 core + +layout(location = 0) in vec3 inPosition; +layout(location = 1) in vec4 inColor; +layout(location = 2) in vec2 inUV; + +layout(location = 1) out vec4 v2fColor; +layout(location = 2) out vec2 v2fUV; + +void main() +{ + gl_Position = vec4(inPosition.xyz, 1); + v2fColor = inColor; + v2fUV = inUV; +} + +)EOSHADER"; + + std::string bindingZooPixel = R"EOSHADER( + +#version 460 core + +// have uniforms unused - our glslang recompilation will likely assign these locations but drivers likely won't. +uniform int unused; + +// ensure alphabetical order does not match declaration order, also to test that we don't depend on that +// by accident +uniform vec4 zzzTest; + +uniform mat4 matrix_test; + +uniform float array_test[4]; + +struct TestStruct +{ + float a; + int b; +}; + +uniform TestStruct struct_test; + +layout(binding = 0, std140) uniform ubo_test +{ + vec4 data; +} ubo; + +layout(binding = 0, std430) buffer ssbo_test +{ + vec4 data; +} ssbo; + +layout(binding = 0) uniform sampler2D tex2d_test; +layout(binding = 1) uniform samplerBuffer texBuf_test; + +layout(location = 1) in vec4 v2fColor; +layout(location = 2) in vec2 v2fUV; + +layout(location = 0) out vec4 outColor; + +void main() +{ + vec4 col = vec4(0, 0, 0, 0); + col += matrix_test * zzzTest; + col.r += array_test[0]; + col.g += array_test[1]; + col.b += array_test[2]; + col.a += array_test[3]; + col += float(struct_test.b) * struct_test.a; + col += ubo.data; + col += ssbo.data; + col += dFdx(v2fUV).xyxy; + col += texture(tex2d_test, v2fUV); + col += texelFetch(texBuf_test, int(v2fUV.x*10)); + outColor = col; +} + +)EOSHADER"; + + void Prepare(int argc, char **argv) + { + OpenGLGraphicsTest::Prepare(argc, argv); + + if(!Avail.empty()) + return; + + if(!SpvCompilationSupported()) + Avail = InternalSpvCompiler() ? "Internal SPIR-V compiler did not initialise" + : "Couldn't find 'glslc' or 'glslangValidator' in PATH - " + "required for SPIR-V compilation"; + } + + int main() + { + // initialise, create window, create context, etc + if(!Init()) + return 3; + + GLuint vao = MakeVAO(); + glBindVertexArray(vao); + + const DefaultA2V tri[9] = { + {Vec3f(-1.0f, 1.0f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(1.0f, 1.0f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(-1.0f, -1.0f, 0.0f), Vec4f(0.0f, 1.0f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + + {Vec3f(-1.0f, 1.0f, 0.0f), Vec4f(0.0f, 0.0f, 1.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(1.0f, 1.0f, 0.0f), Vec4f(0.0f, 0.0f, 1.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(-1.0f, -1.0f, 0.0f), Vec4f(0.0f, 0.0f, 1.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + + {Vec3f(-1.0f, 1.0f, 0.0f), Vec4f(1.0f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 0.0f)}, + {Vec3f(1.0f, 1.0f, 0.0f), Vec4f(1.0f, 0.0f, 0.0f, 1.0f), Vec2f(0.0f, 1.0f)}, + {Vec3f(-1.0f, -1.0f, 0.0f), Vec4f(1.0f, 0.0f, 0.0f, 1.0f), Vec2f(1.0f, 0.0f)}, + }; + + GLuint vb = MakeBuffer(); + glBindBuffer(GL_ARRAY_BUFFER, vb); + glBufferStorage(GL_ARRAY_BUFFER, sizeof(tri), tri, 0); + + ConfigureDefaultVAO(); + + struct ExtraA2V + { + float a; + int b; + }; + + const ExtraA2V tri2[6] = { + {2.5f, 50}, {3.5f, 7}, {4.5f, 8}, {5.5f, 40}, {6.5f, 50}, {7.5f, 60}, + }; + + GLuint vb2 = MakeBuffer(); + glBindBuffer(GL_ARRAY_BUFFER, vb2); + glBufferStorage(GL_ARRAY_BUFFER, sizeof(tri2), tri2, 0); + + glVertexAttribPointer(3, 1, GL_FLOAT, GL_FALSE, sizeof(ExtraA2V), (void *)(0)); + glVertexAttribDivisor(3, 1); + glVertexAttribIPointer(4, 1, GL_INT, sizeof(ExtraA2V), (void *)(sizeof(float))); + glVertexAttribDivisor(4, 1); + + glEnableVertexAttribArray(3); + glEnableVertexAttribArray(4); + + std::string vs = "#version 460 core\n"; + vs += vertex; + + std::string ps = "#version 460 core\n"; + ps += pixel; + + GLuint spirvprogram = MakeProgram(); + GLuint spirvStrippedProgram = MakeProgram(); + + { + GLuint vsShad = glCreateShader(GL_VERTEX_SHADER); + GLuint psShad = glCreateShader(GL_FRAGMENT_SHADER); + + std::vector vsSPIRV = + CompileShaderToSpv(vs, SPIRVTarget::opengl, ShaderLang::glsl, ShaderStage::vert, "main"); + std::vector fsSPIRV = + CompileShaderToSpv(ps, SPIRVTarget::opengl, ShaderLang::glsl, ShaderStage::frag, "main"); + + glShaderBinary(1, &vsShad, GL_SHADER_BINARY_FORMAT_SPIR_V, vsSPIRV.data(), + (GLsizei)vsSPIRV.size() * 4); + glShaderBinary(1, &psShad, GL_SHADER_BINARY_FORMAT_SPIR_V, fsSPIRV.data(), + (GLsizei)fsSPIRV.size() * 4); + + std::string entry_point = "main"; + glSpecializeShaderARB(vsShad, (const GLchar *)entry_point.c_str(), 0, nullptr, nullptr); + glSpecializeShaderARB(psShad, (const GLchar *)entry_point.c_str(), 0, nullptr, nullptr); + + glAttachShader(spirvprogram, vsShad); + glAttachShader(spirvprogram, psShad); + + glLinkProgram(spirvprogram); + + glDetachShader(spirvprogram, vsShad); + glDetachShader(spirvprogram, psShad); + + glDeleteShader(vsShad); + glDeleteShader(psShad); + + // strip any OpName/OpMemberName + + for(size_t i = 5; i < vsSPIRV.size();) + { + uint16_t WordCount = vsSPIRV[i] >> 16; + uint16_t opcode = vsSPIRV[i] & 0xffff; + + // OpName / OpMemberName + if(opcode == 5 || opcode == 6) + { + vsSPIRV.erase(vsSPIRV.begin() + i, vsSPIRV.begin() + i + WordCount); + continue; + } + + i += WordCount; + } + + for(size_t i = 5; i < fsSPIRV.size();) + { + uint16_t WordCount = fsSPIRV[i] >> 16; + uint16_t opcode = fsSPIRV[i] & 0xffff; + + // OpName / OpMemberName + if(opcode == 5 || opcode == 6) + { + fsSPIRV.erase(fsSPIRV.begin() + i, fsSPIRV.begin() + i + WordCount); + continue; + } + + i += WordCount; + } + + vsShad = glCreateShader(GL_VERTEX_SHADER); + psShad = glCreateShader(GL_FRAGMENT_SHADER); + + glShaderBinary(1, &vsShad, GL_SHADER_BINARY_FORMAT_SPIR_V, vsSPIRV.data(), + (GLsizei)vsSPIRV.size() * 4); + glShaderBinary(1, &psShad, GL_SHADER_BINARY_FORMAT_SPIR_V, fsSPIRV.data(), + (GLsizei)fsSPIRV.size() * 4); + + glSpecializeShaderARB(vsShad, (const GLchar *)entry_point.c_str(), 0, nullptr, nullptr); + glSpecializeShaderARB(psShad, (const GLchar *)entry_point.c_str(), 0, nullptr, nullptr); + + glAttachShader(spirvStrippedProgram, vsShad); + glAttachShader(spirvStrippedProgram, psShad); + + glLinkProgram(spirvStrippedProgram); + + glDetachShader(spirvStrippedProgram, vsShad); + glDetachShader(spirvStrippedProgram, psShad); + + glDeleteShader(vsShad); + glDeleteShader(psShad); + } + + GLuint simpleModernGLSLProgram = MakeProgram(vs, ps); + + vs = "#version 460 core\n"; + vs += "#define LOCATION(x)\n"; + vs += vertex; + + ps = "#version 460 core\n"; + ps += "#define LOCATION(x)\n"; + ps += pixel; + + GLuint noLocationGLSLProgram = MakeProgram(vs, ps, "", [](GLuint prog) { + glBindAttribLocation(prog, 0, "inPosition"); + glBindAttribLocation(prog, 1, "inColor"); + glBindAttribLocation(prog, 2, "inUV"); + }); + + vs = "#version 460 core\n"; + vs += "#define BLOCK 1\n"; + vs += vertex; + + ps = "#version 460 core\n"; + ps += "#define BLOCK 1\n"; + ps += pixel; + + GLuint blockLocationGLSLProgram = MakeProgram(vs, ps); + + vs = "#version 460 core\n"; + vs += "#define BLOCK 1\n"; + vs += "#define LOCATION(x)\n"; + vs += vertex; + + ps = "#version 460 core\n"; + ps += "#define BLOCK 1\n"; + ps += "#define LOCATION(x)\n"; + ps += pixel; + + GLuint blockNoLocationGLSLProgram = MakeProgram(vs, ps, "", [](GLuint prog) { + glBindAttribLocation(prog, 0, "inPosition"); + glBindAttribLocation(prog, 1, "inColor"); + glBindAttribLocation(prog, 2, "inUV"); + glBindAttribLocation(prog, 3, "a2v_a"); + glBindAttribLocation(prog, 4, "a2v_b"); + }); + + vs = "#version 460 core\n"; + vs += "#define MIXED 1\n"; + vs += vertex; + + ps = "#version 460 core\n"; + ps += "#define MIXED 1\n"; + ps += pixel; + + GLuint mixedLocationGLSLProgram = + MakeProgram(vs, ps, "", [](GLuint prog) { glBindAttribLocation(prog, 3, "a2v_a"); }); + + vs = "#version 460 core\n"; + vs += "#define WEIRD_V2F 1\n"; + vs += vertex; + + ps = "#version 460 core\n"; + ps += "#define WEIRD_V2F 1\n"; + ps += pixel; + + GLuint weirdLocationGLSLProgram = MakeProgram(vs, ps); + + GLuint pipeline = MakePipeline(); + GLuint pipelineVert = MakePipelineProgram(GL_VERTEX_SHADER, vs); + GLuint pipelinePix = MakePipelineProgram(GL_FRAGMENT_SHADER, ps); + glUseProgramStages(pipeline, GL_VERTEX_SHADER_BIT, pipelineVert); + glUseProgramStages(pipeline, GL_FRAGMENT_SHADER_BIT, pipelinePix); + + vs = "#version 460 core\n"; + vs += "#define WEIRD_V2F 1\n"; + vs += "#define MULTI 1\n"; + vs += vertex; + + ps = "#version 460 core\n"; + ps += "#define WEIRD_V2F 1\n"; + ps += "#define MULTI 1\n"; + ps += pixel; + + GLuint multiGLSLProgram = MakeProgram(vs, ps); + + vs = "#version 460 core\n"; + vs += "#define WEIRD_V2F 1\n"; + vs += "#define LOCATION(x)\n"; + vs += vertex; + + ps = "#version 460 core\n"; + ps += "#define WEIRD_V2F 1\n"; + ps += "#define LOCATION(x)\n"; + ps += pixel; + + GLuint weirdNoLocationGLSLProgram = MakeProgram(vs, ps, "", [](GLuint prog) { + glBindAttribLocation(prog, 0, "inPosition"); + glBindAttribLocation(prog, 1, "inColor"); + glBindAttribLocation(prog, 2, "inUV"); + glBindAttribLocation(prog, 3, "a2v_a"); + glBindAttribLocation(prog, 4, "a2v_b"); + }); + + GLuint bindingZooProgram = MakeProgram(bindingZooVertex, bindingZooPixel); + (void)bindingZooProgram; + + glUseProgram(bindingZooProgram); + + glUniform4f(glGetUniformLocation(bindingZooProgram, "zzzTest"), 0.2f, 0.3f, 0.4f, 0.5f); + float mat[16] = {}; + for(int i = 0; i < 16; i++) + mat[i] = 0.6f + float(i) / 10.0f; + glUniformMatrix4fv(glGetUniformLocation(bindingZooProgram, "matrix_test"), 1, GL_FALSE, mat); + float arr[4] = {}; + for(int i = 0; i < 4; i++) + arr[i] = 2.1f + float(i) / 10.0f; + glUniform1fv(glGetUniformLocation(bindingZooProgram, "array_test"), 4, arr); + glUniform1f(glGetUniformLocation(bindingZooProgram, "struct_test.a"), 9.9f); + glUniform1i(glGetUniformLocation(bindingZooProgram, "struct_test.b"), 99); + + const size_t bindOffset = 16; + + Vec4f cbufferdata[1024 + bindOffset]; + + for(int i = 0; i < bindOffset; i++) + cbufferdata[i] = Vec4f(-99.9f, -88.8f, -77.7f, -66.6f); + + for(int i = 0; i < 1024; i++) + cbufferdata[bindOffset + i] = + Vec4f(float(i * 4 + 0), float(i * 4 + 1), float(i * 4 + 2), float(i * 4 + 3)); + + GLuint cb = MakeBuffer(); + glBindBuffer(GL_UNIFORM_BUFFER, cb); + glBufferStorage(GL_UNIFORM_BUFFER, sizeof(cbufferdata), cbufferdata, GL_MAP_WRITE_BIT); + + glBindBufferRange(GL_UNIFORM_BUFFER, 0, cb, bindOffset * sizeof(Vec4f), 16 * sizeof(Vec4f)); + + GLuint ssbo = MakeBuffer(); + glBindBuffer(GL_SHADER_STORAGE_BUFFER, ssbo); + glBufferStorage(GL_SHADER_STORAGE_BUFFER, sizeof(cbufferdata), cbufferdata, GL_MAP_WRITE_BIT); + + glBindBufferRange(GL_SHADER_STORAGE_BUFFER, 0, ssbo, bindOffset * sizeof(Vec4f) * 2, + 16 * sizeof(Vec4f)); + + GLuint tbuf_store = MakeBuffer(); + glBindBuffer(GL_TEXTURE_BUFFER, tbuf_store); + glBufferStorage(GL_TEXTURE_BUFFER, sizeof(Vec4f) * 16, + cbufferdata + bindOffset * sizeof(Vec4f) * 3, 0); + + GLuint tbuf = MakeTexture(); + glBindTexture(GL_TEXTURE_BUFFER, tbuf); + glTexBuffer(GL_TEXTURE_BUFFER, GL_RGB32F, tbuf_store); + + glActiveTexture(GL_TEXTURE1); + glBindTexture(GL_TEXTURE_BUFFER, tbuf); + + Texture rgba8; + LoadXPM(SmileyTexture, rgba8); + + glActiveTexture(GL_TEXTURE0); + GLuint smiley = MakeTexture(); + glBindTexture(GL_TEXTURE_2D, smiley); + glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, rgba8.width, rgba8.height); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, rgba8.width, rgba8.height, GL_RGBA, GL_UNSIGNED_BYTE, + rgba8.data.data()); + + // 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); + + while(Running()) + { + glBindFramebuffer(GL_FRAMEBUFFER, fbo); + + float col[] = {0.0f, 0.0f, 0.0f, 1.0f}; + glClearBufferfv(GL_COLOR, 0, col); + + glBindVertexArray(vao); + + int test = 0; + + pushMarker("GLSL tests"); + { + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(simpleModernGLSLProgram); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(noLocationGLSLProgram); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(blockLocationGLSLProgram); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(blockNoLocationGLSLProgram); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(mixedLocationGLSLProgram); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(weirdLocationGLSLProgram); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(multiGLSLProgram); + glDrawArraysInstancedBaseInstance(GL_TRIANGLES, 3, 3, 3, 3); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(multiGLSLProgram); + glDrawArrays(GL_TRIANGLES, 0, 9); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(weirdNoLocationGLSLProgram); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(bindingZooProgram); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(0); + glBindProgramPipeline(pipeline); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glBindProgramPipeline(0); + } + popMarker(); + + pushMarker("SPIRV tests"); + { + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(spirvprogram); + glDrawArrays(GL_TRIANGLES, 0, 3); + + glViewport(test * 4, screenHeight - 4, 4, 4); + test++; + glUseProgram(spirvStrippedProgram); + glDrawArrays(GL_TRIANGLES, 0, 3); + } + popMarker(); + + 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/gl/gl_test.cpp b/util/test/demos/gl/gl_test.cpp index 3e62b4192..c45ad7cd7 100644 --- a/util/test/demos/gl/gl_test.cpp +++ b/util/test/demos/gl/gl_test.cpp @@ -143,7 +143,8 @@ void OpenGLGraphicsTest::Shutdown() delete mainWindow; } -GLuint OpenGLGraphicsTest::MakeProgram(std::string vertSrc, std::string fragSrc, std::string geomSrc) +GLuint OpenGLGraphicsTest::MakeProgram(std::string vertSrc, std::string fragSrc, + std::string geomSrc, std::function prelink) { GLuint vs = vertSrc.empty() ? 0 : glCreateShader(GL_VERTEX_SHADER); GLuint fs = fragSrc.empty() ? 0 : glCreateShader(GL_FRAGMENT_SHADER); @@ -232,6 +233,9 @@ GLuint OpenGLGraphicsTest::MakeProgram(std::string vertSrc, std::string fragSrc, if(!vs || !fs) glProgramParameteri(program, GL_PROGRAM_SEPARABLE, GL_TRUE); + if(prelink) + prelink(program); + glLinkProgram(program); glGetProgramiv(program, GL_LINK_STATUS, &status); diff --git a/util/test/demos/gl/gl_test.h b/util/test/demos/gl/gl_test.h index a0e522f03..9a807b205 100644 --- a/util/test/demos/gl/gl_test.h +++ b/util/test/demos/gl/gl_test.h @@ -28,6 +28,7 @@ #include "3rdparty/glad/glad.h" +#include #include struct OpenGLGraphicsTest : public GraphicsTest @@ -44,7 +45,8 @@ struct OpenGLGraphicsTest : public GraphicsTest void PostInit(); - GLuint MakeProgram(std::string vertSrc, std::string fragSrc, std::string geomSrc = ""); + GLuint MakeProgram(std::string vertSrc, std::string fragSrc, std::string geomSrc = "", + std::function prelink = {}); GLuint MakeProgram(std::string compSrc); GLuint MakeProgram(); GLuint MakePipelineProgram(GLenum type, std::string src); diff --git a/util/test/tests/GL/GL_Shader_Debug_Zoo.py b/util/test/tests/GL/GL_Shader_Debug_Zoo.py new file mode 100644 index 000000000..f202ae2fb --- /dev/null +++ b/util/test/tests/GL/GL_Shader_Debug_Zoo.py @@ -0,0 +1,165 @@ +import renderdoc as rd +from typing import List +import rdtest + + +class GL_Shader_Debug_Zoo(rdtest.TestCase): + demos_test_name = 'GL_Shader_Debug_Zoo' + + def check_capture(self): + if not self.controller.GetAPIProperties().shaderDebugging: + rdtest.log.success("Shader debugging not enabled, skipping test") + return + + failed = False + + x = -3 + for child in ["GLSL tests", "SPIRV tests"]: + rdtest.log.begin_section(child) + section = self.find_action(child) + for test in range(len(section.children)): + action = section.children[test] + + if not (action.flags & rd.ActionFlags.Drawcall): + continue + + x += 4 + + self.controller.SetFrameEvent(action.eventId, False) + pipe: rd.PipeState = self.controller.GetPipelineState() + + if not pipe.GetShaderReflection(rd.ShaderStage.Vertex).debugInfo.debuggable: + rdtest.log.print("Skipping undebuggable shader at {} in {}.".format(test, child)) + return + + if not pipe.GetShaderReflection(rd.ShaderStage.Pixel).debugInfo.debuggable: + rdtest.log.print("Skipping undebuggable shader at {} in {}.".format(test, child)) + return + + y = 1 + + inputs = rd.DebugPixelInputs() + inputs.primitive = 0 + inputs.sample = 0 + + if action.numIndices > 3: + rdtest.log.print("prim 1") + inputs.primitive = 1 + + # Debug the shader + trace: rd.ShaderDebugTrace = self.controller.DebugPixel(x, y, inputs) + + rdtest.log.print(f"debugging {x},{y}") + + if trace.debugger is None: + failed = True + rdtest.log.error("Test {} in sub-section {} did not debug pixel".format(test, child)) + self.controller.FreeTrace(trace) + continue + + _, variables = self.process_trace(trace) + + output: rd.SourceVariableMapping = self.find_output_source_var(trace, rd.ShaderBuiltin.ColorOutput, 0) + + debugged = self.evaluate_source_var(output, variables) + + try: + self.check_pixel_value(pipe.GetOutputTargets()[0].resource, x, y, debugged.value.f32v[0:4]) + except rdtest.TestFailureException as ex: + failed = True + rdtest.log.error("Test {} in sub-section {} did not match pixel. {}".format(test, child, str(ex))) + continue + finally: + self.controller.FreeTrace(trace) + + rdtest.log.success("Test {} pixel in sub-section {} matched as expected".format(test, child)) + + vtx = 1 + inst = 0 + idx = vtx + + if action.flags & rd.ActionFlags.Instanced: + inst = 1 + + if action.flags & rd.ActionFlags.Indexed: + ib = pipe.GetIBuffer() + + mesh = rd.MeshFormat() + mesh.indexResourceId = ib.resourceId + mesh.indexByteStride = ib.byteStride + mesh.indexByteOffset = ib.byteOffset + action.indexOffset * ib.byteStride + mesh.indexByteSize = ib.byteSize + mesh.baseVertex = action.baseVertex + + indices = rdtest.fetch_indices(self.controller, action, mesh, 0, vtx, 1) + + idx = indices[1] + + postvs = self.get_postvs(action, rd.MeshDataStage.VSOut, first_index=vtx, num_indices=1, instance=inst) + + trace: rd.ShaderDebugTrace = self.controller.DebugVertex(vtx, inst, idx, 0) + + if trace.debugger is None: + failed = True + rdtest.log.error("Test {} in sub-section {} did not debug vertex".format(test, child)) + self.controller.FreeTrace(trace) + continue + + _, variables = self.process_trace(trace) + + outputs = 0 + + for var in trace.sourceVars: + var: rd.SourceVariableMapping + if var.variables[0].type == rd.DebugVariableType.Variable and var.signatureIndex >= 0: + name = var.name + + if name not in postvs[0].keys(): + name = name.replace(".", "Block.") + if name not in postvs[0].keys(): + failed = True + rdtest.log.error("Don't have expected output for {}".format(name)) + continue + + expect = postvs[0][name] + value = self.evaluate_source_var(var, variables) + + if len(expect) != value.columns: + failed = True + rdtest.log.error( + "Output {} at EID {} has different size ({} values) to expectation ({} values)" + .format(name, action.eventId, value.columns, len(expect))) + continue + + compType = rd.VarTypeCompType(value.type) + if compType == rd.CompType.UInt: + debugged = list(value.value.u32v[0:value.columns]) + elif compType == rd.CompType.SInt: + debugged = list(value.value.s32v[0:value.columns]) + else: + debugged = list(value.value.f32v[0:value.columns]) + + if not rdtest.value_compare(expect, debugged): + failed = True + rdtest.log.error("Test {} in sub-section {} did not match vertex.\nExpected {} but got {}".format(test, child, expect, debugged)) + break + + is_eq, diff_amt = rdtest.value_compare_diff(expect, debugged, eps=5.0E-06) + if not is_eq: + failed = True + rdtest.log.error( + "Debugged value {} at EID {} vert {} (idx {}) instance {}: {} difference. {} doesn't exactly match postvs output {}".format( + name, action.eventId, vtx, idx, inst, diff_amt, debugged, expect)) + + outputs = outputs + 1 + + self.controller.FreeTrace(trace) + + rdtest.log.success("Test {} vertex in sub-section {} matched as expected".format(test, child)) + + rdtest.log.end_section(child) + + if failed: + raise rdtest.TestFailureException("Some tests were not as expected") + + rdtest.log.success("All tests matched")