librtprocess - quo vadis

Maybe tomorrow… Had to fix a bug in lmmse demosaic first…

shouldnt we just find out how to make it public?:wink:

1 Like

Ingo, how about the most obvious approach:

#include <iostream>
#include <string>

namespace
{

    class MyClass final
    {
    public:
        explicit MyClass(const std::string& _name) :
            name(_name)
        {
        }

        void identify() const
        {
            std::cout << "This is " << name << '.' << std::endl;
        }

    private:
        const std::string name;
    };

}


void cInterface(void (*func)(void*), void* data)
{
    func(data);
}

int main()
{
    MyClass my_class("me");

    cInterface(
        [](void* data)
        {
            static_cast<MyClass*>(data)->identify();
        },
        &my_class
    );

    return 0;
}

I see the point of having a ubiquitous C interface but don’t think C++ ABI stability is that much of a problem (given the Itanium C++ ABI and using PIMPL for classes) since most of the time you use the same or a compatible toolchain when building and linking the executable.

Just my 2¢,
Flössie

Looking at librtprocess.h, does C permit default parameters (which are bad even for C++ libraries)?

guys. please don’t do a c++ api with the vague hope that it may work most of the time. that’s a classic beginner’s mistake and i will deny i had anything to do with you if you commit it :slight_smile:

as to calling c++ from c interfaces. you’ll need c linkage, something like in a header

#pragma once

#ifdef __cplusplus

extern "C"

{

#endif

void c_api_entry_point();

#ifdef __cplusplus

}

#endif

the weird dance with the preprocessor is that the .h file can be included by c and by cpp files. cpp will declare the c-style linkage, while c will not see the extern “C” declaration.

and in the corresponding cpp file you’ll just implement the function as you normally would, but also need the extern “C” linkage:

#include "previousfile.h"

extern "C"

{

void c_api_entry_point()

{

// call into c++ stuff happily, this will be compiled by cpp compiler only

}

}

// note that the rest here has no "C" linkage:

template <class T>

T metaprogramming_madness()

{

//...

}

if that answers your question?

1 Like

Looks like I have been invited here.

  • I can only agree to use a C API
  • You need symbol versioning once you settled on an API, use abimap for that (copy cmake files from libssh, I can help if you have questions)
  • Use cmocka for creating unit tests (I’m the maintainer, ask if you need help)
  • Use modern cmake. I know cmake very well, ask if you have questions cmocka and libssh are good examples.
  • Run csbuild (all in one static analysis)
  • Don’t ask me about math, I forgot everything from university :wink:
1 Like

@hanatos As long as I can call things from Filmulator I’m fine with the interface being C.

@asn If you could advise us on the ways of cmake and conventions for libraries, I’d definitely appreciate the help. Could you check out this pull request?

1 Like

I’ve replied to the pull request. I would suggest to look at the cmocka and libssh project how to correctly build those libraries. If I find the time I could add all the stuff. I prefer gitlab over github as I find the CI much better.

Take a look at the cmocka pipeline: https://gitlab.com/cmocka/cmocka/pipelines/44452547

It compiles on different platforms, tests for memory leaks, undefined behaviour. It has static analysis running and we build also with visual studio. I could offer cycles on the Windows box with VS.

Libraw offers both a C and C++ API for its library. I use the C++ one, but it only really integrates well if linked statically. It may be an option to just tell folk that, C supports either dynamic/static linking, C++ static linking only.

Just wondering… how do we want to handle things like opencl/metal/vulkan/halide within the lib?

1 Like

I have set up a draft CI implementation for librtprocess. Currently it compiles the demosaic_source_folder branch of RT and generates an AppImage each time there is a new commit or pull request. Other RAW processors (like filmulator and photoflow) can be easily added as well.

The scripts are in my private fork. Should I make a pull request? In such case, we should also see how to integrate the official repository with travis…

I would like to merge RT dev into that branch before. But that will take a while. Maybe I can do this at the weekend, but not sure…

Fine with me. Nevertheless, I suppose the current status of the branch is good enough for checking whether librtprocess gives some compilation errors, right?

I have to check that first. Give me time until tomorrow to push to demosaic_source_folder branch… It may be that currently demosaic_source_folder does not match librtprocess

I would definitely appreciate having multiple computational backend options but I’m not at all sure if we want something like multiple dispatch, or actual different functions for different backends…

And then there’s the complication that Halide can have one function definition with multiple different implementations…

i think we’ll need to evaluate the compute backends some more. i recently tested vulkan headless compute shaders and thought that worked really well, even on an intel GPU via mesa. the api is pretty much like opencl. ideally that compiles to CPU code, too so there might not be the need for different code paths or bloated compiler backends. not sure it would be able to compete with hand-optimized SSE code (but that tends to rot because it’s hard to read and once avx2 avx512 etc comes around it will be suboptimal).

