GNOME Bugzilla – Bug 707196
pygobject depends on unmaintained software
Last modified: 2017-04-20 08:47:55 UTC
pygobject depends on unmaintained and outdated Python-Cairo binding(pycairo). The current version of pycairo lacks many important features(RecordingSurface, ImageSurface.get_data(), cairo_region_t, etc.) that have been introduced to Cairo recently. This makes pygobject actually worse than pygtk.
Should we move PyGObject to use cairocffi? http://pythonhosted.org/cairocffi
It looks like cairocffi is in Fedora 18 and 19: https://bugzilla.redhat.com/show_bug.cgi?id=986715 But I could not find anything for debian/ubuntu. So this would need either a configure switch or runtime check for probably a great while. ./configure --with-cairo-cffi or import gi gi.enable_cairo_cffi() Furthermore, there is no C API for cairocffi which we would need to handle by some other means (implement a light C wrapper which interacts with cairocffi): http://www.cairographics.org/documentation/pycairo/3/pycairo_c_api.html
I fear cairocffi will be too slow and our efforts might be better spent trying to get an updated release of pycairo out. I think it would be a serious performance regression for anyone writing custom controls or viewports which rely on cairo drawing. I'm seeing about a 6 times slow down running the simple tutorial: http://cairographics.org/pycairo/tutorial/ pycairo: $ time python3 example.py real 0m0.034s user 0m0.032s sys 0m0.000s caircffi: $ time python3 example.py real 0m0.211s user 0m0.192s sys 0m0.016s
Hi. cairocffi developer here. I don’t think Debian packages should stop you from using cairocffi. It’s pure Python itself and thus should be very easy to packages, and other than cairo and Python only requires CFFI, which itself is in Debian. (Though admittedly not in stable.) As to the C API: what exactly does pygobject need? If you’re willing to make changes to pygobject to make this work, I think we can come up with a subset (or equivalent thereof) that can be safely implemented in cairocffi. As to performance, you’re also measuring start up. cairocffi parses a bunch of C declarations on startup (which takes some constant amount of time), but after that performs fine in my experience. The following benchmark (largely taken from the tutorial but not measuring startup) gives a difference between cairocffi and pycairo that is within the measuring error margin. #!/usr/bin/env python import io import math import timeit import cairocffi as cairo def run(): WIDTH, HEIGHT = 256, 256 surface = cairo.ImageSurface (cairo.FORMAT_ARGB32, WIDTH, HEIGHT) ctx = cairo.Context (surface) ctx.scale (WIDTH, HEIGHT) # Normalizing the canvas pat = cairo.LinearGradient (0.0, 0.0, 0.0, 1.0) pat.add_color_stop_rgba (1, 0.7, 0, 0, 0.5) # First stop, 50% opacity pat.add_color_stop_rgba (0, 0.9, 0.7, 0.2, 1) # Last stop, 100% opacity ctx.rectangle (0, 0, 1, 1) # Rectangle(x0, y0, x1, y1) ctx.set_source (pat) ctx.fill () ctx.translate (0.1, 0.1) # Changing the current transformation matrix ctx.move_to (0, 0) ctx.arc (0.2, 0.1, 0.1, -math.pi/2, 0) # Arc(cx, cy, radius, start_angle, stop_angle) ctx.line_to (0.5, 0.1) # Line to (x,y) ctx.curve_to (0.5, 0.2, 0.5, 0.4, 0.2, 0.8) # Curve(x1, y1, x2, y2, x3, y3) ctx.close_path () ctx.set_source_rgb (0.3, 0.2, 0.5) # Solid color ctx.set_line_width (0.02) ctx.stroke () file_obj = io.BytesIO() # Bypass the filesystem to avoid measuring IO. surface.write_to_png (file_obj) # Output to PNG print(min(timeit.repeat(stmt=run, repeat=3, number=100)))
Thanks for commenting. Startup is something we need to consider since OLPC uses PyGObject and we have done a lot of work optimizing startup times in general. I think these are fairly low end machines (latest gen seems to be single core 800mhz, 512mb ram) so even small performance differences on a desktop can have big effect. https://bugzilla.gnome.org/showdependencytree.cgi?id=693243&hide_resolved=0 With startup out of the way as you've posted, it basically becomes IO bound which wouldn't be a fair test for custom control drawing or more importantly viewport/clutter drawing. So by removing the IO and context creation, we have a more representative test for what a widget "draw" signal might experience (just draw call marshaling differences). This starts to show a bigger performance difference. But I might be splitting hairs with that, so perhaps what we need viewport frame rates of something more complex to make any kind of judgement. In any event, I think pypy based technologies is a good direction and I'm not opposed to cairocffi integration generally speaking. We just need to work through performance and distribution issues and support it through an explicit switch app developers need to set. This is why I was attempting the approach of mimicking the Pycairo API inside of cairocffi as it is the least intrusive and most re-usable approach, but I don't think it can ever be complete. In terms of what we need as an API, we have a foreign object marshaler specifically for cairo: https://git.gnome.org/browse/pygobject/tree/gi/pygi-foreign-cairo.c
I started discussing caching the results of parsing C declarations with the CFFI developers, to improve the startup time. https://bitbucket.org/cffi/cffi/issue/26/ I think that pygi-foreign-cairo.c could work by using cairocffi’s ._pointer and ._from_pointer(). I would be much more confident in that than in a simulated C API.
(To deal with CFFI not having a C API, I could add ._get_address() and ._from_address() on cairocffi wrappers that work with Python integers, which pygi-foreign-cairo.c could use by casting between C pointers and uintptr_t.)
(In reply to comment #7) > (To deal with CFFI not having a C API, I could add ._get_address() and > ._from_address() on cairocffi wrappers that work with Python integers, which > pygi-foreign-cairo.c could use by casting between C pointers and uintptr_t.) That would be helpful, thanks!
I see that that file binds on cairo.Path objects. cairocffi does not have Path objects but just converts paths to lists if tuples, and back. Do you think it is useful to have actual Path objects? What in PyGObject uses them? (Maybe what introspected API?)
Actually I’m hesitant with having ._get_address() and ._from_address() in cairocffi itself, as they shouldn’t be used IMO unless one knows what they’re doing. There’re quite easy to do as well: Getting the address: int(cairocffi.ffi.cast('uintptr_t', context._pointer)) Make a new wrapper: Context._from_pointer(cairocffi.ffi.cdata('cairo_t*', address)) Yes, this is a bit more verbose in C with CPython’s C API than in Python, but still doable and not terribly complex.
I'm a little lost with all the cairo options. What is that pygobject gains from the pycairo dependency? What does cairo-gobject's cairo-1.0.gir provide (or lack)? What is lost if one compiles pygobject with --disable-cairo?
Patrick: Roughly, PyGObject uses GObject introspection to automatically create Python bindings for GObject types and API. This doesn’t just work for cairo, because cairo does not use GObject but has an API based on pointers to plain C structs. Therefore, we have bindings for Python such as pycairo (which is unfortunately unmaintained) and cairocffi (which I wrote as a replacement for pycairo.) When cairo types are involved in a GObject API (for example gdk_cairo_create() returns a cairo_t*), PyGObject needs to know how to convert between C pointers to cairo structs and Python objects that wrap them. In current versions, there is code that compiles against pycairo’s C API in order to do that. https://developer.gnome.org/gdk3/stable/gdk3-Cairo-Interaction.html#gdk-cairo-create https://git.gnome.org/browse/pygobject/tree/gi/pygi-foreign-cairo.c That code is (I suppose) what is disabled by --disable-cairo. With --disable-cairo, one can not use from Python any API that involves cairo structs. This bug is about replacing that code with something that uses cairocffi instead of pycairo.
Thank you for the explanation! Have you any thoughts on my second question: > What does cairo-gobject's cairo-1.0.gir provide (or lack)?
I have no idea what cairo-1.0.gir does or if it’s useful at all to PyGObject, maybe someone else can explain.
(In reply to comment #11) > What is lost if one compiles pygobject with --disable-cairo? The lost functionality I know about would be custom drawing in both GTK+ and Clutter: https://developer.gnome.org/gtk3/stable/GtkWidget.html#GtkWidget-draw https://developer.gnome.org/clutter/stable/ClutterCanvas.html#ClutterCanvas-draw I'm sure there is more, but if you aren't using doing custom widget/viewport drawing I'm sure you won't be missing much. Also note that the dependency is only needed for a seperate .so plugin to PyGObject (_gi_cairo.cpython-33m.so) and is packaged separately in debian at least (python-gi-cairo).
This ticket makes a lot of sense - I've just been porting some code to gtk3 and cairocffi. The situation becomes even more confusing when you start to use 'pgi' as well as being compatible with cairocffi. 'pgi' seems to be compatible with cairocffi, which is great - hopefully we can replace pygoject with it one day - unfortunately it doesn't support all bindings right now, so I need to support pygobject as well. My code has to 1 - detect if using 'gi' 2 - if (1) convert between pycairo contexts and matrixes (for Gtk consumption) in many places. In my ideal world, the pygobject guys would have a hackfest with the 'pgi' guys and get that up to speed and replace pygobject with it (being based on cffi it can work on pypy and other python implementations). In the meantime getting support for cairocffi is sorely needed.
> > I started discussing caching the results of parsing C declarations with the > CFFI developers, to improve the startup time. > > https://bitbucket.org/cffi/cffi/issue/26/ On that bug report it looks like they implemented this in CFFI 1.0: "This is exactly what cffi 1.0 did with its new out-of-line mode (both for ABI and API level)." Which looks like it could resolve the performance issues mentioned above.
I've tried to move things forward for pycairo by doing an external release: https://lists.cairographics.org/archives/cairo/2017-April/027919.html
Proposal to make pygobject depend on the fork: https://mail.gnome.org/archives/desktop-devel-list/2017-April/msg00065.html and for the release team: https://mail.gnome.org/archives/release-team/2017-April/msg00048.html
Created attachment 350067 [details] [review] Bump pycairo requirement to 1.11.1 This requires pycairo from https://pycairo.readthedocs.io/en/latest/ (already updated/included in JHBuild) For more info on the upstream change see: https://lists.cairographics.org/archives/cairo/2017-April/027919.html https://mail.gnome.org/archives/desktop-devel-list/2017-April/msg00065.html This will enable cairo.Region support for all setups and Python versions as well as make moving to Python 3 easier for applications since all APIs provided under Python 2 are available there as well now.