Native C module: accessing objects. [SOLVED]

C programming, build, interpreter/VM.
Target audience: MicroPython Developers.
User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: Native C module: accessing objects

Post by deshipu » Mon Sep 07, 2020 6:43 pm

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?

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Native C module: accessing objects

Post by pythoncoder » 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.
Peter Hinch
Index to my micropython libraries.

chibill
Posts: 47
Joined: Thu Oct 31, 2019 10:44 pm

Re: Native C module: accessing objects

Post by chibill » 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.

User avatar
jimmo
Posts: 2754
Joined: Tue Aug 08, 2017 1:57 am
Location: Sydney, Australia
Contact:

Re: Native C module: accessing objects

Post by jimmo » Tue Sep 08, 2020 11:52 pm

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.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Native C module: accessing objects

Post by pythoncoder » Wed Sep 09, 2020 4:59 am

Thanks everyone - I will experiment...
Peter Hinch
Index to my micropython libraries.

User avatar
deshipu
Posts: 1388
Joined: Thu May 28, 2015 5:54 pm

Re: Native C module: accessing objects

Post by deshipu » Thu Sep 10, 2020 8:35 am

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?

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Re: Native C module: accessing objects

Post by pythoncoder » Thu Sep 10, 2020 10:04 am

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
Peter Hinch
Index to my micropython libraries.

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Awesome

Post by pythoncoder » Thu Sep 10, 2020 4:39 pm

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:
Peter Hinch
Index to my micropython libraries.

pidou46
Posts: 101
Joined: Sat May 28, 2016 7:01 pm

Re: Native C module: accessing objects

Post by pidou46 » Fri Sep 11, 2020 6:23 am

Would this "magic" could be used to bring unsupported esp32 periferics like "pcnt" without building a specific firmware ?
nodemcu V2 (amica)
micropython firmware Daily build 05/31/2016

User avatar
pythoncoder
Posts: 5956
Joined: Fri Jul 18, 2014 8:01 am
Location: UK
Contact:

Not quite so simple: please help

Post by pythoncoder » Sun Sep 13, 2020 2:20 pm

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
}
Peter Hinch
Index to my micropython libraries.

Post Reply