Page 2 of 3

Re: Native C module: accessing objects

Posted: Mon Sep 07, 2020 6:43 pm
by deshipu
pythoncoder wrote:
Mon Sep 07, 2020 8:07 am
I suspect this is all above my pay grade ;)
How about you just take the existing framebuf module, rename it and build on top of it?

Re: Native C module: accessing objects

Posted: Tue Sep 08, 2020 8:15 am
by pythoncoder
I was hoping to avoid having users build the firmware: this is a significant barrier for many people.

Re: Native C module: accessing objects

Posted: Tue Sep 08, 2020 8:12 pm
by chibill
If https://github.com/micropython/micropython/pull/5711 ever gets looked at this will be possible I think.

Re: Native C module: accessing objects

Posted: Tue Sep 08, 2020 11:52 pm
by jimmo
deshipu wrote:
Mon Sep 07, 2020 6:43 pm
How about you just take the existing framebuf module, rename it and build on top of it?
pythoncoder wrote:
Tue Sep 08, 2020 8:15 am
I was hoping to avoid having users build the firmware: this is a significant barrier for many people.
The underlying issue here is that without a loader/symbol table, there's no way for your .mpy to be able to know the location of the existing framebuf code. We work around this for the core functionality by providing mp_fun_table. It's not really clear what the best way to support this -- there's a long list of "core adjacent" functionality (like framebuf), as well as things like port-specific HAL functionality (see below).

However, to elaborate on deshipu's suggestion, a possible way to work around this is to duplicate any code from framebuf that you need. When your blit method gets called, you're passed two mp_obj_t that happen to point to two mp_obj_framebuf_t instances.

So nothing stops you providing your own copy of the mp_obj_framebuf_t struct definition inside yournativemodule.c, using MP_OBJ_TO_PTR to convert to that, and then you can write your own functions that operate on this struct. You might find that you end up having to make duplicate implementations of some of the "real" functions from modframebuf.c, so it's not ideal from an efficiency perspective.
chibill wrote:
Tue Sep 08, 2020 8:12 pm
If https://github.com/micropython/micropython/pull/5711 ever gets looked at this will be possible I think.
As it stands, this doesn't quite solve Peter's problem as it provides port-specific functionality, but the same mechanism could maybe be used.

Re: Native C module: accessing objects

Posted: Wed Sep 09, 2020 4:59 am
by pythoncoder
Thanks everyone - I will experiment...

Re: Native C module: accessing objects

Posted: Thu Sep 10, 2020 8:35 am
by deshipu
pythoncoder wrote:
Tue Sep 08, 2020 8:15 am
I was hoping to avoid having users build the firmware: this is a significant barrier for many people.
But framebuf is a loadable module now?

Re: Native C module: accessing objects

Posted: Thu Sep 10, 2020 10:04 am
by pythoncoder
It is indeed and I think I'm well on the way to doing this. I've managed to add an additional FrameBuffer.blit_x method which is currently just a copy of blit but I can run it on the Unix build and on STM. Now to rename it and make it do what I want...

It hadn't occurred to me that it was possible - indeed rather simple - to add a method yet retain all the existing functionality of the class. Very cunning.

Thanks all for the pointers :D

Awesome

Posted: Thu Sep 10, 2020 4:39 pm
by pythoncoder
Several hours on I remain gobsmacked at how easy that was. The docs had convinced me that adding a method to a native class was probably impossible. It actually took a few minutes: compiled and ran on the second attempt. It still strikes me as the closest thing to magic that I've seen in years... :lol:

Re: Native C module: accessing objects

Posted: Fri Sep 11, 2020 6:23 am
by pidou46
Would this "magic" could be used to bring unsupported esp32 periferics like "pcnt" without building a specific firmware ?

Not quite so simple: please help

Posted: Sun Sep 13, 2020 2:20 pm
by pythoncoder
Adding functionality to framebuf was easy, but using it is proving crash-prone. I'd appreciate some feedback as to whether my C code is legitimate before I raise an issue (code posted at end of this message).

