0
votes

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:

  1. Shaders are compiled and linked. Vertex shader takes a single position attribute
  2. Vertex position array is allocated (but position data not uploaded yet)
  3. Index array is created and index data uploaded

Once per-frame:

  1. Program and vertex array object are used/bound
  2. Vertex position data are updated, uploaded, and the buffer bound to a buffer binding point (index 0)
  3. The attribute format is set and linked to that binding index
  4. Index buffer is linked to the vao using glVertexArrayElementBuffer (and not binding to GL_ELEMENT_ARRAY_BUFFER)
  5. 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_);
}
1
I should note that the glClearDepth and glClearColor (etc) are actually done in the main part of the app but I included them here to show I haven't omitted them entirely)Smileyofoz
The value for glClearDepth has to be in [0.0, 1.0]. By default it is 1,0 and that is fine.Rabbid76

1 Answers

3
votes

Since you don't use any projection or transformation matrix, the vertex coordinates have to be specified in normalized device space. The normalized device space is a cube with the left, bottom, near of (-1, -1, -1) and right, top, far of (1, 1, 1).
All the geometry which is not inside this viewing volume, is clipped.

Your geometry is clipped, because all the z coordinates are 10.0f.
Change the z coordiantes (e.g. 0.0f) to solve the issue.

Note glClearDepth does not specify the far plane of the viewing volume. It defines the value which is written to the depth buffer when it is cleared by glClear(GL_DEPTH_BUFFER_BIT) and has to be in range [0.0, 1.0]. By default it is 1.0.
(See glDepthRange and Depth Test)