wxMouseEvent slow rate Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
User avatar
Artifact2238
Earned a small fee
Earned a small fee
Posts: 23
Joined: Mon May 28, 2018 2:43 am

wxMouseEvent slow rate

Post by Artifact2238 »

Hello wxWidgets forum,

The rate at which a wxMouseEvent occurs in wxWidgets is a bit too low for my liking. I have the same logic setup, but using another window/event system (SFML), and the rate of mouse events is about twice as fast. I like SFML, but I really want to use wxWidgets due to its superior native menu/widget usability.

I am measuring the number of wxMouseEvents that occur in 10 seconds while my mouse is dragging, and I visually put a small square red dot each drag event to visually compare.

Measuring on wxWIdgets;
I get about 611 events, or about 60 events per second. The dots are farther apart and the less smooth (less dots = sharper angles between dots when dragging the mouse in a circle).

Measuring on SFML:
I get about 1611 events, or about 160 events per second. So, about twice that of wxWidgets. The dots are a lot more smooth!

I will post what I hope is the relevant parts of my program. Posting the whole program would be a bit cumbersome because of all the OpenGL setup, but I could if there is interest...

wxWidgets src:

Code: Select all

wxBEGIN_EVENT_TABLE(MyGLCanvas, wxGLCanvas)
    EVT_PAINT(MyGLCanvas::OnPaint)
    EVT_SIZE(MyGLCanvas::OnSize)
    EVT_MOUSE_EVENTS(MyGLCanvas::OnMouse)

    //EVT_IDLE(MyGLCanvas::OnIdle)

wxEND_EVENT_TABLE()

Code: Select all

void MyGLCanvas::OnMouse(wxMouseEvent& event)
{
    static int i = 0;
    if (i++ % 10 == 0) std::cout << i << "\n";

    event.Skip();

    // GL 0 Y-coordinate is at bottom of the window
    int oglwinY = m_winHeight - event.GetY();

    if (event.LeftUp())
    {

    }
    if (event.LeftDown())
    {
        //std::cout << "(" << i << ") " << "Click at " << "(" << event.GetX() << ", " << event.GetY() << ")\n";

        size_t index = 4 * m_width * event.GetY() + 4 * event.GetX();
        m_pixels[index+0] = 255;
        m_pixels[index+1] = 0;
        m_pixels[index+2] = 0;
    }

    if (event.LeftIsDown())
    {
        //std::cout << "...\n";
        if (!event.Dragging())
        {

        }
        else
        {
            //std::cout << "(" << i << ") " << "dragging\n";
            static const std::vector<unsigned char> red_data {
                255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255,
				255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255, 255, 0, 0, 255, 255, 0, 0, 255,
                255, 0, 0, 255,
                255, 0, 0, 255

            };

            // Update the  image to OpenGL
            // by sending a red box
            glTexSubImage2D(
                GL_TEXTURE_2D, // target
                0, // level
                event.GetX(), // xoffset
                event.GetY(), // yoffset
                   // note: no internal format can be changed for Sub
                m_width/100, // width
                m_height/100, // height
                //GL_BGRA, // format (of the pixel data)
                GL_RGBA,
                GL_UNSIGNED_BYTE, // type
                red_data.data() //dat
            );

            // Generate paint event without erasing the background.
            Refresh(false);
        }
    }
}

Code: Select all

void MyGLCanvas::OnPaint( wxPaintEvent& WXUNUSED(event) )
{
    // This is a dummy, to avoid an endless succession of paint messages.
    // OnPaint handlers must always create a wxPaintDC.
    wxPaintDC dc(this);

    // Avoid painting when we have not yet a size
    if (m_winHeight < 1)
        return;

    // This should not be needed, while we have only one canvas
    SetCurrent(*m_oglContext);

    // Do the magic
    //m_oglManager->Render();
    glClearColor(0.0, 0.2, 0.0, 1.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    glUseProgram(shader.getProgram());

    // Bind our texture in Texture Unit 0
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, textureID);

    // Set our "myTextureSampler" sampler to use Texture Unit 0
    glUniform1i(uniformTextureID, 0);

    // 1st attribute buffer : vertices
    glEnableVertexAttribArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
    glVertexAttribPointer(
        0,          // attribute 0. Matches layout of shader.
        3,          // size
        GL_FLOAT,   // type
        GL_FALSE,   // normalized?
        0,          // stride
        (void*)0    // array buffer offset
    );

    // 2nd attribute buffer : UVs
    glEnableVertexAttribArray(1);
    glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
    glVertexAttribPointer(
			1,                                // attribute. No particular reason for 1, but must match the layout in the shader.
			2,                                // size : U+V => 2
			GL_FLOAT,                         // type
			GL_FALSE,                         // normalized?
			0,                                // stride
			(void*)0                          // array buffer offset
    );

    glDrawArrays(GL_TRIANGLES, 0, 3 * 2); // 3 * num_triangles_to_draw
    glDisableVertexAttribArray(0);
    glDisableVertexAttribArray(1);

    SwapBuffers();
}
SFML src:

