I have some existing OpenGL code that I am porting to Vulkan. The question is somewhat relevant to both OpenGL and Vulkan.
I am rendering some primitives using textures, some single channel, others RGBA, as well as flat colored primitives with per-vertex colors. The GL code that I am porting frequently switches shader programs with glUseProgram
.
In Vulkan, using two distinct sets of shaders requires two pipelines created with vkCreateGraphicsPipelines
and later bound with vkCmdBindPipeline
plus vkCmdBindDescriptorSets
, thus changing shader programs equates program switches with graphics pipeline reconfiguration.
I am wondering whether it is efficient to switch to a single shader program and hence a single graphics pipeline with a vertex layout that contains texture coordinates, a color, and texture sampler. When I want to draw flat colored objects, the code would set the color in the vertex and bind a 1x1 white sampler and when I want to render images the code can bind a full image. I currently render the following:
- flat color using color from vertex
- alpha from sampler and color from vertex
- RGBA from sampler and alpha from vertex
The question is from a hardware, driver/performance perspective, is it more efficient to use a single pipeline and descriptor layout and only performing uniform updates and have a slightly larger Vertex, or is switching pipelines low enough cost to warrant two or three pipelines and two of three sets of shaders and different vertex formats. i.e. where is the best balance between these concerns?
- three sets of shaders, three small vertex structs; program, uniform, and sampler switches
- one set of shaders, one slightly larger vertex struct and just uniform, and sampler switches
We could benchmark this but wonder if someone already has data to compare these approaches. I am aware of Ubershaders however this is hardly an Ubershader (see below) rather concerns any data on the ratio of program switch cost overhead versus the overhead of a 1x1 NOP texture lookups and texture color versus switching to a shader with no texture lookups.
With multiple shaders, the required program switches are likely to be in the order of 100's or possibly 1000's because of transparent content that requires a specific rendering order. In my case, it is for a mostly 2D app with a mix of line/block/bezier art, fonts using single alpha channel texture atlas plus vertex color and finally, RGBA images, typically just using the vertex alpha value and all currently using an orthographic projection with no lighting.
Ideally, I would like data on pipeline/program switch cost versus descriptor set switch cost.
For the shared program model, I am thinking about using the following:
This is the vertex struct:
struct Vertex {
glm::vec3 vertex;
glm::vec2 texcoord;
uint32_t color;
};
and this is the vertex shader:
#version 450
layout (location = 0) in vec4 vcolor;
layout (location = 1) in vec2 vtexcoord;
layout (binding = 1) uniform sampler2D tex;
layout (location = 0) out vec4 outColor;
void main()
{
outColor = vcolor * texture(tex, vtexcoord);
}
and this is the fragment shader:
#version 450
layout (location = 0) in vec3 vertex;
layout (location = 1) in vec2 texcoord;
layout (location = 2) in vec4 color;
layout (binding = 0) uniform UBO
{
mat4 modelViewProjection;
} ubo;
layout (location = 0) out vec4 vcolor;
layout (location = 1) out vec2 vtexcoord;
out gl_PerVertex
{
vec4 gl_Position;
};
void main()
{
vcolor = color;
vtexcoord = texcoord;
gl_Position = ubo.modelViewProjection * vec4(vertex.xyz, 1.0);
}
Note: as pointed out in the comments, swizzle masks in VkImageViewCreateInfo
components
field can be used to create VkImageView
that translates the color and alpha channels instead of tieing shader code to the number of image channels in the sampler.
To broadcast the first color channel to all elements in OpenGL:
glTexParameteriv(GL_TEXTURE_2D, GL_TEXTURE_SWIZZLE_RGBA,
(GLint[]) { GL_RED, GL_RED, GL_RED, GL_RED } );
and for Vulkan, use the VkImageViewCreateInfo
components
field:
VkImageViewCreateInfo imageViewInfo;
imageViewInfo.components = {
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_R,
VK_COMPONENT_SWIZZLE_R
};
VkImageView
, not of the image itself. $\endgroup$