I am writing a Shadertoy-like application as part of a school project, and noticed that the values of my uniform variables were not updating on my (old) Thinkpad while they were working fine on my desktop PC. My application uses compiled GLSL shaders (using glslc
into SPIR-V binary), which are linked into the final executable and read using the glShaderBinary
function.
I have made a MWE application to showcase this effect. The application reads the fragment and vertex shader in both GLSL source format and in SPIR-V binary format. For both formats, it links the fragment and vertex shader into a single program (called shader
in the source code), and lists the names of all active uniforms. It also attempts to directly get the location of the uniform named test
in shader.frag
.
On Intel (i5-4200U):
vendor: Intel
renderer: Mesa Intel(R) HD Graphics 4400 (HSW GT2)
version: 4.6 (Core Profile) Mesa 24.0.7-arch1.3
glsl version: 4.60
------------------------------
GLSL:
active uniform count: 1
(location = 0) = "test"
`test` uniform location = 0
SPIR-V:
active uniform count: 1
(location = 0) = ""
`test` uniform location = -1
On NVIDIA:
vendor: NVIDIA Corporation
renderer: NVIDIA GeForce GTX 1050 Ti/PCIe/SSE2
version: 4.6.0 NVIDIA 550.78
glsl version: 4.60 NVIDIA
------------------------------
GLSL:
active uniform count: 1
(location = 0) = "test"
`test` uniform location = 0
SPIR-V:
active uniform count: 1
(location = 0) = "test"
`test` uniform location = 0
The sources are also available as a git repository:
git clone --branch bug2 https://git.pipeframe.xyz/school/project-iprj
main.c
#include <stdio.h>
#include <stdlib.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
const uint32_t vert_spirv[] =
#include "vert_spirv.h"
;
const uint32_t frag_spirv[] =
#include "frag_spirv.h"
;
const char vert_src[] = {
#include "vert_src.h"
, 0x00 };
const char frag_src[] = {
#include "frag_src.h"
, 0x00 };
GLuint load_shader_src(GLenum type, const char* src) {
GLuint shader = glCreateShader(type);
glShaderSource(shader, 1, &src, NULL);
glCompileShader(shader);
return shader;
}
GLuint load_shader_spirv(GLenum type, const char* src, size_t size) {
GLuint shader = glCreateShader(type);
glShaderBinary(1, &shader, GL_SHADER_BINARY_FORMAT_SPIR_V, src, size);
glSpecializeShader(shader, "main", 0, NULL, NULL);
return shader;
}
GLuint link_shaders(GLuint vert, GLuint frag) {
GLuint shader = glCreateProgram();
glAttachShader(shader, vert);
glAttachShader(shader, frag);
glLinkProgram(shader);
glUseProgram(shader); // <- use the program before getting uniform name
glDeleteShader(vert);
glDeleteShader(frag);
return shader;
}
void test(const char* label, GLuint shader) {
printf("%s:\n", label);
// list all ACTIVE uniforms in shader
GLint count;
glGetProgramiv(shader, GL_ACTIVE_UNIFORMS, &count);
printf("\tactive uniform count: %d\n", count);
for (unsigned i = 0; i < count; i++) {
GLchar name[80];
GLsizei length;
glGetActiveUniformName(shader, i, 80, &length, name);
printf("\t(location = %u) = \"%.*s\"\n", i, length, name);
}
// try directly geting uniform location
GLint uniform_location = glGetUniformLocation(shader, "test");
printf("\t`test` uniform location = %d\n", uniform_location);
}
int main(int argc, char** argv) {
// initialize window
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 6);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_VISIBLE, GLFW_FALSE);
GLFWwindow* window = glfwCreateWindow(800, 600, "test", NULL, NULL);
glfwMakeContextCurrent(window);
glewInit();
// print driver info
printf("vendor: %s\n", glGetString(GL_VENDOR));
printf("renderer: %s\n", glGetString(GL_RENDERER));
printf("version: %s\n", glGetString(GL_VERSION));
printf("glsl version: %s\n", glGetString(GL_SHADING_LANGUAGE_VERSION));
printf("------------------------------\n");
// test w/ glsl source
GLuint src_vert = load_shader_src(GL_VERTEX_SHADER, vert_src);
GLuint src_frag = load_shader_src(GL_FRAGMENT_SHADER, frag_src);
GLuint src_shader = link_shaders(src_vert, src_frag);
test("GLSL", src_shader);
// test w/ spir-v
GLuint spirv_vert = load_shader_spirv(GL_VERTEX_SHADER, (char*) vert_spirv, sizeof(vert_spirv));
GLuint spirv_frag = load_shader_spirv(GL_FRAGMENT_SHADER, (char*) frag_spirv, sizeof(frag_spirv));
GLuint spirv_shader = link_shaders(spirv_vert, spirv_frag);
test("SPIR-V", spirv_shader);
return 0;
}
makefile
(GNU make)
LDFLAGS += -lglfw
LDFLAGS += -lOpenGL
LDFLAGS += -lGLEW
GLFLAGS += --target-env=opengl
GLFLAGS += -fauto-map-locations
main: main.c
main.c: vert_spirv.h
main.c: frag_spirv.h
main.c: vert_src.h
main.c: frag_src.h
frag_spirv.h: shader.frag
glslc $(GLFLAGS) -mfmt=c -o $@ $<
vert_spirv.h: shader.vert
glslc $(GLFLAGS) -mfmt=c -o $@ $<
frag_src.h: shader.frag
xxd -i < $< > $@
vert_src.h: shader.vert
xxd -i < $< > $@
shader.vert
#version 460 core
layout (location = 0) in vec3 vert;
void main() {
gl_Position = vec4(vert.xyz, 0.001);
}
shader.frag
#version 460 core
uniform float test;
layout(location = 0) out vec4 color;
void main() {
vec2 uv = gl_FragCoord.xy / ivec2(800, 600);
color = vec4(uv.xy, test, 1.0);
}
The uniform name becomes empty when running the SPIR-V shaders on Intel HD Graphics. The test
uniform is still active as shown in the above output. When I hard-code the location of the test
uniform instead of using glGetUniformLocation
, updating its value works fine on both Intel and NVIDIA, but I would like to avoid this approach as I have no control over the location.
I have tried the following, but they all did not have any effect on the output of the MWE:
layout(location = ...)
before the uniform declarationglslc
's -fauto-bind-uniforms
flagglShaderBinary
I would like to keep using the SPIR-V compiler, as it allows me to use the C preprocessor and catch GLSL compiler warnings during compile-time instead of during runtime.
The behavior of this program is according to the specifications (emphasis mine):
Because SPIR-V is an intermediate language, things like names are unnecessary. As such, while SPIR-V does permit you to assign a name to a particular construct, it does not require you to do so. Therefore, any OpenGL introspection query that involves the name of a SPIR-V variable or other construct may not produce reasonable results.
As the program output indicates, the Intel implementation does detect the (active) uniform variable. Only its name cannot reliably be queried. User-defined variables in SPIR-V can only be interfaced with using locations:
Input/output interface matching for user-defined variables in SPIR-V works by matching explicit
Location
s. As such, all variables that are used for input/output interfaces must have a location assigned.