Code: Select all

    while (window.isOpen())
    {
        sf::Event event;
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::MouseButtonPressed)
            {
                if (event.mouseButton.button == sf::Mouse::Left)
                {
                    std::cout << "the left button was pressed" << std::endl;
                    std::cout << "mouse x: " << event.mouseButton.x << std::endl;
                    std::cout << "mouse y: " << event.mouseButton.y << std::endl;

                    size_t index = 4 * m_width * event.mouseButton.y + 4 * event.mouseButton.x;
                    m_pixels[index+0] = 255;
                    m_pixels[index+1] = 0;
                    m_pixels[index+2] = 0;

                    dragging = true;
                }
            }
            else if (event.type == sf::Event::MouseButtonReleased)
            {
                dragging = false;
            }
            else if (event.type == sf::Event::MouseMoved)
            {
                static int i = 0;
                if (i++ % 10 == 0) std::cout << i << "\n";

                if (dragging)
                {
                    //std::cout << "dragging at (" << event.mouseMove.x << ", " << event.mouseMove.y << "\n";
                    static const std::vector<unsigned char> red_data {
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255, 255, 0, 0, 255,
                        255, 0, 0, 255
                    };

                    // Update the  image to OpenGL
                    // by sending a red box
                    glTexSubImage2D(
                        GL_TEXTURE_2D, // target
                        0, // level
                        event.mouseMove.x, // xoffset
                        event.mouseMove.y, // yoffset
                           // note: no internal format can be changed for Sub
                        m_width/100, // width
                        m_height/100, // height
                        GL_BGRA, // format (of the pixel data)
                        GL_UNSIGNED_BYTE, // type
                        red_data.data() //dat
                    );

                }
            }
            else if (event.type == sf::Event::Closed)
                window.close();
        }

        sf::Time t = sf::milliseconds(1);
        sf::sleep(t);

        glClearColor(0.0, 0.2, 0.0, 1.0);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glUseProgram(shader.getProgram());

        // Bind our texture in Texture Unit 0
        glActiveTexture(GL_TEXTURE0);
        glBindTexture(GL_TEXTURE_2D, textureID);

        // Set our "myTextureSampler" sampler to use Texture Unit 0
        glUniform1i(uniformTextureID, 0);

        // 1st attribute buffer : vertices
        glEnableVertexAttribArray(0);
        glBindBuffer(GL_ARRAY_BUFFER, vertexbuffer);
        glVertexAttribPointer(
            0,          // attribute 0. Matches layout of shader.
            3,          // size
            GL_FLOAT,   // type
            GL_FALSE,   // normalized?
            0,          // stride
            (void*)0    // array buffer offset
        );

        // 2nd attribute buffer : UVs
        glEnableVertexAttribArray(1);
        glBindBuffer(GL_ARRAY_BUFFER, uvbuffer);
        glVertexAttribPointer(
                1,                                // attribute. No particular reason for 1, but must match the layout in the shader.
                2,                                // size : U+V => 2
                GL_FLOAT,                         // type
                GL_FALSE,                         // normalized?
                0,                                // stride
                (void*)0                          // array buffer offset
        );

        glDrawArrays(GL_TRIANGLES, 0, 3 * 2); // 3 * num_triangles_to_draw
        glDisableVertexAttribArray(0);
        glDisableVertexAttribArray(1);
        window.display();
    }
So basically, am I doing something wrong? How can increase the rate at which wxWidgets can detect mouse movement?

Thank you.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxMouseEvent slow rate

Post by doublemax »

I'm pretty sure this has nothing to do with the mouse events. They're just generated as the native events/messages come in.

As you get 60 events per second i have a strong suspicion that it has to do with OpenGL output locked to vsync. That's the direction i would investigate further.
Use the source, Luke!
User avatar
Artifact2238
Earned a small fee
Earned a small fee
Posts: 23
Joined: Mon May 28, 2018 2:43 am

Re: wxMouseEvent slow rate

