std::shared_ptr<SpectraCalculator> SpectraGetDefaultGPUFourierTransf(
  SpectraSharedObjects& shared_objects, std::size_t spectrum_size) {
    std::size_t frame_size = spectrum_size * 2 - 1;
    shared_objects.GLCanvas()->SetCurrent(*shared_objects.GLContext());
    assert(spectrum_size > 2);
    return std::make_shared<SpectraDefaultGPUMatrixTransf>(
      shared_objects,
      frame_size,
      spectrum_size,
      "Discrete Complex Fourier Transform (GPU)",
      SpectraFourierMatrixGen(frame_size, spectrum_size));
}
SpectraDefaultGPUMatrixTransf::SpectraDefaultGPUMatrixTransf(
	SpectraSharedObjects& shared_objects,
	std::size_t in_sz,
	std::size_t out_sz,
	const std::string& transf_name,
	const std::function<
		std::complex<double>(
			std::size_t,
			std::size_t,
			std::size_t,
			std::size_t
		)
	>& gen
): gl_context(shared_objects.GLContext())
 , gl_canvas(shared_objects.GLCanvas())
 , max_transforms(4)
 , current_transform(0)
 , in_size(in_sz)
 , out_size(out_sz)
 , name(transf_name)
 , transf_prog(shared_objects.BuildProgramWithXFB("xfb_matrix_transf.prog", "Output"))
 , prog_input_offs(transf_prog, "InputOffs")
 , prog_input_size(transf_prog, "InputSize")
 , prog_input_data(transf_prog, "InputData")
 , prog_matrix_data(transf_prog, "MatrixData")
 , output_bufs(max_transforms)
 , xfbs(max_transforms)
 , queries(max_transforms)
{
	assert(gl_context);
	assert(gl_canvas);

	std::vector<GLfloat> mat_data(in_size*out_size*2);
	auto m = mat_data.begin();
	for(std::size_t row=0; row!=out_size; ++row)
	{
		for(std::size_t col=0; col!=in_size; ++col)
		{
			std::complex<double> v = gen(col, row, in_size, out_size);
			*m = GLfloat(v.real()); ++m;
			*m = GLfloat(v.imag()); ++m;
		}
	}
	assert(m == mat_data.end());

	using namespace oglplus;

	matrix_buf.Bind(Buffer::Target::Texture);
	Buffer::Data(Buffer::Target::Texture, mat_data);
	Texture::Active(0);
	matrix_tex.Bind(Texture::Target::Buffer);
	Texture::Buffer(
		Texture::Target::Buffer,
		PixelDataInternalFormat::RG32F,
		matrix_buf
	);

	std::vector<GLfloat> init_data(in_size*max_transforms, 0.0f);

	input_buf.Bind(Buffer::Target::Texture);
	Buffer::Data(Buffer::Target::Texture, init_data);
	Texture::Active(1);
	input_tex.Bind(Texture::Target::Buffer);
	Texture::Buffer(
		Texture::Target::Buffer,
		PixelDataInternalFormat::R32F,
		input_buf
	);

	init_data.resize(out_size, 0.0f);

	vao.Bind();
	init_buf.Bind(Buffer::Target::Array);
	Buffer::Data(Buffer::Target::Array, init_data);
	(transf_prog|"Initial").Setup<GLfloat>().Enable();

	VertexArray::Unbind();

	for(GLuint t=0; t!=max_transforms; ++t)
	{
		xfbs[t].Bind();

		output_bufs[t].Bind(Buffer::Target::TransformFeedback);
		Buffer::Data(
			Buffer::Target::TransformFeedback,
			init_data,
			Buffer::Property::Usage::DynamicCopy
		);
		output_bufs[t].BindBase(
			Buffer::IndexedTarget::TransformFeedback,
			0
		);
	}
}