Search code examples
c++openglglslsdlglew

Can't figure out how to sample from a 1D sampler in a 2D fragment shader in OpenGL


For an OpenGL project I'm working on in C++, I'm trying to encode distance information into a 1D buffer, and I would really like to use unsigned integers for that. I created a debug step to export the contents of the buffer to a file, which shows that it is being written to successfully, but I can't read the contents of the framebuffer texture from the fragment shader. The shaders themselves are all close to the top of the file.

For this test program, my goal is to draw a simple gradient (done with UVs) to the 1D framebuffer, then read it from a fragment shader and drawing it to the screen. To clarify, I know how to make a basic UV gradient. That isn't my actual goal here, my goal is to write to and then read from a 1D buffer of unsigned integers.*

This is the actual output of the program: Output This is a mockup of what I'm trying to replicate: Mockup This is a screenshot of the contents of the "buffer.bin" file I write to after rendering: buffer.bin

Here's a chunky 345-line file. Sorry for the lack of readability and a lot of redundancy, to reduce the potential sources of error I created a second project. However, I need to stress this: the code posted here is the exact same code that I have been unable to get working, and I have not hidden anything for the sake of secrecy. It should compile just fine on Linux or Windows (tested under gcc) given that SDL and GLEW are installed.

main.cpp (only one file)

#include <iostream>
#include <SDL2/SDL.h>
#include <GL/glew.h>
#include <SDL_opengl.h>
#include <fstream>
#include <iostream>
#include <filesystem>
#include <initializer_list>
#include <exception>
#include <stdexcept>
#include <type_traits>
#include <SDL2/SDL.h>
#include <SDL2/SDL_main.h>
#include <GL/glew.h>

typedef union SizeVec2 {
    struct {
        size_t x = 0, y = 0;
    };
    struct { size_t w, h; };
} SizeVec2;

using namespace std;

SDL_Window* window = nullptr;
SDL_GLContext context;
bool isRunning = true;
SizeVec2 windowSize = { 640, 640 };

bool init() {
    if (SDL_Init(SDL_INIT_EVERYTHING) < 0 ) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to initialize SDL: %s", SDL_GetError());
        return false;
    }
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 4);
    SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
    window = SDL_CreateWindow("OpenGL Test", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, (int)windowSize.w, (int)windowSize.h, SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL);
    if (window == nullptr) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to create window: %s", SDL_GetError());
        return false;
    }

    context = SDL_GL_CreateContext(window);
    if (context == nullptr) {
        SDL_LogCritical(SDL_LOG_CATEGORY_APPLICATION, "Failed to create OpenGL context: %s", SDL_GetError());
        return false;
    }

    GLenum ret = glewInit();
    if (ret) {
        SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "glewInit() = %u", ret);
        return false;
    }

    return true;
}

const char* vertShaderSrc = "#version 330 core\n"
                            "layout (location = 0) in vec2 aPosition;\n"
                            "layout (location = 1) in vec2 aTexCoord;\n"
                            "out vec2 texCoord;\n"
                            "\n"
                            "void main() {\n"
                            "    texCoord = aTexCoord;\n"
                            "    gl_Position = vec4(aPosition, 0.0, 1.0);\n"
                            "}";

// ignore this
const char* fragShaderSrc_shadowPass1 = "#version 420 core\n"
                                        "in vec2 texCoord;\n"
                                        "out float fragColor;\n"
                                        "uniform sampler2D occludersTexture;\n";

const char* fragShaderSrc_shadowPass2 = "#version 420 core\n"

                                        "in vec2 texCoord;\n"
                                        "layout(location = 0) out uint fragDistance;\n"
                                        // NOTE: if it weren't for this being a test, this would be taking input from pass 1
                                        "uniform sampler2D polarOccludersTexture;\n"

                                        "void main() {\n"
                                        "    fragDistance = uint(texCoord.x * 256.0);\n"
                                        "}";

const char* fragShaderSrc_shadowPass3 = "#version 420 core\n"

                                        "in vec2 texCoord;\n"
                                        "out vec4 fragColor;\n"
                                        "uniform usampler1D shadowMap;\n"

                                        "void main() {\n"
                                        //                                        "    int shadowMapWidth = textureSize(shadowMap, 0);\n"
                                        "    uvec4 samp = texture(shadowMap, texCoord.x);\n"
                                        "    uint msamp = max(samp.r, max(samp.g, max(samp.b, samp.a)));"
                                        "    fragColor = vec4(float(samp.r) / 256.0, 0.1, 0.0, 1.0);\n"
                                        //                                        "    fragColor = vec4(texCoord, 0.0, 1.0);\n"
                                        //                                        "    fragColor = vec4(1.0, 0.0, 0.0, 1.0);\n"
                                        "}";