I added a render(framebuf, row, col, fgcolor, bgcolor) method which renders a monochrome glyph to a color framebuf. This works, both in testing at the REPL and in my application but the board crashes after a number of iterations. I have reduced the failing Python code to this test case

Code: Select all

import framebuf_r
import framebuf
# Emulate a display driver subclassed from framebuf.FrameBuffer
db = bytearray(40 * 80 * 2)
device = framebuf.FrameBuffer(db, 40, 80, framebuf.RGB565)

def foo():
    width = 20  # Glyph dimensions
    height = 40
    i = 0
    while True:
        # Source monochrome glyph
        buf = bytearray(width * height // 8)
        fbc = framebuf_r.FrameBuffer(buf, width, height, framebuf_r.MONO_HMSB)
        # Destination: temporary color frame buffer for the glyph
        bufd = bytearray(width * height * 2)
        fbd = framebuf_r.FrameBuffer(bufd, width, height, framebuf_r.RGB565)
        # Render it in specified colors to the temporary buffer
        fbd.render(fbc, 0, 0, 0x5555, 0xaaaa)  # fbc must be a framebuf_r otherwise we get a TypeError
        # Instantiate a FrameBuffer pointed at color glyph
        # Cannot be a framebuf_r
        fbx = framebuf.FrameBuffer(bufd, width, height, framebuf.RGB565)
        device.blit(fbx, 0, 0)
        i += 1
        print(i)
This code runs on a Pyboard D SF2W for 93 iterations before crashing. There is evidence that the crash is caused by an allocation issue.

framebuf_r is my native C module. The rather convoluted code is because MicroPython evidently sees my FrameBuffer and the built-in FrameBuffer as different types. My attempt to use my type throughout failed because it seems that I can only subclass the display device from the built-in type. And I can't blit between the two types of FrameBuffer without getting a TypeError.

I don't know if this type conflict is to be expected - the code could be much simplified if it did not occur or if I could subclass the device from my FrameBuffer, using that class throughout.

Here is the C code. The render method is a trivial adaptation of blit. Comments welcome:

Code: Select all

#define MICROPY_PY_FRAMEBUF (1)

#include "py/dynruntime.h"

#if !defined(__linux__)
void *memset(void *s, int c, size_t n) {
    return mp_fun_table.memset_(s, c, n);
}
#endif

mp_obj_type_t mp_type_framebuf;

#include "extmod/modframebuf.c"

// render(mono_framebuf, x, y, fgcolor, bgcolor=0)
STATIC mp_obj_t framebuf_render(size_t n_args, const mp_obj_t *args) {
    mp_obj_framebuf_t *self = MP_OBJ_TO_PTR(args[0]);
    mp_obj_t source_in = mp_obj_cast_to_native_base(args[1], MP_OBJ_FROM_PTR(&mp_type_framebuf));
    if (source_in == MP_OBJ_NULL) {
        mp_raise_TypeError(NULL);
    }
    mp_obj_framebuf_t *source = MP_OBJ_TO_PTR(source_in);

    mp_int_t x = mp_obj_get_int(args[2]);
    mp_int_t y = mp_obj_get_int(args[3]);
    mp_int_t fgcol = mp_obj_get_int(args[4]);
    mp_int_t bgcol = 0;
    if (n_args > 5) {
        bgcol = mp_obj_get_int(args[5]);
    }

    if (
        (x >= self->width) ||
        (y >= self->height) ||
        (-x >= source->width) ||
        (-y >= source->height)
        ) {
        // Out of bounds, no-op.
        return mp_const_none;
    }

    // Clip.
    int x0 = MAX(0, x);
    int y0 = MAX(0, y);
    int x1 = MAX(0, -x);
    int y1 = MAX(0, -y);
    int x0end = MIN(self->width, x + source->width);
    int y0end = MIN(self->height, y + source->height);

    for (; y0 < y0end; ++y0) {
        int cx1 = x1;
        for (int cx0 = x0; cx0 < x0end; ++cx0) {
            uint32_t col = getpixel(source, cx1, y1);
            if (col == 0) {
                setpixel(self, cx0, y0, bgcol);
            }
            else {
                setpixel(self, cx0, y0, fgcol);
            }
            ++cx1;
        }
        ++y1;
    }
    return mp_const_none;
}
STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(framebuf_render_obj, 5, 6, framebuf_render);

mp_map_elem_t framebuf_locals_dict_table[11];
STATIC MP_DEFINE_CONST_DICT(framebuf_locals_dict, framebuf_locals_dict_table);

mp_obj_t mpy_init(mp_obj_fun_bc_t *self, size_t n_args, size_t n_kw, mp_obj_t *args) {
    MP_DYNRUNTIME_INIT_ENTRY

    mp_type_framebuf.base.type = (void*)&mp_type_type;
    mp_type_framebuf.name = MP_QSTR_FrameBuffer;
    mp_type_framebuf.make_new = framebuf_make_new;
    mp_type_framebuf.buffer_p.get_buffer = framebuf_get_buffer;
    framebuf_locals_dict_table[0] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_fill), MP_OBJ_FROM_PTR(&framebuf_fill_obj) };
    framebuf_locals_dict_table[1] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_fill_rect), MP_OBJ_FROM_PTR(&framebuf_fill_rect_obj) };
    framebuf_locals_dict_table[2] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_pixel), MP_OBJ_FROM_PTR(&framebuf_pixel_obj) };
    framebuf_locals_dict_table[3] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_hline), MP_OBJ_FROM_PTR(&framebuf_hline_obj) };
    framebuf_locals_dict_table[4] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_vline), MP_OBJ_FROM_PTR(&framebuf_vline_obj) };
    framebuf_locals_dict_table[5] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_rect), MP_OBJ_FROM_PTR(&framebuf_rect_obj) };
    framebuf_locals_dict_table[6] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_line), MP_OBJ_FROM_PTR(&framebuf_line_obj) };
    framebuf_locals_dict_table[7] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_blit), MP_OBJ_FROM_PTR(&framebuf_blit_obj) };
    framebuf_locals_dict_table[8] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_scroll), MP_OBJ_FROM_PTR(&framebuf_scroll_obj) };
    framebuf_locals_dict_table[9] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_text), MP_OBJ_FROM_PTR(&framebuf_text_obj) };
    framebuf_locals_dict_table[10] = (mp_map_elem_t){ MP_OBJ_NEW_QSTR(MP_QSTR_render), MP_OBJ_FROM_PTR(&framebuf_render_obj) };
    mp_type_framebuf.locals_dict = (void*)&framebuf_locals_dict;

    mp_store_global(MP_QSTR_FrameBuffer, MP_OBJ_FROM_PTR(&mp_type_framebuf));
    mp_store_global(MP_QSTR_FrameBuffer1, MP_OBJ_FROM_PTR(&legacy_framebuffer1_obj));
    mp_store_global(MP_QSTR_MVLSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_MVLSB));
    mp_store_global(MP_QSTR_MONO_VLSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_MVLSB));
    mp_store_global(MP_QSTR_RGB565, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_RGB565));
    mp_store_global(MP_QSTR_GS2_HMSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_GS2_HMSB));
    mp_store_global(MP_QSTR_GS4_HMSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_GS4_HMSB));
    mp_store_global(MP_QSTR_GS8, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_GS8));
    mp_store_global(MP_QSTR_MONO_HLSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_MHLSB));
    mp_store_global(MP_QSTR_MONO_HMSB, MP_OBJ_NEW_SMALL_INT(FRAMEBUF_MHMSB));

    MP_DYNRUNTIME_INIT_EXIT
}