Using 3D shaders on a SDL_texture

Hi,

I’m having some issues using 3D shaders on an SDL_texture.
Everything is fine except for the z-buffer. When I enable glEnable(GL_DEPTH_TEST); nothing is rendered to the texture (everything is black).

Here is the example code:

#include <SDL.h>
#include "SDL_opengl.h"
#include <iostream>
#include <chrono>
#include <thread>

#define WIDTH 640
#define HEIGHT 800

const std::string _vert = R"(
#version 320 es
layout(location = 0) in vec3 vertPos;

out vec3 theColor;

uniform float angleX;
uniform float angleY;
const float pi = 3.14159265359;

mat3 rotateX(float angle)
{
    angle *= pi / 180.0;

    return mat3(
        1.0, 0.0, 0.0,
        0.0, cos(angle), -sin(angle),
        0.0, sin(angle), cos(angle)
    );
}

mat3 rotateY(float angle)
{
    angle *= pi / 180.0;

    return mat3(
        cos(angle), 0.0, sin(angle),
        0.0, 1.0, 0.0,
        -sin(angle), 0.0, cos(angle)
    );
}

mat3 rotateZ(float angle)
{
    angle *= pi / 180.0;

    return mat3(
        cos(angle), -sin(angle), 0.0,
        sin(angle), cos(angle), 0.0,
        0.0, 0.0, 1.0
    );
}

void main()
{
    theColor = vertPos;
    gl_Position = vec4(rotateX(angleX) * rotateY(angleY) * vertPos, 1.0);
}
)";

const std::string _frag = R"(
#version 320 es
precision mediump float;

in vec3 theColor;
out vec4 fragColor;

void main()
{
    fragColor = vec4(theColor, 1.0);
}
)";

const GLfloat _vertices[] = {
        
        //Front face
        -0.5f, -0.5f, 0.5f, // A
        0.5f, -0.5f, 0.5f,  // B
        -0.5f, 0.5f, 0.5f,  // C
        0.5f, -0.5f, 0.5f,  // B
        0.5f, 0.5f, 0.5f,   // D
        -0.5f, 0.5f, 0.5f,  // C
        
        //Bottom face
        -0.5f, -0.5f, 0.5f,  // A
        -0.5f, -0.5f, -0.5f, // E
        0.5f, -0.5f, 0.5f,   // B
        -0.5f, -0.5f, -0.5f, // E
        0.5f, -0.5f, -0.5f,  // F
        0.5f, -0.5f, 0.5f,   // B
        
        //Left Face
        -0.5f, -0.5f, 0.5f,  // A
        -0.5f, 0.5f, 0.5f,   // C
        -0.5f, 0.5f, -0.5f,  // G
        -0.5f, -0.5f, 0.5f,  // A
        -0.5f, -0.5f, -0.5f, // E
        -0.5f, 0.5f, -0.5f,  // G
        
        //Back Face
        -0.5f, -0.5f, -0.5f, // E
        -0.5f, 0.5f, -0.5f,  // G
        0.5f, 0.5f, -0.5f,   // H
        -0.5f, -0.5f, -0.5f, // E
        0.5f, -0.5f, -0.5f,  // F
        0.5f, 0.5f, -0.5f,   // H
        
        //Right Face
        0.5f, 0.5f, 0.5f,   // D
        0.5f, 0.5f, -0.5f,  // H
        0.5f, -0.5f, -0.5f, // F
        0.5f, -0.5f, -0.5f, // F
        0.5f, -0.5f, 0.5f,  // B
        0.5f, 0.5f, 0.5f,   // D
        
        //Top Face
        -0.5f, 0.5f, 0.5f,  // C
        0.5f, 0.5f, 0.5f,   // D
        -0.5f, 0.5f, -0.5f, // G
        0.5f, 0.5f, 0.5f,   // D
        0.5f, 0.5f, -0.5f,  // H
        -0.5f, 0.5f, -0.5f, // G
    
};