GLuint vertexShader = 0, fragShader_shadowPass1 = 0, fragShader_shadowPass2 = 0, fragShader_shadowPass3 = 0;
GLuint prog_shadowPass1 = 0, prog_shadowPass2 = 0, prog_shadowPass3 = 0;

GLuint fbo_shadowPass[] = { 0, 0, 0 };
GLuint fbt_shadowPass[] = { 0, 0, 0 };

GLfloat screenVertices[16] = {
    -1.f, 1.f,    0.0f, 0.0f, // tl
    1.f, 1.f,     1.0f, 0.0f, // tr
    -1.f, -1.f,   0.0f, 1.0f, // bl
    1.f, -1.f,    1.0f, 1.0f, // br
};

GLuint screenIndices[] = {
    0, 1, 2, // tl
    3, 2, 1, // br
};

GLuint screenVao = 0, screenVbo = 0, screenEbo = 0;

void tick() {

    SDL_Event ev;
    while (SDL_PollEvent(&ev)) {
        switch (ev.type) {
            case SDL_QUIT: {
                isRunning = false;
                return;
                break;
            }
        }
    }

    glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo_shadowPass[1]);
    glDisable(GL_BLEND);
    GLenum a = GL_COLOR_ATTACHMENT0;
    glDrawBuffers(1, &a);
    glClearColor(0.0, 0.0, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, fbt_shadowPass[0]);
    glUseProgram(prog_shadowPass2);
    glUniform1i(glGetUniformLocation(prog_shadowPass2, "polarOccludersTexture"), 1);
    glBindVertexArray(screenVao);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

    void* buf = malloc(sizeof(unsigned int) * windowSize.w);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_shadowPass[1]);
//    glReadPixels(0, 0, 640, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, buf);
//    glReadPixels(0, 0, (GLsizei)windowSize.w, 1, GL_RED, GL_UNSIGNED_INT, buf);
    glReadPixels(0, 0, (GLsizei)windowSize.w, 1, GL_RED_INTEGER, GL_UNSIGNED_INT, buf);
    ofstream writeout{"buffer.bin"};
    writeout.write((const char *)(buf), (streamsize)(sizeof(unsigned int) * windowSize.w));
    writeout.flush();
    writeout.close();
    free(buf);
    SDL_GL_SwapWindow(window);
    GLenum e = glGetError();
    if (e != 0) SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "%x %s", e, glewGetErrorString(e));

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo_shadowPass[1]);
    glClearColor(0.0, 0.0, 0.5, 0.0);
    glClear(GL_COLOR_BUFFER_BIT);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_1D, fbt_shadowPass[1]);
//    glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
    glUniform1i(glGetUniformLocation(prog_shadowPass3, "shadowMap"), 2);
    glUseProgram(prog_shadowPass3);
    glBindVertexArray(screenVao);
    glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, nullptr);

    SDL_GL_SwapWindow(window);
    GLenum err = glGetError();
    if (err != 0) SDL_LogError(SDL_LOG_CATEGORY_VIDEO, "%x %s", err, glewGetErrorString(err));

    SDL_Delay(5000);
    exit(0);
}

int main(int argc, char* argv[]) {
    SDL_Log("running");
    if (!init()) return 1;
#ifdef NDEBUG
    SDL_LogSetAllPriority(SDL_LOG_PRIORITY_DEBUG);
#endif
    SDL_Log("initialized");

    size_t vertexCount = 16;
    size_t verticesSize = vertexCount * sizeof(GLfloat);
    size_t indexCount = 6;
    size_t indexesSize = indexCount * sizeof(GLuint);
    glGenVertexArrays(1, &screenVao);
    glGenBuffers(1, &screenVbo);
    glGenBuffers(1, &screenEbo);
    glBindVertexArray(screenVao);
    glBindBuffer(GL_ARRAY_BUFFER, screenVbo);
    glBufferData(GL_ARRAY_BUFFER, (GLsizeiptr)verticesSize, screenVertices, GL_STATIC_DRAW);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, screenEbo);
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, (GLsizeiptr)indexesSize, screenIndices, GL_STATIC_DRAW);
    glBindBuffer(GL_ARRAY_BUFFER, screenVbo);
    // specify vertex positions
    glVertexAttribPointer(
        0, // index
        2, // size
        GL_FLOAT, // type
        GL_FALSE, // normalized
        4 * sizeof(float), // stride
        nullptr // pointer
    );
    glEnableVertexAttribArray(0);
    // specify UV attribute
    glVertexAttribPointer(
        1, // index
        2, // size
        GL_FLOAT, // type
        GL_TRUE, // normalized
        4 * sizeof(float), // stride
        (void*)(2 * sizeof(float)) // pointer
    );
    glEnableVertexAttribArray(1);

    glGenFramebuffers(3, fbo_shadowPass);
    glGenTextures(3, fbt_shadowPass);

    glBindFramebuffer(GL_FRAMEBUFFER, fbo_shadowPass[1]);
    glBindTexture(GL_TEXTURE_1D,  fbt_shadowPass[1]);
    glTexImage1D(GL_TEXTURE_1D, 0, GL_R32UI, (GLsizei)windowSize.w, 0, GL_RED_INTEGER, GL_UNSIGNED_INT, nullptr);
    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glFramebufferTexture1D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_1D, fbt_shadowPass[1], 0);
    if(glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) SDL_Log("Framebuffer 2 incomplete!");

    glBindFramebuffer(GL_FRAMEBUFFER, 0);

    vertexShader = glCreateShader(GL_VERTEX_SHADER);
    fragShader_shadowPass1 = glCreateShader(GL_FRAGMENT_SHADER);
    fragShader_shadowPass2 = glCreateShader(GL_FRAGMENT_SHADER);
    fragShader_shadowPass3 = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(vertexShader,           1, (const GLchar* const*)&vertShaderSrc,             nullptr);
    glCompileShader(vertexShader);
    glShaderSource(fragShader_shadowPass1, 1, (const GLchar* const*)&fragShaderSrc_shadowPass1, nullptr);
    glCompileShader(fragShader_shadowPass1);
    glShaderSource(fragShader_shadowPass2, 1, (const GLchar* const*)&fragShaderSrc_shadowPass2, nullptr);
    glCompileShader(fragShader_shadowPass2);
    glShaderSource(fragShader_shadowPass3, 1, (const GLchar* const*)&fragShaderSrc_shadowPass3, nullptr);
    glCompileShader(fragShader_shadowPass3);
    // check err
    prog_shadowPass1 = glCreateProgram();
    glAttachShader(prog_shadowPass1, vertexShader);
    glAttachShader(prog_shadowPass1, fragShader_shadowPass1);
    glLinkProgram(prog_shadowPass1);
    prog_shadowPass2 = glCreateProgram();
    glAttachShader(prog_shadowPass2, vertexShader);
    glAttachShader(prog_shadowPass2, fragShader_shadowPass2);
    glLinkProgram(prog_shadowPass2);
    prog_shadowPass3 = glCreateProgram();
    glAttachShader(prog_shadowPass3, vertexShader);
    glAttachShader(prog_shadowPass3, fragShader_shadowPass3);
    glLinkProgram(prog_shadowPass3);

    GLuint shaders[4] = { vertexShader, fragShader_shadowPass1, fragShader_shadowPass2,fragShader_shadowPass3};
    for (int i = 0; i < 4; i++) {
        GLint compiled = 0;
        glGetShaderiv(shaders[i], GL_COMPILE_STATUS, &compiled);
        if (!compiled) {
            GLint infoLen = 0;
            glGetShaderiv(shaders[i], GL_INFO_LOG_LENGTH, &infoLen);
            if (infoLen > 1) {
                char *infoLog = (char *) malloc(infoLen * sizeof(char));
                glGetShaderInfoLog(shaders[i], infoLen, nullptr, infoLog);
                SDL_Log("Error compiling shader %i: %s", i, infoLog);
                ::free(infoLog);
            } else SDL_Log("Error compiling shader %i (no further information)", i);
            return 1;
        }
    }

    GLuint progs[3] = { prog_shadowPass1, prog_shadowPass2, prog_shadowPass3};

    for (int i = 0; i < 3; i++) {
        GLint linked;
        glGetProgramiv(progs[i], GL_LINK_STATUS, &linked);
        if (!linked) {
            GLint infoLen = 0;
            glGetProgramiv(progs[i], GL_INFO_LOG_LENGTH, &infoLen);
            if (infoLen > 1) {
                char *infoLog = (char *) malloc(sizeof(char) * infoLen);
                glGetProgramInfoLog(progs[i], infoLen, nullptr, infoLog);
                SDL_Log("Error linking program %i: %s", i, infoLog);
                ::free(infoLog);
            }
            SDL_Log("Error linking program %i (no further information)", i);
            glDeleteProgram(progs[i]);
            return 1;
        }
    }

    while (isRunning) tick();

    SDL_Log("shutting down");

    SDL_Quit();

    SDL_Log("goodnight");
    return 0;
}

Solution

  • The culprit is

    glTexParameteri(GL_TEXTURE_1D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
    

    Because you only generated the base level of your 1D texture, and for some reason the "nearest mipmap" is not level 0 when rendering, the shader tries to sample unallocated data, giving you values of 0 everytime.

    Either generate mipmaps by hand or with glGenerateMipmap - OpenGL 4 Reference Pages, OR change your min filter to GL_NEAREST.