Okay, so I’ve cloned librtprocess, compiled and installed it, and with @heckflosse’s help cobbled together a rough integration into rawproc.

First rumination: Personally, I’d prefer to have the cmake options to enable/disable static and dynamic library building. For this sort of thing, I’d rather statically link it into my application, and I’m not seeing how having shared libraries will benefit the overall image processing architecture, e.g., RT, pFl, dt, rp, being able to use the same .so or .dll. FWIW, school me if you consider this thinking myopic.

Second rumination: First off, pardon me if I’m describing what most of you already understand, but I’m going to discuss this from the viewpoint of a user who, while C-proficient, may not have had to deal with C muilti-dimensional array conventions. Here’s the rawproc code segment to use librtprocess’ vng4_demosaic() in my gImage::ApplyDemosaic() method:

else if (algorithm == DEMOSAIC_VNG) {
		
		float **rawdata = (float **)malloc(h * sizeof(float *));
		rawdata[0] = (float *)malloc(w*h * sizeof(float));
		for (unsigned i=1; i<h; i++) 
			rawdata[i] = rawdata[i - 1] + w; 

		float **red     = (float **)malloc(h * sizeof(float *)); 
		red[0] = (float *)malloc(w*h * sizeof(float));
		for (unsigned i=1; i<h; i++) 
			red[i]     = red[i - 1] + w;

		float **green     = (float **)malloc(h * sizeof(float *)); 
		green[0] = (float *)malloc(w*h * sizeof(float));
		for (unsigned i=1; i<h; i++) 
			green[i]     = green[i - 1] + w;

		float **blue     = (float **)malloc(h * sizeof(float *)); 
		blue[0] = (float *)malloc(w*h * sizeof(float));
		for (unsigned i=1; i<h; i++) 
			blue[i]     = blue[i - 1] + w;

		for (unsigned y=0; y<h; y++) {
			for (unsigned x=0; x<w; x++) {
				unsigned pos = x + y*w;
				rawdata[y][x] = image[pos].r * 65535.f;
			}
		}
	
		vng4_demosaic (w, h, rawdata, red, green, blue, cfarray, f);
	
		for (unsigned y=0; y<h; y++) {
			for (unsigned x=0; x<w; x++) {
				unsigned pos = x + y*w;
				image[pos].r = red[y][x] /65535.f;
				image[pos].g = green[y][x] /65535.f;
				image[pos].b = blue[y][x] /65535.f;
			}
		}
		
		free (blue[0]);
		free( blue );
		free (green[0]);
		free( green );
		free (red[0]);
		free( red );
		free (rawdata[0]);
		free( rawdata );
	}

Note, image is gImage’s internal data structure, std::vector<pix>, pix is a struct { float r, float g, float b }. I don’t do one-channel data, for me monochrome is r=g=b. So, I’m just using the r component of the struct to load the C array. The demosaic algorithms require:

const float * const *rawData

which is the very fastest data organization for doing the demosaic, but not what most image libraries use. There’s a lot to get wrong in all those mallocs, so for a C interface I’d recommend including a couple of helper routines, e.g.,

float ** RT_malloc(unsigned width, unsigned height, size_t membersize);
void RT_free(void* ptr);

for bears-of-little-brain like me to use in preparing our data for the routines. @heckflosse has an alternate simplification, a C++ JaggedArray class, that works beautifully, but requires exposing jaggedarray.h in the interface and is a villified C++ thing. Here’s rcd_demosaic in rawproc, using it:

	else if (algorithm == DEMOSAIC_RCD) {

		librtprocess::JaggedArray<float> rawdata(w, h);
		librtprocess::JaggedArray<float> red(w, h);
		librtprocess::JaggedArray<float> green(w, h);
		librtprocess::JaggedArray<float> blue(w, h);
		
		for (unsigned y=0; y<h; y++) {
			for (unsigned x=0; x<w; x++) {
				unsigned pos = x + y*w;
				rawdata[y][x] = image[pos].r * 65535.f;
			}
		}
	
		rcd_demosaic (w, h, rawdata, red, green, blue, cfarray, f);
	
		for (unsigned y=0; y<h; y++) {
			for (unsigned x=0; x<w; x++) {
				unsigned pos = x + y*w;
				image[pos].r = red[y][x]   / 65535.f;
				image[pos].g = green[y][x] / 65535.f;
				image[pos].b = blue[y][x]  / 65535.f;
			}
		}
	}

A lot cleaner…

I’ve got some other thoughts about how to organize the interface, but let me see how you all respond to the above, first… :smile:

It does not require exposing it because it is not used in the interface. Just copy jaggedarray.h to your code if you want to use it :wink: