jpcima / ysfx

Hosting library for JSFX
Apache License 2.0
161 stars 26 forks source link

Graphics #4

Open jpcima opened 2 years ago

jpcima commented 2 years ago

https://www.reaper.fm/sdk/js/gfx.php

Note (1): this appears to be the default font https://int10h.org/oldschool-pc-fonts/fontlist/font?tandy1k-ii_200l

Note (2): gfx_showmenu is synchronous this blocks @gfx from running, but not the entire host

GavinRay97 commented 2 years ago

Did you have a particular implementation plan here already? Maybe myself or someone else in the community could help out.

EDIT: Nevermind, they map to this: https://github.com/jpcima/ysfx/blob/8ef69f9d6bef85b1b7da7cfe66b9f777f536cf4c/thirdparty/WDL/source/WDL/eel2/eel_lice.h

I think the REAPER JSFX likely use SWELL, the WDL "Simple Windows Emulation Layer" This is the foundational cross-platform GFX and windowing lib that REAPER itself is built with.

IE, for gfx.rect(), that might map to this:

https://github.com/justinfrankel/WDL/blob/master/WDL/swell/swell-gdi-generic.cpp#L279-L290

https://github.com/justinfrankel/WDL/blob/9bcb9595e4dd0a6f8fde3a944a564bfe1d36e702/WDL/swell/swell-gdi-lice.cpp#L739-L762

I am no expert though

For the JUCE example of using REAPER's embedded UI stuff, I had to figure out to draw the LICE bitmaps that REAPER hands to the render method into a JUCE component.

To do that is: https://github.com/juce-framework/JUCE/blob/master/examples/Plugins/ReaperEmbeddedViewPluginDemo.h#L329-L384

void doPaint (reaper::REAPER_FXEMBED_IBitmap* bitmap)
    {
        if (bitmap == nullptr || drawInfo == nullptr || bitmap->getWidth() <= 0 || bitmap->getHeight() <= 0)
            return 0;

        Image img (juce::Image::PixelFormat::ARGB, bitmap->getWidth(), bitmap->getHeight(), true);
        Graphics g (img);

        Image::BitmapData imgData { img, Image::BitmapData::readOnly };
        const auto pixelsWidth = imgData.pixelStride * imgData.width;

        auto* px = bitmap->getBits();
        const auto rowSpan = bitmap->getRowSpan();
        const auto numRows = bitmap->getHeight();

        for (int y = 0; y < numRows; ++y)
            std::memcpy (px + (y * rowSpan), imgData.getLinePointer (y), (size_t) pixelsWidth);
    }

So maybe that eel_lice.h header can be re-used, and then a method like this used to draw it on the JUCE Editor component? (REAPER_FXEMBED_IBitmap = LICE_IBitmap)

jpcima commented 2 years ago

Did you have a particular implementation plan here already?

Kind of yes, but it's not set in stone, there would be 2 ways to go at this problem.

  1. to import SWELL+Lice+eel_lice.h which implements all of gfx as software renderer There's a bit of practical difficulty make it build it though, not entirely as trivial as it sounds.

  2. to provide a library API which lets a user implement the entire graphics interface

only the method 2. would have the ability of accelerated rendering. (decent candidates for JUCE's graphics contexts, which can render both to GL and raster)

GavinRay97 commented 2 years ago

only the method 2. would have the ability of accelerated rendering. (decent candidates for JUCE's graphics contexts, which can render both to GL and raster)

Probably better to do the second one, I've noticed you've also kept the core of it decoupled from JUCE which is nice given the flexibility of re-using it in other environments/context

jpcima commented 2 years ago

Probably better to do the second one, I've noticed you've also kept the core of it decoupled from JUCE which is nice given the flexibility of re-using it in other environments/context

It's a goal, given that this lib is not only for plugins but also for hosts, starting with Carla.

A thing about these graphics is they are modeled after Windows GDI software rendering. I'd really like to get Lice in if that's possible. Some parts of Swell give problems (eg. the windowing which pulls the Gdk dep and it's desired to avoid this) It might get away with it, if linking it with a Swell subset which is restricted to the GDI code only. That remains to verify.

jpcima commented 2 years ago

There is success so far with getting Lice into the master branch. That means that drawing is going to be kept inside the library, which keeps the usage simple.

The library user should only provide the framebuffer of the window, and other information like mouse and key data, and Retina status.

GavinRay97 commented 2 years ago

The library user should only provide the framebuffer of the window, and other information like mouse and key data, and Retina status.

What exactly is a framebuffer?

Is it something you can get from a window pointer/handle, like HWND on Windows or XID (now called Window I think) in X11 on Linux?

jpcima commented 2 years ago

The framebuffer is an image that keeps the pixel data in RAM memory. The current master has it implemented, and shows how to use it together with a juce::Image.

This is checked working, which means the hardest work is done, next it will be about adding the graphics primitives. (which should be a copy-and-paste from eel_lice)

GavinRay97 commented 2 years ago

The framebuffer is an image that keeps the pixel data in RAM memory. The current master has it implemented, and shows how to use it together with a juce::Image.

This is checked working, which means the hardest work is done, next it will be about adding the graphics primitives. (which should be a copy-and-paste from eel_lice)

We have liftoff! 🚀 Video clip below is incredible! Build was done ~5 minutes ago:

https://user-images.githubusercontent.com/26604994/143162633-b999bdb8-24bb-4b6a-88e0-64089a873235.mp4

https://user-images.githubusercontent.com/26604994/143163511-def1635f-98ae-4be1-8e23-632e7a01b0ea.mp4

jpcima commented 2 years ago

Yes, I've added a few more right now but there still remains to do some major ones. All I make so far is a tiny example, and not tried any actual plugins. Any elaborate ones which are worth testing?

desc:000 gfx2

out_pin:out

@sample
spl=0.0;

@gfx 600 400
gfx_r=rand(1);
gfx_g=rand(1);
gfx_b=rand(1);

cx=rand(gfx_w);
cy=rand(gfx_h);
cr=rand(10)+5;
gfx_circle(cx, cy, cr, 1);

n=n+1;
(n<500)?(gfx_clear=-1.0):(gfx_clear=0.0;n=0);
GavinRay97 commented 2 years ago

Any elaborate ones which are worth testing?

@JoepVanlier ("Saike") sets the bar for JSFX development IMO, his plugins are probably the golden standard as far as unit testing:

Big repo of them here:

The ones under the Basics category would probably be good "first-goal" bars since they are more minimal in terms of UI than his other plugins:

There's also Geraint Luff's JSFX, which are fantastic:

ghost commented 2 years ago

ReEQ is another one. https://github.com/Justin-Johnson/ReJJ

jpcima commented 2 years ago

Graphics are mostly working now, needs just these few features such as cursor and popup.

jpcima commented 2 years ago

The status of gfx is supposed to be now implemented 100%. The popup has needed a rewrite of gfx processing at plugin-side. More precisely, the @gfx is allowed to block indefinitely (by gfx_showmenu), and so it can't happen on the main UI thread except by creating a modal loop; as a solution, @gfx has been moved into a background thread.