I've been dusting off an app that uses the "old style" OpenGL 1.2 api and want to upgrade the app to use OpenGL 4.5, and in particular, to use bindless state management using direct state access because it seems - in theory - that it will make it easier to manage rendered object state with less accidental over-tread between objects.
I'm trying to get an absolute bare-bones DSA-only implementation of a triangle but the triangle is not showing up and upon using glUseProgram(0) the fixed functionality pipeline does not "return to service", so the rest of the legacy objects disappear as well.
The main app creates two instances of the triangle - each gets its own vertex array object, program object, index buffer, and vertex position buffer. As it works out the program objects have identical shader code but part of the exercise is to demonstrate that I can change program objects around in a way that other rendered objects in the app are unaffected. The two triangle instances are distinct only in that they receive a different value at instantiation time that is used to offset the triangle in X, so two triangles should show up side by side. During the render cycle a cyclic value moves the triangle up and down in Y. The viewport is set in the main part of the app using glViewport and rendering is done to the default framebuffer.
At initialisation time:
- Shaders are compiled and linked. Vertex shader takes a single position attribute
- Vertex position array is allocated (but position data not uploaded yet)
- Index array is created and index data uploaded
Once per-frame:
- Program and vertex array object are used/bound
- Vertex position data are updated, uploaded, and the buffer bound to a buffer binding point (index 0)
- The attribute format is set and linked to that binding index
- Index buffer is linked to the vao using glVertexArrayElementBuffer (and not binding to GL_ELEMENT_ARRAY_BUFFER)
- Draw command is issued
At no point do I use glBindBuffer - the point being that I'm trying to use DSA only, and my [possibly mistaken?] belief is that by establishing the vao while unbound using DSA functions only and then binding that vao using glBindVertexArray, all the state will become "current". Note that this intentionally naive sample does not use a projection matrix or model-view matrix - it aims to directly create the triangles in the middle of the screen, much like any "hello-world" style first triangle app.
I've got a verified 4.5 GL compatibility context created through WGL, the shaders compile, and the program links. I've aggressively coded defensively and check glGetError after literally every call (although I've taken many of those out of the code here for readability).
The GL version reported is "4.5.0 NVIDIA 382.05" and GLSL version is "4.50 NVIDIA".
I've tried binding the index buffer to the GL_ELEMENT_ARRAY_BUFFER buffer binding point to see if that makes a difference and it does not work. I've even tried binding the vertex position buffer to GL_ARRAY_BUFFER binding point, with (not unexpectedly) no result.
Either I'm missing something simple, or my concept of how DSA works is over-glorified and wrong. What can I try next?
GlslTriangle.h:
#pragma once
#include <glad.h>
typedef unsigned short uint16;
typedef unsigned int uint32;
class GlslTriangle
{
private:
int instance_{ 0 };
GLuint prg_{ 0 };
GLuint vao_{ 0 };
GLuint vtx_{ 0 };
GLuint idx_{ 0 };
uint32 frameCount_{ 0 };
GLuint CompileVtxShader();
GLuint CompileFrgShader();
void LinkProgram(GLuint v, GLuint f);
void CreateBuffers();
void ClearErr();
void BreakIfErr();
public:
GlslTriangle(int inst) :
instance_(inst)
{}
void OneTimeInit();
void Generate();
void Render();
void Deallocate();
};
GlglTriangle.cpp:
#include <stdafx.h>
using std::string;
#include <Geometry.h> //Point3f
#include <GlslTriangle.h>
void GlslTriangle::BreakIfErr()
{
GLenum err;
if ((err = glGetError()) != GL_NO_ERROR)
assert(false);
}
void GlslTriangle::ClearErr()
{
GLenum err;
while ((err = glGetError()) != GL_NO_ERROR)
{
}
}
GLuint GlslTriangle::CompileVtxShader()
{
auto v = glCreateShader(GL_VERTEX_SHADER);
BreakIfErr();
string vsrc =
"#version 450 core \n"\
"layout(location = 0) in vec3 vertexPosition; \n"\
"void main() \n"\
"{ \n"\
"gl_Position.xyz = vertexPosition; \n"\
"gl_Position.w = 1.0; \n"\
"} \n";
auto vsrca = vsrc.c_str();
GLint vsrcl = vsrc.length();
glShaderSource(v, 1, &vsrca, &vsrcl);
BreakIfErr();
glCompileShader(v);
BreakIfErr();
GLint vstatus{ 0 };
glGetShaderiv(v, GL_COMPILE_STATUS, &vstatus);
assert(vstatus);
return v;
}
GLuint GlslTriangle::CompileFrgShader()
{
auto f = glCreateShader(GL_FRAGMENT_SHADER);
string fsrc =
"#version 450 core \n" \
"out vec3 color; \n "\
"void main() \n" \
"{ \n"\
"color = vec3(0.5, 0.5, 1.0); \n"\
"} \n";
auto fsrca = fsrc.c_str();
GLint fsrcl = fsrc.length();
glShaderSource(f, 1, &fsrca, &fsrcl);
BreakIfErr();
glCompileShader(f);
BreakIfErr();
GLint fstatus{ 0 };
glGetShaderiv(f, GL_COMPILE_STATUS, &fstatus);
assert(fstatus);
return f;
}
void GlslTriangle::LinkProgram(GLuint v, GLuint f)
{
glAttachShader(prg_, v);
glAttachShader(prg_, f);
glLinkProgram(prg_);
BreakIfErr();
GLint lstatus{ 0 };
glGetProgramiv(prg_, GL_LINK_STATUS, &lstatus);
assert(lstatus);
glDetachShader(prg_, v);
glDetachShader(prg_, f);
glDeleteShader(v);
glDeleteShader(f);
}
void GlslTriangle::CreateBuffers()
{
//Allocate space for 3 points - we'll populate data later
glCreateBuffers(1, &vtx_);
glNamedBufferStorage(vtx_, 3 * sizeof(Point3f), nullptr, GL_DYNAMIC_STORAGE_BIT);
BreakIfErr();
//Allocate space for 3 indices
glCreateBuffers(1, &idx_);
uint16 i[3];
i[0] = 0;
i[1] = 1;
i[2] = 2;
//Upload index data
glNamedBufferStorage(idx_, 3 * sizeof(uint16), i, GL_DYNAMIC_STORAGE_BIT);
BreakIfErr();
}
void GlslTriangle::OneTimeInit()
{
ClearErr();
glCreateVertexArrays(1, &vao_);
prg_ = glCreateProgram();
BreakIfErr();
auto v = CompileVtxShader();
auto f = CompileFrgShader();
LinkProgram(v, f);
CreateBuffers();
}
void GlslTriangle::Generate()
{
ClearErr();
//Provide a cyclic value that will push the triangle up and down in Y
float cycle{ 1000.0f };
float offset = 5 * sin(2*PI *(float)frameCount_ / cycle);
//The instance parameter is provided at instantiation of "this" and
//just offsets the triangle - with 2 instances of "this" we should see
//two triangles at different positions in X
Point3f data[3];
data[0] = { -1.0f + (float)instance_, 0.0f + offset, 10.0f};
data[1] = { 0.0f + (float)instance_, 1.0f + offset, 10.0f};
data[2] = { 1.0f + (float)instance_, 0.0f + offset, 10.0f};
GLintptr bo{ 0 }; //buffer offset
glNamedBufferSubData(vtx_, bo, 3 * sizeof(Point3f), data);
BreakIfErr();
++frameCount_;
frameCount_ = frameCount_ == cycle ? 0 : frameCount_;
}
void GlslTriangle::Render()
{
GL::ClearErr();
GLfloat skyColor[4] = { 0.75f, 0.75f, 1.0f, 1.0f };
glClearColor(skyColor[0], skyColor[1], skyColor[2], skyColor[3]);
glClearDepth(100.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glUseProgram(prg_);
glBindVertexArray(vao_);
glVertexArrayElementBuffer(vao_, idx_);
GLuint a{ 0 }; //attribute idx
GLuint b{ 0 }; //binding idx
GLintptr offset{ 0 }; //offset in buffer
GLint c{ 3 }; //components
GLuint r{ 0 }; //relative offset in buffer element
glVertexArrayAttribFormat(vao_, a, c, GL_FLOAT, GL_FALSE, r);
glVertexArrayVertexBuffer(vao_, b, vtx_, offset, sizeof(Point3f));
glVertexArrayAttribBinding(vao_, a, b);
glEnableVertexArrayAttrib(vao_, a);
GLsizei e{ 3 }; //count of elements
glDrawElements(GL_TRIANGLES, e, GL_UNSIGNED_SHORT, nullptr);
BreakIfErr();
glUseProgram(0);
}
void GlslTriangle::Deallocate()
{
glDeleteProgram(prg_);
glDeleteVertexArrays(1, &vao_);
glDeleteBuffers(1, &vtx_);
glDeleteBuffers(1, &idx_);
}
glClearDepth
has to be in [0.0, 1.0]. By default it is 1,0 and that is fine. – Rabbid76