I’m considering the idea of a higher-but-still-pretty-low-level library to reduce the time and cost of Vulkan development. The basic problem, as I see it, is this:
- Application code is complex
- Vulkan code is complex
- Trying to interface these two things directly is extremely time-consuming and results in a large amount of complicated and brittle code.
I believe an intermediate graphics library, perhaps modeled after OpenGL’s API style, would greatly improve programmer efficiency with no loss of performance and allow more agile application development. WebGPU and ANGLE are proof this approach can work well, but unfortunately those libraries are both “lowest common denominator” abstraction layers that each offer less functionality than even OpenGL 4.6.
I am curious to hear if other developers around here recognize a need for reducing development time and costs with something like this? I have a lot of experience with Khronos graphics APIs and could probably write the whole thing myself, but would prefer to collaborate with others if there is a general need for this.
My general concept right now is like a cleaned-up version of OpenGL 4.6, with the following additions:
- glInstance objects
- glPipeline objects
- glCommandBuffer objects (probably this is how multithreading would work, although I would not force use of them)
- Raytracing support
- Lots of additional error checking in debug builds
I think it would be possible to make an example of a spinning cube with raytraced shadows in less than 150 lines of code, as well as more complex applications that would be indistinguishable from a pure Vulkan app.
Here is some concept code I wrote down:
void RunExample(HWND hwnd, void* texturedata, int texsize, void* vertshader, GLSizei vertshadersize, void* fragshader, GLSizei fragshadersize)
{
// Initialize instance
GLinstance inst = glCreateInstance();
// Select GPU
GLuint count;
GLDevice *device;
glGetDevices(inst, &count, &device);
GLDeviceSelectInfo dinfo;// constructor fills in default values
dinfo.extensions.bindlesstextures.enabled = true;// enable an extension
glSelectDevice(inst, deviceid[0], dinfo);// discrete GPU always comes first
printf(glGetDeviceString(deviceid[0], GL_VENDOR));
printf(glGetDeviceString(deviceid[0], GL_RENDERER));
// Create framebuffer
GLFramebuffer framebuffer = glCreateFramebuffer(inst, hwnd);
// Create vertex shader module
GLShader vertshader = glCreateShader(inst, GL_VERTEX_SHADER);
glShaderBinary(1, &vertshader, GL_SHADER_BINARY_FORMAT_SPIR_V, vertshader, vertshadersize);
glSpecializeShader(vertshader, "main", 0, 0, 0);
// Create fragment shader module
GLShader fragshader = glCreateShader(inst, GL_FRAGMENT_SHADER);
glShaderBinary(1, &fragshader, GL_SHADER_BINARY_FORMAT_SPIR_V, fragshader, fragshadersize);
glSpecializeShader(fragshader, "main", 0, 0, 0);
// Create shader program
GLProgram program = glCreateProgram(inst);
glAttachShader(program, vertshader);
glAttachShader(program, fragshader);
glLinkProgram(program);
glValidateProgram(program);
// Create vertex buffer
const int vertexstride = 5 * 4;// position x3 + texcoords x2
GLVertexBuffer vertbuffer = glCreateVertexBuffer(inst);
float vertexdata[24 * 5] = { 0.0f, 0.0f, 0.0f };
glVertexBufferSetData(vertbuffer, &vertexdata[0], 24 * vertexstride);
// Create indice buffer
GLElementBuffer indicebuffer = glCreateElementBuffer(inst);
unsigned short indices[36] = { 0, 1, 2, 2, 3, 0 };
glElementBufferSetData(indicebuffer, &indices[0], 36 * 2, GL_UINT16);
// Create texture
GLTexture texture = glCreateTexture(inst);
glTextureStorage2D(texture, 1, GL_R8G8B8A8_UNORM, texsize, texsize);
glTextureSubImage2D(texture, 0, 0, 0, texsize, texsize, GL_R8G8B8A8_UNORM, texturedata);
glTextureParameteri(texture, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTextureParameteri(texture, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
// Create pipeline - this is just a container for draw settings, not a 1:1 mapping to Vulkan pipelines
GLPipeline pipeline = glCreatePipeline(inst);
// Set pipeline attributes
glSetPipelineAttribute(pipeline, 0, 3, GL_FLOAT, GL_FALSE, vertexstride, vertexbuffer);
glSetPipelineElementBuffer(pipeline, 0, 3, GL_FLOAT, GL_FALSE, vertexstride, indicebuffer);
while (true)
{
//Clear the frame buffer
GLfloat cleardepth = 1.0f;
GLfloat clearcolor[4] = { 0.0f, 0.0f, 0.0f, 1.0f };
glClearFramebufferfv(framebuffer, GL_DEPTH, 0, &cleardepth);
glClearFramebufferfv(framebuffer, GL_COLOR, 0, &clearcolor[0]);
// Prepare the pipeline
glSetPipelineFramebuffer(pipeline, framebuffer);
glSetPipelineShader(pipeline, program);
glBindPipelineTextures(pipeline, 1, &texture);
glPipelineParameteri(pipeline, GL_BLEND, GL_FALSE);// instead of glDisable(GL_BLEND)
// Set vertex buffers for position and texcoords
glPipelineVertexAttrib(pipeline, 0, 3, GL_FLOAT, GL_FALSE, vertexstride, 0, vertexbuffer);
glPipelineVertexAttrib(pipeline, 1, 2, GL_FLOAT, GL_FALSE, vertexstride, 12, vertexbuffer);
glEnablePipelineVertexAttribArray(pipeline, 0);
glEnablePipelineVertexAttribArray(pipeline, 1);
// Enable the indice buffer
glEnablePipelineElementArray(pipeline);
// Draw the mesh
glDrawElements(pipeline, GL_TRIANGLES, 36, GL_UINT16, 0);
// Pipeline cleanup
glDisablePipelineVertexArrayAttrib(pipeline, 0);
glDisablePipelineElementArray(pipeline);
// Swap buffers
glSwapBuffer(framebuffer);
}
}
Please let me know if this idea interests you.