class OpenGlFunctions
{
public:
    OpenGlFunctions()
    {
        _createProgram = getFunction<PFNGLCREATEPROGRAMPROC>("glCreateProgram");
        _attachShader = getFunction<PFNGLATTACHSHADERPROC>("glAttachShader");
        _linkProgram = getFunction<PFNGLLINKPROGRAMPROC>("glLinkProgram");
        _deleteShader = getFunction<PFNGLDELETESHADERPROC>("glDeleteShader");
        _getProgramiv = getFunction<PFNGLGETPROGRAMIVPROC>("glGetProgramiv");
        _getProgramInfoLog = getFunction<PFNGLGETPROGRAMINFOLOGPROC>("glGetProgramInfoLog");
        _createShader = getFunction<PFNGLCREATESHADERPROC>("glCreateShader");
        _shaderSource = getFunction<PFNGLSHADERSOURCEPROC>("glShaderSource");
        _compileShader = getFunction<PFNGLCOMPILESHADERPROC>("glCompileShader");
        _getShaderiv = getFunction<PFNGLGETSHADERIVPROC>("glGetShaderiv");
        _getShaderInfoLog = getFunction<PFNGLGETSHADERINFOLOGPROC>("glGetShaderInfoLog");
        _depthFunc = getFunction<PFNGLPATHCOVERDEPTHFUNCNVPROC>("glDepthFunc");
        _enable = getFunction < void(*)(GLenum) > ("glEnable");
        _uniform1i = getFunction<PFNGLUNIFORM1IPROC>("glUniform1i");
        _uniform1f = getFunction<PFNGLUNIFORM1FPROC>("glUniform1f");
        _clear = getFunction<PFNGLCLIENTATTRIBDEFAULTEXTPROC>("glClear");
        _enableVertexAttribArray = getFunction<PFNGLENABLEVERTEXATTRIBARRAYPROC>("glEnableVertexAttribArray");
        _disable = getFunction < void(*)(GLenum) > ("glDisable");
        _vertexAttribPointer = getFunction<PFNGLVERTEXATTRIBPOINTERPROC>("glVertexAttribPointer");
        _drawArrays = getFunction<PFNGLDRAWARRAYSEXTPROC>("glDrawArrays");
        _useProgram = getFunction<PFNGLUSEPROGRAMPROC>("glUseProgram");
        _getUniformLocation = getFunction<PFNGLGETUNIFORMLOCATIONPROC>("glGetUniformLocation");
        _viewport = getFunction < void(*)(GLint, GLint, GLsizei, GLsizei) > ("glViewport");
        _getIntegerv = getFunction<void (*)(GLenum, GLint *)>("glGetIntegerv");
        _disableVertexAttribArray = getFunction<PFNGLDISABLEVERTEXATTRIBARRAYPROC>("glDisableVertexAttribArray");
    }
    
    PFNGLCREATEPROGRAMPROC _createProgram;
    PFNGLATTACHSHADERPROC _attachShader;
    PFNGLLINKPROGRAMPROC _linkProgram;
    PFNGLDELETESHADERPROC _deleteShader;
    PFNGLGETPROGRAMIVPROC _getProgramiv;
    PFNGLGETPROGRAMINFOLOGPROC _getProgramInfoLog;
    PFNGLCREATESHADERPROC _createShader;
    PFNGLSHADERSOURCEPROC _shaderSource;
    PFNGLCOMPILESHADERPROC _compileShader;
    PFNGLGETSHADERIVPROC _getShaderiv;
    PFNGLGETSHADERINFOLOGPROC _getShaderInfoLog;
    PFNGLPATHCOVERDEPTHFUNCNVPROC _depthFunc;
    void (*_enable)(GLenum);
    PFNGLUNIFORM1IPROC _uniform1i;
    PFNGLUNIFORM1FPROC _uniform1f;
    PFNGLCLIENTATTRIBDEFAULTEXTPROC _clear;
    PFNGLENABLEVERTEXATTRIBARRAYPROC _enableVertexAttribArray;
    void (*_disable)(GLenum);
    PFNGLVERTEXATTRIBPOINTERPROC _vertexAttribPointer;
    PFNGLDRAWARRAYSEXTPROC _drawArrays;
    PFNGLUSEPROGRAMPROC _useProgram;
    PFNGLGETUNIFORMLOCATIONPROC _getUniformLocation;
    void (*_viewport)(GLint, GLint, GLsizei, GLsizei);
    void (*_getIntegerv)(GLenum, GLint*);
    PFNGLDISABLEVERTEXATTRIBARRAYPROC _disableVertexAttribArray;

private:
    template<class Type>
    static Type getFunction(std::string const &procName)
    {
        if (auto funcPtr = SDL_GL_GetProcAddress(procName.c_str()))
        {
            return reinterpret_cast<Type>(funcPtr);
        }
        
        std::cout << procName << " is unavailable\n";
        abort();
    }
    
};


