Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

wxGLCanvas: Discrete Colour Sections Instead of Smooth Colour Gradient #24458

Open
ReuBird23 opened this issue Apr 6, 2024 · 15 comments
Open

Comments

@ReuBird23
Copy link

Description

wxWidgets programs using OpenGL don't display colour gradients smoothly, but in small discrete sections of colour.
I ran into this problem when trying to implement a simple wxWidgets app to display a quad which fades from black on the left to red on the right, and it instead displayed several vertical rectangles. I have reproduced the problem by converting a glfw program from https://learnopengl.com/Getting-started/Shaders to wxWidgets . The program displays smooth gradients with glfw, but not smooth with wxWidgets.

GLFW

GLFW

wxWidgets

wxWidgets

Additionally, when compiling the pyramid and isosurf OpenGL samples, the same issue can be seen.

Pyramid

Pyramid

Isosurf

Isosurf

GLFW Triangle Gradient Source (modified from learnopengl to use GLEW)

#include <GL/glew.h>
#include <GLFW/glfw3.h>

#include <iostream>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char *vertexShaderSource =R"(
    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aColor;
    out vec3 ourColor;
    void main()
    {
       gl_Position = vec4(aPos, 1.0);
       ourColor = aColor;
    }
    )";

const char *fragmentShaderSource = R"(
    #version 330 core
    out vec4 FragColor;
    in vec3 ourColor;
    void main()
    {
       FragColor = vec4(ourColor, 1.0f);
    }
    )";

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (glewInit() != GLEW_OK)
    {
        std::cout << "Failed to initialize GLEW" << std::endl;
        return -1;
    }

    // build and compile our shader program
    // ------------------------------------
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // fragment shader
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // link shaders
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    // set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float vertices[] = {
        // positions         // colors
         0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  // bottom right
        -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  // bottom left
         0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f   // top

    };

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    // glBindVertexArray(0);

    // as we only have a single shader, we could also just activate our shader once beforehand if we want to
    glUseProgram(shaderProgram);

    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processInput(window);

        // render
        // ------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // render the triangle
        glBindVertexArray(VAO);
        glDrawArrays(GL_TRIANGLES, 0, 3);

        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // optional: de-allocate all resources once they've outlived their purpose:
    // ------------------------------------------------------------------------
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

wxWidgets Triangle Gradient Source

#include <GL/glew.h>
#include <wx/wx.h>
#include <wx/glcanvas.h>

const char *vertexShaderSource =R"(
    #version 330 core
    layout (location = 0) in vec3 aPos;
    layout (location = 1) in vec3 aColor;
    out vec3 ourColor;
    void main()
    {
       gl_Position = vec4(aPos, 1.0);
       ourColor = aColor;
    }
    )";

const char *fragmentShaderSource = R"(
    #version 330 core
    out vec4 FragColor;
    in vec3 ourColor;
    void main()
    {
       FragColor = vec4(ourColor, 1.0f);
    }
    )";


class MyApp : public wxApp
{
public:
    bool OnInit() override;
};


wxIMPLEMENT_APP(MyApp);


class MyCanvas : public wxGLCanvas
{
public:
    MyCanvas(wxWindow* parent, int args[]);
    ~MyCanvas();
    void InitOpenGL();
    void OnPaint(wxPaintEvent& evt);
    void OnSize(wxSizeEvent& evt);
private:
    wxGLContext* m_context;
    unsigned int VBO, VAO;
    unsigned int shaderProgram;
    DECLARE_EVENT_TABLE();
};


class MyFrame : public wxFrame
{
public:
    MyFrame();
private:
    MyCanvas* canvas;
};


bool MyApp::OnInit()
{
    MyFrame *frame = new MyFrame();
    //frame->Show();
    return true;
}


MyFrame::MyFrame()
    : wxFrame(nullptr, wxID_ANY, "OpenGL Test")
    {
        int args[] = {WX_GL_RGBA, WX_GL_DOUBLEBUFFER, WX_GL_DEPTH_SIZE, 16, 0};
        canvas = new MyCanvas(this, args);
        Show();
        canvas->InitOpenGL();
    }


BEGIN_EVENT_TABLE(MyCanvas, wxGLCanvas)
EVT_PAINT(MyCanvas::OnPaint)
EVT_SIZE(MyCanvas::OnSize)
END_EVENT_TABLE()


MyCanvas::MyCanvas(wxWindow* parent, int args[])
    : wxGLCanvas(parent, wxID_ANY, args, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE), m_context(new wxGLContext(this)) {}


MyCanvas::~MyCanvas()
{
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);
}


