https://github.com/halide/Halide
Tip revision: fb39d7e3149c0ee1e848bb9957be2ea18765c35d authored by Derek Gerstmann on 15 November 2021, 21:02:02 UTC
Bump latest version to v13.0.1 in README.md
Bump latest version to v13.0.1 in README.md
Tip revision: fb39d7e
lesson_15_generators.cpp
// Halide tutorial lesson 15: Generators part 1
// This lesson demonstrates how to encapsulate Halide pipelines into
// reusable components called generators.
// On linux, you can compile and run it like so:
// g++ lesson_15*.cpp <path/to/tools/halide_image_io.h>/GenGen.cpp -g -std=c++17 -fno-rtti -I <path/to/Halide.h> -L <path/to/libHalide.so> -lHalide -lpthread -ldl -o lesson_15_generate
// bash lesson_15_generators_usage.sh
// On os x:
// g++ lesson_15*.cpp <path/to/tools/halide_image_io.h>/GenGen.cpp -g -std=c++17 -fno-rtti -I <path/to/Halide.h> -L <path/to/libHalide.so> -lHalide -o lesson_15_generate
// bash lesson_15_generators_usage.sh
// If you have the entire Halide source tree, you can also build it by
// running:
// make tutorial_lesson_15_generators
// in a shell with the current directory at the top of the halide
// source tree.
#include "Halide.h"
#include <stdio.h>
using namespace Halide;
// Generators are a more structured way to do ahead-of-time
// compilation of Halide pipelines. Instead of writing an int main()
// with an ad-hoc command-line interface like we did in lesson 10, we
// define a class that inherits from Halide::Generator.
class MyFirstGenerator : public Halide::Generator<MyFirstGenerator> {
public:
// We declare the Inputs to the Halide pipeline as public
// member variables. They'll appear in the signature of our generated
// function in the same order as we declare them.
Input<uint8_t> offset{"offset"};
Input<Buffer<uint8_t>> input{"input", 2};
// We also declare the Outputs as public member variables.
Output<Buffer<uint8_t>> brighter{"brighter", 2};
// Typically you declare your Vars at this scope as well, so that
// they can be used in any helper methods you add later.
Var x, y;
// We then define a method that constructs and return the Halide
// pipeline:
void generate() {
// In lesson 10, here is where we called
// Func::compile_to_file. In a Generator, we just need to
// define the Output(s) representing the output of the pipeline.
brighter(x, y) = input(x, y) + offset;
// Schedule it.
brighter.vectorize(x, 16).parallel(y);
}
};
// We compile this file along with tools/GenGen.cpp. That file defines
// an "int main(...)" that provides the command-line interface to use
// your generator class. We need to tell that code about our
// generator. We do this like so:
HALIDE_REGISTER_GENERATOR(MyFirstGenerator, my_first_generator)
// If you like, you can put multiple Generators in the one file. This
// could be a good idea if they share some common code. Let's define
// another more complex generator:
class MySecondGenerator : public Halide::Generator<MySecondGenerator> {
public:
// This generator will take some compile-time parameters
// too. These let you compile multiple variants of a Halide
// pipeline. We'll define one that tells us whether or not to
// parallelize in our schedule:
GeneratorParam<bool> parallel{"parallel", /* default value */ true};
// ... and another representing a constant scale factor to use:
GeneratorParam<float> scale{"scale",
1.0f /* default value */,
0.0f /* minimum value */,
100.0f /* maximum value */};
// You can define GeneratorParams of all the basic scalar
// types. For numeric types you can optionally provide a minimum
// and maximum value, as we did for scale above.
// You can also define GeneratorParams for enums. To make this
// work you must provide a mapping from strings to your enum
// values.
enum class Rotation { None,
Clockwise,
CounterClockwise };
GeneratorParam<Rotation> rotation{"rotation",
/* default value */
Rotation::None,
/* map from names to values */
{{"none", Rotation::None},
{"cw", Rotation::Clockwise},
{"ccw", Rotation::CounterClockwise}}};
// We'll use the same Inputs as before:
Input<uint8_t> offset{"offset"};
Input<Buffer<uint8_t>> input{"input", 2};
// And a similar Output. Note that we don't specify a type for the Buffer:
// at compile-time, we must specify an explicit type via the "output.type"
// GeneratorParam (which is implicitly defined for this Output).
Output<Buffer<>> output{"output", 2};
// And we'll declare our Vars here as before.
Var x, y;
void generate() {
// Define the Func. We'll use the compile-time scale factor as
// well as the runtime offset param.
Func brighter;
brighter(x, y) = scale * (input(x, y) + offset);
// We'll possibly do some sort of rotation, depending on the
// enum. To get the value of a GeneratorParam, cast it to the
// corresponding type. This cast happens implicitly most of
// the time (e.g. with scale above).
Func rotated;
switch ((Rotation)rotation) {
case Rotation::None:
rotated(x, y) = brighter(x, y);
break;
case Rotation::Clockwise:
rotated(x, y) = brighter(y, 100 - x);
break;
case Rotation::CounterClockwise:
rotated(x, y) = brighter(100 - y, x);
break;
}
// We'll then cast to the desired output type.
output(x, y) = cast(output.type(), rotated(x, y));
// The structure of the pipeline depended on the generator
// params. So will the schedule.
// Let's start by vectorizing the output. We don't know the
// type though, so it's hard to pick a good factor. Generators
// provide a helper called "natural_vector_size" which will
// pick a reasonable factor for you given the type and the
// target you're compiling to.
output.vectorize(x, natural_vector_size(output.type()));
// Now we'll possibly parallelize it:
if (parallel) {
output.parallel(y);
}
// If there was a rotation, we'll schedule that to occur per
// scanline of the output and vectorize it according to its
// type.
if (rotation != Rotation::None) {
rotated
.compute_at(output, y)
.vectorize(x, natural_vector_size(rotated.output_types()[0]));
}
}
};
// Register our second generator:
HALIDE_REGISTER_GENERATOR(MySecondGenerator, my_second_generator)
// After compiling this file, see how to use it in
// lesson_15_generators_build.sh