int createShader(std::string const &source, int type, OpenGlFunctions const &openGl)
{
    GLuint const shaderID = openGl._createShader(type);
    char const *psource = source.c_str();
    openGl._shaderSource(shaderID, 1, &psource, nullptr);
    openGl._compileShader(shaderID);
    
    GLint success;
    openGl._getShaderiv(shaderID, GL_COMPILE_STATUS, &success);
    
    if (success == GL_FALSE)
    {
        char error[1024];
        openGl._getShaderInfoLog(shaderID, 1024, nullptr, error);
        
        std::cout << "Error compiling shader: " << error << std::endl;
        
        throw std::runtime_error(error);
    }
    
    return shaderID;
}

int createProgram(int vertexShaderID, int fragmentShaderID, OpenGlFunctions const &openGl)
{
    auto programID = openGl._createProgram();
    openGl._attachShader(programID, vertexShaderID);
    openGl._attachShader(programID, fragmentShaderID);
    openGl._linkProgram(programID);
    openGl._deleteShader(vertexShaderID);
    openGl._deleteShader(fragmentShaderID);
    
    GLint success;
    openGl._getProgramiv(programID, GL_LINK_STATUS, &success);
    
    if (success == GL_FALSE)
    {
        char error[1024];
        openGl._getProgramInfoLog(programID, 1024, nullptr, error);
        
        std::cout << "Error compiling shader: " << error << std::endl;
        
        throw std::runtime_error(error);
    }
    
    return programID;
}

template<class T>
using Uniform = std::pair<std::string, T>;

template<class U>
void apply(int programID, U &&u, OpenGlFunctions const &openGl)
{
    openGl._uniform1f(openGl._getUniformLocation(programID, u.first.c_str()), u.second);
}


int main(int argc, char *argv[])
{
    // Initialize SDL
    SDL_Init(SDL_INIT_VIDEO);
    SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "1");
    SDL_SetHint(SDL_HINT_RENDER_BATCHING, "1");

#ifdef _WINDOWS
    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl");
#else
    SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengles2");
#endif
    SDL_GL_SetAttribute(SDL_GL_SHARE_WITH_CURRENT_CONTEXT, 1);
    
    // Create a window
    SDL_Window* window = SDL_CreateWindow("OpenGL Example", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, WIDTH, HEIGHT, SDL_WINDOW_SHOWN);
    
    // Create a renderer
    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED);

    // Clear the renderer
    SDL_RenderClear(renderer);
    
    auto texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, 400, 400);
    
    OpenGlFunctions openGl;
    
    openGl._depthFunc(GL_LESS);
   // openGl._enable(GL_DEPTH_TEST);
    
    auto programID = createProgram(createShader(_vert, GL_VERTEX_SHADER, openGl),
                                   createShader(_frag, GL_FRAGMENT_SHADER, openGl), openGl);
    
    float x = 0.0f;
    float y = 0.0f;
    // Wait for a key press to exit
    bool quit = false;
    SDL_Event event;
    while (!quit)
    {
    
        SDL_SetRenderDrawColor(renderer, 0x00, 0x00, 0x00, 0x00);
        SDL_RenderClear(renderer);
       
        SDL_RenderFlush(renderer);
    
        SDL_SetRenderTarget(renderer, texture);
        
        // get previous context
        int previousProgramID;
        int previousViewport[4]; // x y w h
        int previousBlendEnabled;
        openGl._getIntegerv(GL_CURRENT_PROGRAM, &previousProgramID);
        openGl._getIntegerv(GL_VIEWPORT, (GLint*) &previousViewport);
        openGl._getIntegerv(GL_BLEND, &previousBlendEnabled);
        
        openGl._clear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
        openGl._useProgram(programID);
        apply(programID, Uniform<float>("angleX", x), openGl);
        apply(programID, Uniform<float>("angleY", y), openGl);
        
        // Update the screen
        openGl._viewport(0, 0, 400, 400);
        openGl._enableVertexAttribArray(0);
        openGl._disable(GL_BLEND);
        openGl._vertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, _vertices);
        openGl._drawArrays(GL_TRIANGLES, 0, 36);
        openGl._disableVertexAttribArray(0);
        
        // restore previous context
        openGl._useProgram(previousProgramID);
        openGl._viewport(previousViewport[0], previousViewport[1], previousViewport[2], previousViewport[3]);
        if (previousBlendEnabled)
        {
            openGl._enable(GL_BLEND);
        }
    
        SDL_SetRenderTarget(renderer, nullptr);
    
        SDL_Rect r;
        r.x = 100;
        r.y = 100;
        r.w = 400;
        r.h = 400;
    
        SDL_RenderCopy(renderer, texture, nullptr, &r);
    
        SDL_RenderPresent(renderer);
        
        while (SDL_PollEvent(&event))
        {
            if (event.type == SDL_QUIT)
                quit = true;
            else if (event.type == SDL_KEYDOWN)
                quit = true;
            else if (event.type == SDL_MOUSEMOTION && event.motion.state == SDL_PRESSED)
            {
                x += event.motion.yrel;
                y += event.motion.xrel;
            }
        }
        
        std::this_thread::sleep_for(std::chrono::milliseconds(16)); //give some time
    }
    
    // Cleanup and quit
    SDL_DestroyRenderer(renderer);
    SDL_DestroyWindow(window);
    SDL_Quit();
    
    return 0;
}

With this I can see the cube and rotate it. Howver when I uncomment the line: //openGl._enable(GL_DEPTH_TEST); I don’t see anything. Why? :frowning:
However if I don’t use the texture and the renderScope is null the GL_DEPTH_TEST will work, but I really want to use a texture to facilitate the integration with my application.

Thank you. :slight_smile:

Hi, I too have chosen to use OpenGL with the SDL_Renderer/SDL_Texture API. This is not something that is supported by SDL, but it is possible. I also use the render batching and haven’t noticed an issue on Mac/Linux/Windows. This does require SDL_RenderFlush to be called at appropriate times. You seem to be aware of this.

In order for it to work well there are a few things I recomend

  1. overwrite the SDL shaders at runtime with your own compatible shaders that have the extra functionality you need.
  2. before calling SDL_RenderClear and SDL_RenderPresent you should reset any extra uniforms you added to the shader that could affect rendering.
  3. be mindful of other ways you have changed OpenGL state. SDL may not be reseting things, and you would have to do this on your own.

I would recommend you checkout this project to explore in better detail what I mean. Specifically, search the code for USE_OPENGL_BACKEND.

Good Luck.

1 Like

So, your problem here is that the render target FBO that gets created doesn’t have a depth attachment, just the SDL texture as a color attachment, so when you enable depth testing it doesn’t work.

If all you want to do is use shaders (no actual 3D) then you don’t need depth testing.

Also, you don’t need to put the thread to sleep. Instead, when you create your renderer add the flag SDL_RENDERER_PRESENTVSYNC, and move your event handling code to the top of the loop. Then when SDL_RenderPresent() gets called it’ll handle sync with the display for you. And it’ll have the advantage of actually syncing with the display; sleeping 16ms (which isn’t 60 FPS, by the way; 60 FPS is 16.666666… ms) isn’t guaranteed to actually wake from sleep in sync with the display refresh.

Hi,

I do want 3D and depth testing, that is the thing. :slight_smile:

Probably have to set up your own FBO then. There’s an SDL function where you can get the OpenGL ID for an SDL texture, and could probably use that to construct your own FBO using the SDL texture as the color attachment and a depth buffer of your own creation as the depth attachment.

But once you’re at the point of setting up your own FBO outside of SDL_Renderer it seems like it makes sense to stop using SDL_Renderer entirely, especially if you’re also doing your own 3D etc.