Forum Rule: Always post complete source code & details to reproduce any issue!
Page 2 of 2 FirstFirst 1 2
Results 26 to 40 of 40

Thread: tgx: a 2D/3D graphics library for Teensy.

  1. #26
    Join Date
    Feb 2017
    Chicago, IL
    Quote Originally Posted by vindar View Post

    Drawing the viewport with multiple calls as above permits to use less memory and so we can put the buffers in faster memory region. However, this approach also has its drawback because, for each call, the renderer must iterate over all the triangles of the mesh to see which ones should be drawn and which one are clipped/discarded... Therefore, the speed up obtained by using a faster memory can be lost because of the increase in the number of triangle to iterate over. I think that splitting the viewport instead of using EXTMEM will be efficient when the screen is large but the mesh has relatively few triangles (maybe 5K like 'bunny" in the texture example) but it may not prove very good for detailed mesh like 'buddha' (20K).

    A solution for large meshes would be to split it into is many smaller sub-meshes. Then the renderer will discard the sub-meshes whose bounding box do not intersect the image being drawn without having to loop over its triangles. But splitting a mesh in sub-meshes like this is a tedious process...
    Thanks for this! I went with this loop in the end, which works for even multiples of the screen height:
    static const int SLX = 480;
    static const int SLY = 800;
    static const int chunks = 4;
    static const int chunkSize = SLY / chunks;
    // main screen framebuffer 
    uint16_t fb[SLX * chunkSize];
    // zbuffer in 16bits precision
    DMAMEM uint16_t zbuf[SLX * chunkSize];           
    // image that encapsulates fb.
    Image<RGB565> im(fb, SLX, chunkSize);
    for (uint16_t y=0; y<SLY; y+=chunkSize) {
          // draw chunk
          im.fillScreen(RGB565_Blue); // we must erase it
          renderer.clearZbuffer(); // and do not forget to erase the zbuffer also
          renderer.setOffset(0, y); // set the offset inside the viewport for the lower half
          renderer.drawMesh(buddha_cached, false);   // draw that part on the image
          tft.setAddrWindow(0, y, SLX -1, y + chunkSize - 1);
          // update the screen
          tft.pushPixels16bit(fb,  fb + (SLX * chunkSize) - 1);
    Works like a charm! On an SSD1963 800*480 screen over 16 bit parallel GPIO6, frame rate is a steady 13.57fps; that includes the rendering calculations and the (blocking) screen transfer time. Thanks, this is fun!

    (My setAddressWindow takes x1, y1, x2, y2 rather than x1, y1, w, h, hence the -1 in the calls, and for iterating an N byte block of memory, that's contained in <start> to <start> + N-1 , hence the -1 in the pushPixels bufferEnd)

  2. #27
    Good to know it is working

    I think it should be faster using only 3 chunks instead of 4 no ?

    By the way, I just push an update to the library with some breaking change: the view port dimensions are not template parameter of the Renderer3D class anymore but just regular parameters that can be set at runtime. THis porvides more flexibility but this means that previous sketches must now replace
    Renderer3D<RGB565, SLX, SLY, LOADED_SHADERS, uint16_t> renderer;
    Renderer3D<RGB565, LOADED_SHADERS, uint16_t> renderer;
    and add a line during setup:

  3. #28
    Quote Originally Posted by neutron7 View Post
    That was a big help, thanks! I got it working with t3n, single buffer for now with 50mHz SPI, its getting the same FPS as before without any TGX primitives, (36FPS which is quite acceptable, its just a 2D interface) and a few alpha blended objects dont slow it down much as long as they aren't too large. I may use double buffer later, but i'm not yet sure if i will need that memory for the rest of the project.

    I just want to let you know that I updated the ILI9341_T4 library so that it now accepts any pin for CS and DS (but using a hardware CS pin for DS will provide the best performance). So you can now try it with your PCB if you wish. If you are using it to display a UI, then you might see big improvements in the framerate and smoothness of the transitions compared with to the ILI9341_t3(n) libraries.

  4. #29
    Thanks! i will try it.

    I was wondering if there is a way to set a Viewport "clipping window" for 2D as well as the renderer? it can be quite useful.

  5. #30
    Yes, it is very easy to clip any drawing operation to a given rectangular region of an image by creating a sub image referencing this region and drawing on it instead of the main image. Let me illustrate this. Say you have a 320x240 image:
    tgx::Image<tgx::RGB565> im(buffer,*320,240);
    And you want to draw a red circle centered at (200,120) with radius 100 but you want to clip it to the region [150,320]x[110,200]. Then you create a sub image for this region (thus sharing the same memory buffer) with
    auto subim = im.getCrop({150,320,110,200});
    And then draw the circle on subim but do not forget to subtract the offset of the upper left corner of the sub image to convert coordinate between im and subim:
    subim.drawCircle({200 - 150, 120 - 110}, 100, tgx::RGB565_Red);
    Note that creating images is very cheap, the object itself is only 16 bytes so you can create sub images whenever needed and then discard them without any performance concerns.

    As you can see, this method is quite powerful because any method that draws on an image can also be clipped to any région. This is the same idea as the «view» in Numpy (the Python lib) and I think it is very elegant solution . The only thing needed to implement it is to specify that an image has a stride that may be different from its width.

  6. #31
    That is really good. I made a new image 128x64, for an oscilloscope overlay, and draw directly on to it with "persistence" done by drawing "transparent black" over it every frame, then blit it on to my main image buffer (also with transparency) and it only slowed down by about 1.2 FPS. (t3_n, single buffered). Thank you for the useful and cool library!

  7. #32
    I don't know If you have done something like this already and I could not find it, or if it is too slow, or this is asking a bit much! but is there something like the layer blending modes in graphics programs like Photoshop when you do a blit? It would be useful for things like fake lighting.

    Click image for larger version. 

Name:	Screenshot 2022-05-31 200009.jpg 
Views:	7 
Size:	52.4 KB 
ID:	28543

  8. #33

    The blitting operations that are currently implemented use only basic alpha blending (and you must call the methods with an opacity parameter as the last parameter to activate blending, otherwise colors are simply overwritten). This applies for 'blit()', 'blitMasked()' and 'blitScaledRotated()'. I do not understand much about alpha blending so I do not really know other blending modes or how they would be implemented in the library while still remaining compatible with all the color types (RGB16, RGB32, RGBf...). Most color types do not even have an alpha channel...

    However, if you code a blending operator say (col1,col2, opacity) -> blend(col1, col2, opacity) which return the blending of two colors, then it is straightforward to adapt the blitting methods above to use this operation instead of the usual alpha blending.

    Maybe I could create a generic 'blit()' method which takes as parameter a user-defined blending operator. This might be a reasonable solution as it will allow users to perform fancy custom blending operation without adding too much code to the library.

  9. #34
    That is a nice solution! "milkdrop" here we come

  10. #35
    Quote Originally Posted by neutron7 View Post
    That is a nice solution! "milkdrop" here we come

    I just added a new 'blend()' method to the library that performs the blitting of a sprite onto an image using a custom blending operator. If it works fine, I will also later add 'blendMask()' and 'blendScaleRotated()' methods (similar to the existing 'blitMask()' and 'blitScaleRotated()' methods). The documentation for using the method is, as usual, located above its declaration (Image.h, line 876). Here is an almost complete example for performing multiplicative blending of two images:

    #include <tgx.h>
    using namespace tgx;
    uint16_t dst_buf[320 * 240];  // buffer
    Image<RGB565> dst(dst_buf, 320, 240); // image
    uint16_t src_buf[200*200]; // buffer
    Image<RGB565> src(src_buf, 200, 200); // sprite
    /* blending operator: perform multiplicative blending of two RGBf colors */
    RGBf mult_op(RGBf col_src, RGBf col_dst)  {
        return RGBf(col_src.R*col_dst.R, col_src.G*col_dst.G, col_src.B*col_dst.B);
    void setup() {
        // source image: draw an horizontal gradient and a filled circle
        src.fillScreenHGradient(RGB565_Purple, RGB565_Orange);
        src.fillCircle({ 100, 100 }, 80, RGB565_Salmon, RGB565_Black);
        // destination image: draw a vertical gradient
        dst.fillScreenVGradient(RGB565_Green, RGB565_White);
        // perform the blending of src over dst using the 'mult_op' operator
        dst.blend(src, { 60 , 20 }, mult_op);
       // *** add code to display dst on screen ***
    void loop() {
    As you can see, the blending operator does not need to have the same color type as the sprite and destination images (which can also have different color types!). Color conversion is performed automatically when needed. However, one should favor a blending operator with the same color types as the images as this will give the best performance. Here, I used the RGBf color type just because I was lazy and it was simpler to write the blending operator with floating point valued channels...

    Note also that the blending operator does not need to be a function, it can be a functor object so it can have an internal state. For example, here is a (stupid) custom blending operator that depends on some parameter p:

    struct MyBlend
        /* Set the p parameter in the constructor */
        MyBlend(float p) : _p(p) {}
        RGBf operator()(RGBf col_src, RGBf col_dst) const
          return RGBf(col_src.R*_p  + col_dst.R*(1-_p), col_src.G*(1-_p) + col_dst.G*_p , max(col_src.B,col_dst.B));
        float _p;
    Then, as in the example before, we can use this operator by simply calling 'blend()' with a temporary instance like so:
    // perform the blending of src over dst using the 'MyBlend' operator with param 0.7
    dst.blend(src, { 60 , 20 }, MyBlend(0.7f));

  11. #36
    I did a little more testing and everything seems to work nicely. However, since my previous post, I have changed the name of the new method from 'blend()' to 'blit()' because, in retrospect, it is just an extension of the already existing blit() method... [so, in the code of the post above, all references to 'blend' should be changed to 'blit'].

    I also extended the following 4 methods to support user-defined blending operators:

    1. BlitScaledRotated()
    2. copyFrom()
    3. drawTexturedTriangle()
    4. DrawTexturedQuad()

    Just as for blit(), the blending operator can be a function/functor/lambda. Note that, in the same spirit, the 'iterate()' method also takes an operator as parameter and applies it to each pixel of the image. All the doc/details for these methods can be found in the 'image.h' header file.

    For example, in order draw a 'sprite' image centered on 'im', rotated by 65 degrees, but blitting only the sprite's red channel, we can use the 'BlitScaledRotated()' method with a anonymous lambda function:
    im.blitScaledRotated(sprite, sprite.dim()/2, im.dim()/2, 1.0f, 65.0f, [](RGB565 src, RGB565 dst) { return RGB565(src.R, dst.G, dst.B); });
    I hope you will find these methods useful !

  12. #37
    I just noticed a function called "_drawCircleHelper" that appears to draw quarter circles for the rounded rectangles. is there a way to use it from the sketch? It would be quite handy for what i'm doing (drawing virtual "patch cables")

  13. #38

    The _drawCircleHelper() method is a legacy from the Adafruit GFX library and it is indeed used to draw quarter circles. It is not currently a public method but making it accessible would not a problem. However, you can already achieve the same result (and more !) by combining clipping with circle drawing methods. For example, below is a function that draws the bottom right quarter of a circle:
    template<typename color_t> 
    void drawBottomRightCorner(tgx::Image<color_t> & im, int x, int y, int r, color_t color)
    	tgx::Image<color_t> sub_im = im.getCrop({ x, x + r + 1, y, y + r + 1}); // create a sub image for clipping
    	sub_im.drawCircle({ 0,0 }, r, color); // draw the circle on the sub image 
    I did not perform benchmarking but I expect that this code should be almost as fast as the _drawCircleHelper(). Also, with this approach you can choose whether the endpoints are drawn (they are not drawn with _drawCircleHelper() and the same result can be obtained in the method above by replacing r+1 by r).

    BTW, I will certainly add methods for drawing Bezier curves and splines "soon" (for some good definition of soon..).

  14. #39
    Junior Member
    Join Date
    Sep 2021
    I did not know the teensy was so capable.
    Looks like you are too.

  15. #40
    Quote Originally Posted by vindar View Post

    //stuff about drawcirclehelper

    BTW, I will certainly add methods for drawing Bezier curves and splines "soon" (for some good definition of soon..).
    That is very cool, ill wait for that instead!

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts