lv2 / pugl

A minimal portable API for embeddable GUIs
https://gitlab.com/lv2/pugl/
ISC License
174 stars 34 forks source link

puglPostRedisplayRect without effect on X11 #95

Closed sjaehn closed 1 year ago

sjaehn commented 1 year ago

... if parameter rect.width < rect.x or rect.height < rect.y.

I did a bit more testing and it turns out that it puglPostRedisplayRect properly works with {x1, y1, x2, y2} as second parameter instead of PuglRect (= {x, y, width, height}). Did I miss something or is it a (not completed yet) API change or just a bug?

sjaehn commented 1 year ago

FYI, I didn't observe this problem with a 2 years old (Jan 2021) version I used before within my TK (now I try to update). I'll try to find the cause. From my first look into the recent pugl code, I can't find any cause why {x1, y1, x2, y2} works but not {x, y, width, height} as it should. In the recent version, it is AFAIK correctly passed finally to xev.xexpose.x (or y or witdth or heigth) in https://github.com/lv2/pugl/blob/bd4f79646f623e929e6aa22bea028952b515aeef/src/x11.c#L1420-L1424 to XSendEvent() in https://github.com/lv2/pugl/blob/bd4f79646f623e929e6aa22bea028952b515aeef/src/x11.c#L1478 - No idea

sjaehn commented 1 year ago

A short video demonstration of the problem: https://www.youtube.com/watch?v=G7f0KqphYpA

Very simple program. A window 400x300px containing a full size image (thus also 400x300px) with rolling colors. Visual content is updated in Window.cpp using puglPostRedisplayRect.

First: Full size puglPostRedisplayRect with the PuglRect parameter = 0, 0, 400, 300. Everything as expected. Second: Try to color roll only a 100x100px rect at position 200,100 with PuglRect = 200, 100, 100, 100. Nothing. Third: Use pairs of coords x1, y1, x2, y2 = 200, 100, 300, 200 instead. This works.

drobilla commented 1 year ago

Just a bug, it seems. While I am idly thinking about killing PuglRect entirely (nothing but trouble in most cases), it should work as it always has.

Not sure off the top of my head how this is broken, maybe the rectangle merging is wrong. I'll look into it.

sjaehn commented 1 year ago

I didn't see the bug inside mergeExposeEvents. Although this was my first idea too.

drobilla commented 1 year ago

Yeah I took a quick look and everything seems to (be trying to) do the correct thing.

Can you share the above-mentioned program's code?

sjaehn commented 1 year ago

The above mentioned program (and the video) was made with my toolkit which wraps around pugl. To simplify, I did the same with the naked pugl:

#include "pugl/cairo.h"
#include "pugl/pugl.h"

#include <cairo/cairo.h>

#include <math.h>
#include <stdbool.h>
#include <stdio.h>
#include <string.h>
#include <time.h>

typedef struct {
  PuglWorld*      world;
  int             quit;
  cairo_t*        cr;
  clock_t         t;
} PuglTestApp;

static void
onDisplay(PuglTestApp* app, PuglView* view, const PuglExposeEvent* event)
{
  app->cr = (cairo_t*)puglGetContext(view);
  app->t = clock();
  const double x = (double)app->t / 1000.0;
  cairo_set_source_rgba(app->cr, sin (x), sin (x + 0.667 * M_PI), sin (x + 1.333 * M_PI), 1.0);
  cairo_rectangle(app->cr, 0, 0, 400, 300);
  cairo_fill (app->cr);
}

static void
onClose(PuglView* view)
{
  PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);
  app->quit = 1;
}

static PuglStatus
onEvent(PuglView* view, const PuglEvent* event)
{
  PuglTestApp* app = (PuglTestApp*)puglGetHandle(view);

  switch (event->type) {
  case PUGL_UPDATE:
    {
      PuglRect rect;
      rect.x = 150;
      rect.y = 100;
      rect.width = 200;
      rect.height = 200;
      puglPostRedisplayRect(view,rect);
    }
    break;
  case PUGL_EXPOSE:
    onDisplay(app, view, &event->expose);
    break;
  case PUGL_CLOSE:
    onClose(view);
    break;
  default:
    break;
  }

  return PUGL_SUCCESS;
}

int
main(int argc, char** argv)
{
  PuglTestApp app;
  memset(&app, 0, sizeof(app));

  app.world = puglNewWorld(PUGL_PROGRAM, 0);
  puglSetWorldString(app.world, PUGL_CLASS_NAME, "Demo");
  PuglView* view = puglNewView(app.world);
  puglSetViewString(view, PUGL_WINDOW_TITLE, "Demo");
  puglSetSizeHint(view, PUGL_DEFAULT_SIZE, 400, 300);
  puglSetViewHint(view, PUGL_RESIZABLE, PUGL_FALSE);
  puglSetHandle(view, &app);
  puglSetBackend(view, puglCairoBackend());
  puglSetEventFunc(view, onEvent);
  puglRealize(view);
  puglShow(view, PUGL_SHOW_RAISE);

  const double   timeout    = 1.0 / 60.0;
  while (!app.quit) puglUpdate(app.world, timeout);

  puglFreeView(view);
  puglFreeWorld(app.world);
  return 0;
}

Play around with the PuglRect parameters in onEvent() and you'll see it.

drobilla commented 1 year ago

I see. The X11 and expose dispatching mechanism works fine (easy to verify by printing the expose event bounds), but the Cairo backend screws up the clipping and/or offset so rendering partial regions doesn't work properly.

drobilla commented 1 year ago

Fixed in 2298803. Thanks for the test, that made the problem pretty obvious.

I added a finely-exposed mouse cursor to pugl_cairo_demo which is extremely sensitive to this sort of thing (and handily doubles as a latency tester), so hopefully this regression won't happen again.

I'm considering refining the API to truly support fine-grained exposure (so only each individual region will be exposed separately), so I'll probably tinker with that test a little more to make it very obviously broken when exposure isn't working properly. Cairo's the main case there, I think, if you're using OpenGL or Vulkan you're probably just blasting entire frames to the GPU anyway. That said, you could be smart about it there, too, so I think it's worth doing overall. For audio software, I can pretty easily imagine cases where updating a little level meter or similar would chew way less power / resources in general with this.

sjaehn commented 1 year ago

Cool, and your commits fixed another related (maybe the same) drawing problem I observed but not reported.

And I go with you, fractional display updating makes sense as audio software UIs become more complex and thus bigger and as CPU rendering is not dead yet. We started with plugins with just a few knobs one or two decades before. But now think about the full screen synthesizers like vitalium or surge. Or the growing LSP plugins. Or my B.Oops effect sequencer plugin. Therefore I decided to use puglPostRedisplayRect from the beginning.

Finally thanks for the quick fixing.