(Demo) Loading SPIR-V Shaders in your OpenGL Demos


GL_ARB_gl_spirv demo with GeeXLab



Six months ago, I added in GeeXLab 0.29.0 the support of SPIR-V shaders in OpenGL (GL_ARB_gl_spirv).

Playing with SPIR-V modules requires the support of OpenGL 4.6. On Windows, all recent drivers from NVIDIA, AMD and Intel support OpenGL 4.6. On Linux, only proprietary drivers exposes OpenGL 4.6…

Let’s see in a simple demo (a RGB triangle is perfect) how to create an OpenGL GPU program from SPIR-V binary modules. The demo is available in the OpenGL 4 demopack here: geexlab-demopack-gl-4x/d13-gl46-arb-gl-spirv/triangle/main.xml

The demo displays a RGB triangle using the following vertex and pixel shaders:

Vertex shader

#version 450

// Uniform buffer
layout (std140, binding = 2) uniform uniforms_t
{ 
  mat4 ViewProjectionMatrix;
  mat4 ModelMatrix;
} ub;

layout (location = 0) in vec4 vposition;
layout (location = 3) in vec4 vcolor;
layout (location = 0) out vec4 v_color;

out gl_PerVertex 
{
  vec4 gl_Position;
};

void main()
{
  vec4 P = ub.ModelMatrix * vposition;
  gl_Position = ub.ViewProjectionMatrix * P;
  v_color = vcolor;
}

Pixel shader

#version 450

layout (location = 0) in vec4 v_color;
layout (location = 0) out vec4 FragColor;

void main()
{
  FragColor = v_color;
}

 
Uniform variables are no longer available. We have to use a GPU buffer to pass data to the GPU program (in our example, we pass data to the vertex shader using the ub GPU buffer).

Before creating the GPU program, we have to generate the SPIR-V modules using the glslangValidator utility available in the Vulkan SDK.

glslangValidator -G vs.vert -o vs.spv
glslangValidator -G ps.frag -o ps.spv

 
We can now create the GPU program from these SPIR-V binary files in our Lua INIT script:

local demo_dir = gh_utils.get_demo_dir()   
local vs = demo_dir .. "spirv/vs.spv"
local ps = demo_dir .. "spirv/ps.spv"
local gs = ""
local tcs = ""
local tes = ""
local cs = ""
vertex_color_prog = gh_gpu_program.create_from_shader_files_gl_spirv(
  "vertex_color_prog", 
  vs, ps, gs, tcs, tes, cs)

 
We have now a valid OpenGL GPU program we can use to render our triangle. But before drawing something, we have to create the famous GPU buffer that will let us dialog with the vertex shader.

local flags = "" -- no flags
local buffer_size = 512 -- in bytes
ub = gh_gpu_buffer.create("UNIFORM", "NONE", buffer_size, flags)

local binding_point_index = 2 -- Binding point 2 specified in the vertex shader.
gh_gpu_buffer.bind_base(ub, binding_point_index)

 
We create an uniform buffer of 512 bytes. We bind it to the binding point 2. Why 2? Just to show we can bind our GPU buffer to any binding point. Usually it’s 0. But 2 or 3 are also valid. The important thing is that the binding point value must match the value defined in the vertex shader:

layout (std140, binding = 2) uniform uniforms_t
{ 
  mat4 ViewProjectionMatrix; // offset: 0
  mat4 ModelMatrix; // offset: 64 bytes
} ub;

 
Once the GPU buffer is created and bound, we can update it using the following functions:

function ShaderBufferUpdateCamera(buf, camera)    
  local offset_bytes = 0
  gh_gpu_buffer.set_matrix4x4(buf, offset_bytes, camera, "camera_view_projection")
end  

function ShaderBufferUpdateObject(buf, obj)    
  local offset_bytes = 0
  offset_bytes = offset_bytes + 64 -- A 4x4 matrix == 64 bytes
  gh_gpu_buffer.set_matrix4x4(buf, offset_bytes, obj, "object_global_transform")
end  

 
Updating a GPU program is not difficult. It’s nothing more than an array of bytes. To put the value 4 in the first byte we can use:

gh_gpu_buffer.map(ub)
local offset = 0
local x = 4.0
gh_gpu_buffer.set_value_1f(ub, offset, x)
gh_gpu_buffer.unmap(ub)

 
For common cases, like updating the view-projection matrix (a camera) or the transformation matrix (an mesh), I added an helper function to make our life easier: gh_gpu_buffer.set_matrix4x4(). So to update the camera view-projection matrix of our GPU buffer, we can call:

offset_bytes = 0 -- ViewProjectionMatrix starts at offset 0
gh_gpu_buffer.set_matrix4x4(gpubuf, offset_bytes, camera, "camera_view_projection")

 
For a mesh (here our triangle):

offset_bytes = 64 -- ModelMatrix starts at offset 64
gh_gpu_buffer.set_matrix4x4(gpubuf, offset_bytes, triangle, "object_global_transform")

 
The rest of the demo is usual. The FRAME script is understandable without too much problem:

local elapsed_time = gh_utils.get_elapsed_time()

gh_camera.bind(camera)

gh_renderer.clear_color_depth_buffers(0.2, 0.2, 0.2, 1.0, 1.0)
gh_renderer.set_depth_test_state(1)

-- Rotate the triangle
gh_object.set_euler_angles(triangle, 0, elapsed_time*33.0, 0)

-- Update the GPU buffer
gh_gpu_buffer.map(ub)
ShaderBufferUpdateObject(ub, triangle)    
gh_gpu_buffer.unmap(ub)

-- Bind the GPU program and draw the triangle.
gh_gpu_program.bind(vertex_color_prog)
gh_object.render(triangle)

GL_ARB_gl_spirv demo with GeeXLab

 
The demopack has a second demo that uses SPIR-V modules in OpenGL. This time the demo displays a… textured quad!!!!

GL_ARB_gl_spirv demo with GeeXLab





Leave a Comment

Your email address will not be published. Required fields are marked *