| // |
| // Copyright 2014 The ANGLE Project Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| // |
| |
| #include "util/shader_utils.h" |
| |
| #include <cstring> |
| #include <fstream> |
| #include <iostream> |
| #include <vector> |
| |
| #include "common/utilities.h" |
| #include "util/test_utils.h" |
| |
| namespace |
| { |
| bool ReadEntireFile(const std::string &filePath, std::string *contentsOut) |
| { |
| constexpr uint32_t kMaxBufferSize = 2000; |
| char buffer[kMaxBufferSize] = {}; |
| if (!angle::ReadEntireFileToString(filePath.c_str(), buffer, kMaxBufferSize) || |
| strlen(buffer) == 0) |
| return false; |
| *contentsOut = buffer; |
| return true; |
| } |
| |
| GLuint CompileProgramInternal(const char *vsSource, |
| const char *tcsSource, |
| const char *tesSource, |
| const char *gsSource, |
| const char *fsSource, |
| const std::function<void(GLuint)> &preLinkCallback) |
| { |
| GLuint vs = CompileShader(GL_VERTEX_SHADER, vsSource); |
| GLuint fs = CompileShader(GL_FRAGMENT_SHADER, fsSource); |
| |
| if (vs == 0 || fs == 0) |
| { |
| glDeleteShader(fs); |
| glDeleteShader(vs); |
| return 0; |
| } |
| |
| GLuint program = glCreateProgram(); |
| |
| glAttachShader(program, vs); |
| glDeleteShader(vs); |
| |
| glAttachShader(program, fs); |
| glDeleteShader(fs); |
| |
| GLuint tcs = 0; |
| GLuint tes = 0; |
| GLuint gs = 0; |
| |
| if (strlen(tcsSource) > 0) |
| { |
| tcs = CompileShader(GL_TESS_CONTROL_SHADER_EXT, tcsSource); |
| if (tcs == 0) |
| { |
| glDeleteShader(vs); |
| glDeleteShader(fs); |
| glDeleteProgram(program); |
| return 0; |
| } |
| |
| glAttachShader(program, tcs); |
| glDeleteShader(tcs); |
| } |
| |
| if (strlen(tesSource) > 0) |
| { |
| tes = CompileShader(GL_TESS_EVALUATION_SHADER_EXT, tesSource); |
| if (tes == 0) |
| { |
| glDeleteShader(vs); |
| glDeleteShader(fs); |
| glDeleteShader(tcs); |
| glDeleteProgram(program); |
| return 0; |
| } |
| |
| glAttachShader(program, tes); |
| glDeleteShader(tes); |
| } |
| |
| if (strlen(gsSource) > 0) |
| { |
| gs = CompileShader(GL_GEOMETRY_SHADER_EXT, gsSource); |
| if (gs == 0) |
| { |
| glDeleteShader(vs); |
| glDeleteShader(fs); |
| glDeleteShader(tcs); |
| glDeleteShader(tes); |
| glDeleteProgram(program); |
| return 0; |
| } |
| |
| glAttachShader(program, gs); |
| glDeleteShader(gs); |
| } |
| |
| if (preLinkCallback) |
| { |
| preLinkCallback(program); |
| } |
| |
| glLinkProgram(program); |
| |
| return CheckLinkStatusAndReturnProgram(program, true); |
| } |
| |
| const void *gCallbackChainUserParam; |
| |
| void KHRONOS_APIENTRY DebugMessageCallback(GLenum source, |
| GLenum type, |
| GLuint id, |
| GLenum severity, |
| GLsizei length, |
| const GLchar *message, |
| const void *userParam) |
| { |
| std::string sourceText = gl::GetDebugMessageSourceString(source); |
| std::string typeText = gl::GetDebugMessageTypeString(type); |
| std::string severityText = gl::GetDebugMessageSeverityString(severity); |
| std::cerr << sourceText << ", " << typeText << ", " << severityText << ": " << message << "\n"; |
| |
| GLDEBUGPROC callbackChain = reinterpret_cast<GLDEBUGPROC>(const_cast<void *>(userParam)); |
| if (callbackChain) |
| { |
| callbackChain(source, type, id, severity, length, message, gCallbackChainUserParam); |
| } |
| } |
| } // namespace |
| |
| GLuint CompileShader(GLenum type, const char *source) |
| { |
| GLuint shader = glCreateShader(type); |
| |
| const char *sourceArray[1] = {source}; |
| glShaderSource(shader, 1, sourceArray, nullptr); |
| glCompileShader(shader); |
| |
| GLint compileResult; |
| glGetShaderiv(shader, GL_COMPILE_STATUS, &compileResult); |
| |
| if (compileResult == 0) |
| { |
| GLint infoLogLength; |
| glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLogLength); |
| |
| // Info log length includes the null terminator, so 1 means that the info log is an empty |
| // string. |
| if (infoLogLength > 1) |
| { |
| std::vector<GLchar> infoLog(infoLogLength); |
| glGetShaderInfoLog(shader, static_cast<GLsizei>(infoLog.size()), nullptr, &infoLog[0]); |
| std::cerr << "shader compilation failed: " << &infoLog[0]; |
| } |
| else |
| { |
| std::cerr << "shader compilation failed. <Empty log message>"; |
| } |
| |
| std::cerr << std::endl; |
| |
| glDeleteShader(shader); |
| shader = 0; |
| } |
| |
| return shader; |
| } |
| |
| GLuint CompileShaderFromFile(GLenum type, const std::string &sourcePath) |
| { |
| std::string source; |
| if (!ReadEntireFile(sourcePath, &source)) |
| { |
| std::cerr << "Error reading shader file: " << sourcePath << "\n"; |
| return 0; |
| } |
| |
| return CompileShader(type, source.c_str()); |
| } |
| |
| GLuint CheckLinkStatusAndReturnProgram(GLuint program, bool outputErrorMessages) |
| { |
| if (glGetError() != GL_NO_ERROR) |
| return 0; |
| |
| GLint linkStatus; |
| glGetProgramiv(program, GL_LINK_STATUS, &linkStatus); |
| if (linkStatus == 0) |
| { |
| if (outputErrorMessages) |
| { |
| GLint infoLogLength; |
| glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLogLength); |
| |
| // Info log length includes the null terminator, so 1 means that the info log is an |
| // empty string. |
| if (infoLogLength > 1) |
| { |
| std::vector<GLchar> infoLog(infoLogLength); |
| glGetProgramInfoLog(program, static_cast<GLsizei>(infoLog.size()), nullptr, |
| &infoLog[0]); |
| |
| std::cerr << "program link failed: " << &infoLog[0]; |
| } |
| else |
| { |
| std::cerr << "program link failed. <Empty log message>"; |
| } |
| } |
| |
| glDeleteProgram(program); |
| return 0; |
| } |
| |
| return program; |
| } |
| |
| GLuint GetProgramShader(GLuint program, GLint requestedType) |
| { |
| static constexpr GLsizei kMaxShaderCount = 16; |
| GLuint attachedShaders[kMaxShaderCount] = {0u}; |
| GLsizei count = 0; |
| glGetAttachedShaders(program, kMaxShaderCount, &count, attachedShaders); |
| for (int i = 0; i < count; ++i) |
| { |
| GLint type = 0; |
| glGetShaderiv(attachedShaders[i], GL_SHADER_TYPE, &type); |
| if (type == requestedType) |
| { |
| return attachedShaders[i]; |
| } |
| } |
| |
| return 0; |
| } |
| |
| GLuint CompileProgramWithTransformFeedback( |
| const char *vsSource, |
| const char *fsSource, |
| const std::vector<std::string> &transformFeedbackVaryings, |
| GLenum bufferMode) |
| { |
| auto preLink = [&](GLuint program) { |
| if (transformFeedbackVaryings.size() > 0) |
| { |
| std::vector<const char *> constCharTFVaryings; |
| |
| for (const std::string &transformFeedbackVarying : transformFeedbackVaryings) |
| { |
| constCharTFVaryings.push_back(transformFeedbackVarying.c_str()); |
| } |
| |
| glTransformFeedbackVaryings(program, |
| static_cast<GLsizei>(transformFeedbackVaryings.size()), |
| &constCharTFVaryings[0], bufferMode); |
| } |
| }; |
| |
| return CompileProgramInternal(vsSource, "", "", "", fsSource, preLink); |
| } |
| |
| GLuint CompileProgram(const char *vsSource, const char *fsSource) |
| { |
| return CompileProgramInternal(vsSource, "", "", "", fsSource, nullptr); |
| } |
| |
| GLuint CompileProgram(const char *vsSource, |
| const char *fsSource, |
| const std::function<void(GLuint)> &preLinkCallback) |
| { |
| return CompileProgramInternal(vsSource, "", "", "", fsSource, preLinkCallback); |
| } |
| |
| GLuint CompileProgramWithGS(const char *vsSource, const char *gsSource, const char *fsSource) |
| { |
| return CompileProgramInternal(vsSource, "", "", gsSource, fsSource, nullptr); |
| } |
| |
| GLuint CompileProgramWithTESS(const char *vsSource, |
| const char *tcsSource, |
| const char *tesSource, |
| const char *fsSource) |
| { |
| return CompileProgramInternal(vsSource, tcsSource, tesSource, "", fsSource, nullptr); |
| } |
| |
| GLuint CompileProgramFromFiles(const std::string &vsPath, const std::string &fsPath) |
| { |
| std::string vsSource; |
| if (!ReadEntireFile(vsPath, &vsSource)) |
| { |
| std::cerr << "Error reading shader: " << vsPath << "\n"; |
| return 0; |
| } |
| |
| std::string fsSource; |
| if (!ReadEntireFile(fsPath, &fsSource)) |
| { |
| std::cerr << "Error reading shader: " << fsPath << "\n"; |
| return 0; |
| } |
| |
| return CompileProgram(vsSource.c_str(), fsSource.c_str()); |
| } |
| |
| GLuint CompileComputeProgram(const char *csSource, bool outputErrorMessages) |
| { |
| GLuint program = glCreateProgram(); |
| |
| GLuint cs = CompileShader(GL_COMPUTE_SHADER, csSource); |
| if (cs == 0) |
| { |
| glDeleteProgram(program); |
| return 0; |
| } |
| |
| glAttachShader(program, cs); |
| |
| glLinkProgram(program); |
| |
| return CheckLinkStatusAndReturnProgram(program, outputErrorMessages); |
| } |
| |
| GLuint LoadBinaryProgramOES(const std::vector<uint8_t> &binary, GLenum binaryFormat) |
| { |
| GLuint program = glCreateProgram(); |
| glProgramBinaryOES(program, binaryFormat, binary.data(), static_cast<GLint>(binary.size())); |
| return CheckLinkStatusAndReturnProgram(program, true); |
| } |
| |
| GLuint LoadBinaryProgramES3(const std::vector<uint8_t> &binary, GLenum binaryFormat) |
| { |
| GLuint program = glCreateProgram(); |
| glProgramBinary(program, binaryFormat, binary.data(), static_cast<GLint>(binary.size())); |
| return CheckLinkStatusAndReturnProgram(program, true); |
| } |
| |
| bool LinkAttachedProgram(GLuint program) |
| { |
| glLinkProgram(program); |
| return (CheckLinkStatusAndReturnProgram(program, true) != 0); |
| } |
| |
| void EnableDebugCallback(GLDEBUGPROC callbackChain, const void *userParam) |
| { |
| gCallbackChainUserParam = userParam; |
| |
| glEnable(GL_DEBUG_OUTPUT); |
| glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS); |
| // Enable medium and high priority messages. |
| glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_HIGH, 0, nullptr, |
| GL_TRUE); |
| glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_MEDIUM, 0, nullptr, |
| GL_TRUE); |
| // Disable low and notification priority messages. |
| glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_LOW, 0, nullptr, |
| GL_FALSE); |
| glDebugMessageControlKHR(GL_DONT_CARE, GL_DONT_CARE, GL_DEBUG_SEVERITY_NOTIFICATION, 0, nullptr, |
| GL_FALSE); |
| // Disable performance messages to reduce spam. |
| glDebugMessageControlKHR(GL_DONT_CARE, GL_DEBUG_TYPE_PERFORMANCE, GL_DONT_CARE, 0, nullptr, |
| GL_FALSE); |
| glDebugMessageCallbackKHR(DebugMessageCallback, reinterpret_cast<const void *>(callbackChain)); |
| } |
| |
| namespace angle |
| { |
| |
| namespace essl1_shaders |
| { |
| |
| const char *PositionAttrib() |
| { |
| return "a_position"; |
| } |
| const char *ColorUniform() |
| { |
| return "u_color"; |
| } |
| |
| const char *Texture2DUniform() |
| { |
| return "u_tex2D"; |
| } |
| |
| namespace vs |
| { |
| |
| // A shader that sets gl_Position to zero. |
| const char *Zero() |
| { |
| return R"(void main() |
| { |
| gl_Position = vec4(0); |
| })"; |
| } |
| |
| // A shader that sets gl_Position to attribute a_position. |
| const char *Simple() |
| { |
| return R"(precision highp float; |
| attribute vec4 a_position; |
| |
| void main() |
| { |
| gl_Position = a_position; |
| })"; |
| } |
| |
| // A shader that simply passes through attribute a_position, setting it to gl_Position and varying |
| // v_position. |
| const char *Passthrough() |
| { |
| return R"(precision highp float; |
| attribute vec4 a_position; |
| varying vec4 v_position; |
| |
| void main() |
| { |
| gl_Position = a_position; |
| v_position = a_position; |
| })"; |
| } |
| |
| // A shader that simply passes through attribute a_position, setting it to gl_Position and varying |
| // texcoord. |
| const char *Texture2D() |
| { |
| return R"(precision highp float; |
| attribute vec4 a_position; |
| varying vec2 v_texCoord; |
| |
| void main() |
| { |
| gl_Position = vec4(a_position.xy, 0.0, 1.0); |
| v_texCoord = a_position.xy * 0.5 + vec2(0.5); |
| })"; |
| } |
| |
| } // namespace vs |
| |
| namespace fs |
| { |
| |
| // A shader that renders a simple checker pattern of red and green. X axis and y axis separate the |
| // different colors. Needs varying v_position. |
| const char *Checkered() |
| { |
| return R"(precision highp float; |
| varying vec4 v_position; |
| |
| void main() |
| { |
| bool isLeft = v_position.x < 0.0; |
| bool isTop = v_position.y < 0.0; |
| if (isLeft) |
| { |
| if (isTop) |
| { |
| gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); |
| } |
| else |
| { |
| gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); |
| } |
| } |
| else |
| { |
| if (isTop) |
| { |
| gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); |
| } |
| else |
| { |
| gl_FragColor = vec4(1.0, 1.0, 0.0, 1.0); |
| } |
| } |
| })"; |
| } |
| |
| // A shader that fills with color taken from uniform named "color". |
| const char *UniformColor() |
| { |
| return R"(uniform mediump vec4 u_color; |
| void main(void) |
| { |
| gl_FragColor = u_color; |
| })"; |
| } |
| |
| // A shader that fills with 100% opaque red. |
| const char *Red() |
| { |
| return R"(precision mediump float; |
| |
| void main() |
| { |
| gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0); |
| })"; |
| } |
| |
| // A shader that fills with 100% opaque green. |
| const char *Green() |
| { |
| return R"(precision mediump float; |
| |
| void main() |
| { |
| gl_FragColor = vec4(0.0, 1.0, 0.0, 1.0); |
| })"; |
| } |
| |
| // A shader that fills with 100% opaque blue. |
| const char *Blue() |
| { |
| return R"(precision mediump float; |
| |
| void main() |
| { |
| gl_FragColor = vec4(0.0, 0.0, 1.0, 1.0); |
| })"; |
| } |
| |
| // A shader that samples the texture. |
| const char *Texture2D() |
| { |
| return R"(precision mediump float; |
| uniform sampler2D u_tex2D; |
| varying vec2 v_texCoord; |
| |
| void main() |
| { |
| gl_FragColor = texture2D(u_tex2D, v_texCoord); |
| })"; |
| } |
| |
| } // namespace fs |
| } // namespace essl1_shaders |
| |
| namespace essl3_shaders |
| { |
| |
| const char *PositionAttrib() |
| { |
| return "a_position"; |
| } |
| const char *Texture2DUniform() |
| { |
| return "u_tex2D"; |
| } |
| const char *LodUniform() |
| { |
| return "u_lod"; |
| } |
| |
| namespace vs |
| { |
| |
| // A shader that sets gl_Position to zero. |
| const char *Zero() |
| { |
| return R"(#version 300 es |
| void main() |
| { |
| gl_Position = vec4(0); |
| })"; |
| } |
| |
| // A shader that sets gl_Position to attribute a_position. |
| const char *Simple() |
| { |
| return R"(#version 300 es |
| in vec4 a_position; |
| void main() |
| { |
| gl_Position = a_position; |
| })"; |
| } |
| |
| // A shader that simply passes through attribute a_position, setting it to gl_Position and varying |
| // v_position. |
| const char *Passthrough() |
| { |
| return R"(#version 300 es |
| in vec4 a_position; |
| out vec4 v_position; |
| void main() |
| { |
| gl_Position = a_position; |
| v_position = a_position; |
| })"; |
| } |
| |
| // A shader that simply passes through attribute a_position, setting it to gl_Position and varying |
| // texcoord. |
| const char *Texture2DLod() |
| { |
| return R"(#version 300 es |
| in vec4 a_position; |
| out vec2 v_texCoord; |
| |
| void main() |
| { |
| gl_Position = vec4(a_position.xy, 0.0, 1.0); |
| v_texCoord = a_position.xy * 0.5 + vec2(0.5); |
| })"; |
| } |
| |
| } // namespace vs |
| |
| namespace fs |
| { |
| |
| // A shader that fills with 100% opaque red. |
| const char *Red() |
| { |
| return R"(#version 300 es |
| precision highp float; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(1.0, 0.0, 0.0, 1.0); |
| })"; |
| } |
| |
| // A shader that fills with 100% opaque green. |
| const char *Green() |
| { |
| return R"(#version 300 es |
| precision highp float; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(0.0, 1.0, 0.0, 1.0); |
| })"; |
| } |
| |
| // A shader that fills with 100% opaque blue. |
| const char *Blue() |
| { |
| return R"(#version 300 es |
| precision highp float; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(0.0, 0.0, 1.0, 1.0); |
| })"; |
| } |
| |
| // A shader that samples the texture at a given lod. |
| const char *Texture2DLod() |
| { |
| return R"(#version 300 es |
| precision mediump float; |
| uniform sampler2D u_tex2D; |
| uniform float u_lod; |
| in vec2 v_texCoord; |
| out vec4 my_FragColor; |
| |
| void main() |
| { |
| my_FragColor = textureLod(u_tex2D, v_texCoord, u_lod); |
| })"; |
| } |
| |
| } // namespace fs |
| } // namespace essl3_shaders |
| |
| namespace essl31_shaders |
| { |
| |
| const char *PositionAttrib() |
| { |
| return "a_position"; |
| } |
| |
| namespace vs |
| { |
| |
| // A shader that sets gl_Position to zero. |
| const char *Zero() |
| { |
| return R"(#version 310 es |
| void main() |
| { |
| gl_Position = vec4(0); |
| })"; |
| } |
| |
| // A shader that sets gl_Position to attribute a_position. |
| const char *Simple() |
| { |
| return R"(#version 310 es |
| in vec4 a_position; |
| void main() |
| { |
| gl_Position = a_position; |
| })"; |
| } |
| |
| // A shader that simply passes through attribute a_position, setting it to gl_Position and varying |
| // v_position. |
| const char *Passthrough() |
| { |
| return R"(#version 310 es |
| in vec4 a_position; |
| out vec4 v_position; |
| void main() |
| { |
| gl_Position = a_position; |
| v_position = a_position; |
| })"; |
| } |
| |
| } // namespace vs |
| |
| namespace fs |
| { |
| |
| // A shader that fills with 100% opaque red. |
| const char *Red() |
| { |
| return R"(#version 310 es |
| precision highp float; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(1.0, 0.0, 0.0, 1.0); |
| })"; |
| } |
| |
| // A shader that fills with 100% opaque green. |
| const char *Green() |
| { |
| return R"(#version 310 es |
| precision highp float; |
| out vec4 my_FragColor; |
| void main() |
| { |
| my_FragColor = vec4(0.0, 1.0, 0.0, 1.0); |
| })"; |
| } |
| |
| // A shader that renders a simple gradient of red to green. Needs varying v_position. |
| const char *RedGreenGradient() |
| { |
| return R"(#version 310 es |
| precision highp float; |
| in vec4 v_position; |
| out vec4 my_FragColor; |
| |
| void main() |
| { |
| my_FragColor = vec4(v_position.xy * 0.5 + vec2(0.5), 0.0, 1.0); |
| })"; |
| } |
| |
| } // namespace fs |
| } // namespace essl31_shaders |
| } // namespace angle |