Post by Artifact2238 »

Thank you, I just tested measuring the frame rate itself and it's true, wxWidgets caps it at 60 frames per second by default, while SFML by default is uncapped.

If I remove the

Code: Select all

Refresh(false);
line from the wxMouseEvent OnMouse function, the rate of mouse events does appear to be significantly higher or on par with SFML.
Likewise, if I add the framerate cap on SFML, I get the same 60 mouse events/second rate.

But removing the Refresh call from the OnMouse event function obviously prevents anything from being drawn. :(

I don't think that has to do with OpenGL, specifically. I am not messing with any OpenGL extensions when it comes to framerate or Vsync. Also, even if I remove all opengl calls in my wxPaintEvent function, the result is the same. I thought that stuff was more dependent on the windowing system and not OpenGL itself, i.e. it's wxWIdgets itself capping the frame rate. Is it not?

I initialize the OpenGL context like this:

Code: Select all

MyGLCanvas::MyGLCanvas(MyFrame* parent, const wxGLAttributes& canvasAttrs)
                       : wxGLCanvas(parent, canvasAttrs)
{
    m_parent = parent;
    m_ogl_ready = false;
    m_winHeight = 0; // We have not been sized yet

[b]    // Explicitly create a new rendering context instance for this canvas.
    wxGLContextAttrs ctxAttrs;
    m_oglContext = new wxGLContext(this, NULL, &ctxAttrs);[/b] 
    ...
}
is that where the frame cap gets instated? I could try making another program without any references to OpenGL, if that would help narrow down the problem.

I did a search, but can't find a way to increase the framerate cap on wxWIdgets. Closest I got was viewtopic.php?t=18431

Do you have any suggestion on how to not have wxWidgets cap it to 60 fps?
Also, is it possible to still make wxWidgets have a higher event throughput, despite the paint cap of 60 fps? That would allow me to still have the events more than 60 times a second, but only re-paint 60 times a second. As it stands right now, the mouse movement just isn't smooth enough.

Thank you.
User avatar
doublemax
Moderator
Moderator
Posts: 19160
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: wxMouseEvent slow rate

Post by doublemax »

wxWidgets itself doesn't limit the refresh rate anywhere. It's possible that VSYNC is on by default in OpenGL and that SFML switched that off through an OpenGL extension. You could try to call Update() after Refresh() which causes an immediate refresh, but i don't think it'll make a difference in this case.

Which platform are you testing on? If it's Windows, there should be an option in the GPU settings to switch off OpenGL vsync. If that's indeed the problem (and i'm pretty sure it is), then add some code to switch it off in your program.
Use the source, Luke!
n0wiq
In need of some credit
In need of some credit
Posts: 5
Joined: Thu Jul 05, 2018 8:59 pm

Re: wxMouseEvent slow rate

Post by n0wiq »

If you are on Linux/Xwindows variants, the mouse message rates are different than Windows by miles because of the architecture of Xwindow's protocol structure. There are videos on YouTube about the problems of Xwindows extensively. Ditching Xfree86 would help Linux platforms immensely and why Android, a Linux based platform. is doing so well. The Apple platform, which is based on BSD unix, ditched Xfree86 for something else. So stating which platform you are on makes all the difference. I would be using the Vulcan Api over OpenGL anyways, there is a speed difference there two.
User avatar
Artifact2238
Earned a small fee
Earned a small fee
Posts: 23
Joined: Mon May 28, 2018 2:43 am

Re: wxMouseEvent slow rate

Post by Artifact2238 »

Hi all! Was busy, but got back to testing things out.

I am currently on Windows although have worked on Linux platforms. (So, any solution would preferably be cross-platform, but I haven't actually done any Windows vs. Linux performance measurements yet.)

I looked into the refresh rates and vsync more, as you suggested, doublemax. Once again, you're correct, the framerate does appear to be limited by default, by OpenGL. I'm not sure which part of OpenGL (or combination of opengl+wx?) does this specifically, but regardless, I was able to follow this StackOverflow page and use WGL extensions to remove the 60 fps (vsync) limit: https://stackoverflow.com/questions/589 ... 232#589232
The other answer on the StackOverflow page also lists more cross-platform solutions (GLX/X11).

After calling

Code: Select all

wglSwapIntervalEXT(0);
, the wxWidgets code now runs faster than SFML! Thank you for the help.

Side note, I would really love to see how professional programs like Adobe Photoshop handle this stuff under the hood, to have smooth mouse-movement responsiveness.
Post Reply