void MyCanvas::InitOpenGL()
{
    if (!SetCurrent(*m_context)){
        wxMessageBox("Failed to set current context", "Error");
    }

    if (glewInit()) {
        wxMessageBox("Failed to initialize GLEW", "Error");
    }

    // build and compile our shader program
    // ------------------------------------
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // fragment shader
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // link shaders
    shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    // set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float vertices[] = {
        // positions         // colors
         0.5f, -0.5f, 0.0f,  1.0f, 0.0f, 0.0f,  // bottom right
        -0.5f, -0.5f, 0.0f,  0.0f, 1.0f, 0.0f,  // bottom left
         0.0f,  0.5f, 0.0f,  0.0f, 0.0f, 1.0f   // top

    };

    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    glBindVertexArray(VAO);

    glBindBuffer(GL_ARRAY_BUFFER, VBO);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // position attribute
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);
    // color attribute
    glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 6 * sizeof(float), (void*)(3 * sizeof(float)));
    glEnableVertexAttribArray(1);

    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    // glBindVertexArray(0);

    // as we only have a single shader, we could also just activate our shader once beforehand if we want to
    glUseProgram(shaderProgram);
}


void MyCanvas::OnPaint(wxPaintEvent& evt)
{
    // render
    // ------
    glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);

    // render the triangle
    glBindVertexArray(VAO);
    glDrawArrays(GL_TRIANGLES, 0, 3);

    SwapBuffers();
}


void MyCanvas::OnSize(wxSizeEvent& evt)
{
    glViewport(0, 0, GetSize().x, GetSize().y);
    Update();
}

Platform and version information

  • wxWidgets version you use: 3.2.4
  • wxWidgets port you use: wxGTK
  • OS and its version: Arch Linux
    • GTK version: 3.24.41
    • Which GDK backend is used: Wayland
    • Desktop environment : KDE
@vadz
Copy link
Contributor

vadz commented Apr 6, 2024

Sorry, I have absolutely no idea what could explain this, but I wonder if it's specific to EGL implementation or also happens when using GLX? Unfortunately testing this requires rebuilding wxWidgets with --disable-glcanvasegl configure option or wxUSE_GLCANVAS_EGL=OFF if using CMake.

@ReuBird23
Copy link
Author

Yes, I recompiled wxWidgets with --disable-glcanvasegl and ran them with export GDK_BACKEND=x11 which rendered correctly. In fact recompiling was not necessary, the archlinux packaged version of wxWidgets correctly renders when using x11.

@vadz
Copy link
Contributor

vadz commented Apr 7, 2024

Thanks for testing! But now that we know it's EGL-specific, I can't help asking another question: does GLFW example work correctly when using EGL?

@ReuBird23
Copy link
Author

I believe it was using EGL, how should i determine that?

@vadz
Copy link
Contributor

vadz commented Apr 7, 2024

I believe it was using EGL, how should i determine that?

Not sure how to do it with GLFW. If it uses Wayland, it must be using EGL (GLX can't work in this case), does it?

@ReuBird23
Copy link
Author

I used the xprop and xeyes tools to test, and the glfw program is indeed using Wayland/EGL.
https://superuser.com/questions/1484068/how-to-check-if-a-program-uses-wayland-or-x11-in-linux.

@vadz
Copy link
Contributor

vadz commented Apr 7, 2024

Thanks again for testing!

Unfortunately I still don't know what's actually wrong here, my guess is that we are not setting some attributes correctly but unfortunately I don't have time to look at this further and any help would be welcome.

@dgud
Copy link
Contributor

dgud commented Apr 8, 2024

@vadz Didn't you fix something with selecting the first "context" available which was only 4bits per color channel recently.

That could explain this, i.e. to small color space would cause banding I think.

(just scrolling by the mailing list)

@vadz
Copy link
Contributor

vadz commented Apr 8, 2024

Oops, yes, indeed, thanks for the reminder, I hadn't thought that choosing a wrong mode could result in this, but it definitely could.

@ReuBird23 Can you please test the latest master or 3.2 with #24375 (which will soon be merged into it too and be part of 3.2.5)?

@ReuBird23
Copy link
Author

I tested it, and it made no change.
However, I have just had the opportunity to test the problem on my desktop that I have been away from for a few weeks (I have been having the issues on my laptop). The banding problem didn't happen...until after I updated my system. The wxWidgets-gtk3 was already up to date beforehand, so some other update on my system within the last month has caused the problem. I will do some downgrading to try to find out which package it is.

@ReuBird23
Copy link
Author

Downgrading the kwin package to 6.0.2 fixed the problem. Versions 6.0.3 and 6.0.3.1 have the problem.

@dsa-t
Copy link
Contributor

dsa-t commented Apr 9, 2024

Try adding
WX_GL_MIN_RED, 8, WX_GL_MIN_GREEN, 8, WX_GL_MIN_BLUE, 8
to wxGLCanvas attributes.

Looks like it uses RGB565 mode.

@dsa-t
Copy link
Contributor

dsa-t commented Apr 9, 2024

@ReuBird23
Copy link
Author

Yes thank you, that works. I wonder what changed in the kwin update that meant it wasn't necessary before. Thanks for your help.

@vadz
Copy link
Contributor

vadz commented Apr 10, 2024

Thanks for the hint, Alex!

I wonder if we should we select 8-bit colour channels by default too? It seems that if anybody really wants less, they would be selecting this explicitly anyhow...

If not, this can be just closed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants