Running the compiled pipeline#
// Halide tutorial lesson 10: AOT compilation: running the compiled pipeline
// Before reading this file, see lesson_10_aot_compilation_generate.cpp
// This is the code that actually uses the Halide pipeline we've
// compiled. It does not depend on libHalide, so we won't be including
// Halide.h.
//
// Instead, it depends on the header file that lesson_10_generate
// produced when we ran it:
#include "lesson_10_halide.h"
// We want to continue to use our Halide::Buffer with AOT-compiled
// code, so we explicitly include it. It's a header-only class, and
// doesn't require libHalide.
#include "HalideBuffer.h"
#include <cstdio>
int main() {
// Have a look in the header file above (it won't exist until you've run
// lesson_10_generate). At the bottom is the signature of the function we generated:
// int brighter(halide_buffer_t *_input_buffer, uint8_t _offset, halide_buffer_t *_brighter_buffer);
// The ImageParam inputs have become pointers to "halide_buffer_t"
// structs. This is struct that Halide uses to represent arrays of
// data. Unless you're calling the Halide pipeline from pure C
// code, you don't want to use it
// directly. Halide::Runtime::Buffer is a simple wrapper around
// halide_buffer_t that will implicitly convert to a
// halide_buffer_t *. We will pass Halide::Runtime::Buffer objects
// in those slots.
// The Halide::Buffer class we have been using in JIT code is in
// fact just a shared pointer to the simpler
// Halide::Runtime::Buffer class. They share the same API.
// Finally, the return value of "brighter" is an error code. It's
// zero on success.
// Let's make a buffer for our input and output.
Halide::Runtime::Buffer<uint8_t> input(640, 480), output(640, 480);
// Halide::Runtime::Buffer also has constructors that wrap
// existing data instead of allocating new memory. Use these if
// you have your own Image type that you want to use.
int offset = 5;
int error = brighter(input, offset, output);
if (error != 0) {
printf("Halide returned an error: %d\n", error);
return -1;
}
// Now let's check the filter performed as advertised. It was
// supposed to add the offset to every input pixel.
for (int y = 0; y < 480; y++) {
for (int x = 0; x < 640; x++) {
uint8_t input_val = input(x, y);
uint8_t output_val = output(x, y);
uint8_t correct_val = input_val + offset;
if (output_val != correct_val) {
printf("output(%d, %d) was %d instead of %d\n",
x, y, output_val, correct_val);
return -1;
}
}
}
// Everything worked!
printf("Success!\n");
return 0;
}
# Before reading this file, see lesson_10_aot_compilation_generate.py
# This is the code that actually uses the Halide pipeline we've
# compiled. It does not depend on libHalide, so we won't do
# "import halide".
#
# Instead, it depends on the header file that lesson_10_generate
# produced when we ran it:
import lesson_10_halide
import numpy as np
def main():
# Have a look at the generated files above (they won't exist until you've run
# lesson_10_generate): lesson_10_halide.py.cpp, lesson_10_halide.h
#
# In the header file, the generated function is represented like this:
# int lesson_10_halide(halide_buffer_t*, uint8_t, halide_buffer_t*);
#
# lesson_10_halide.py.cpp creates a Python wrapper around this function.
# Buffers are converted using the Python buffer API:
#
# https://docs.python.org/2/c-api/buffer.html
# https://docs.python.org/3/c-api/buffer.html
#
# In other words, you can pass numpy arrays directly to the generated
# code.
# Let's make some input data to test with. Note that when a numpy array is
# passed to the generated code, its axes are always reversed.
input = np.empty((640, 480), dtype=np.uint8)
for y in range(480):
for x in range(640):
input[x, y] = (x ^ (y + 1)) & 0xFF
# And the memory where we want to write our output:
output = np.empty((640, 480), dtype=np.uint8)
offset_value = 5
lesson_10_halide.lesson_10_halide(input, offset_value, output)
# Now let's check the filter performed as advertised. It was
# supposed to add the offset to every input pixel.
correct_val = np.empty((1), dtype=np.uint8)
for y in range(480):
for x in range(640):
input_val = input[x, y]
output_val = output[x, y]
correct_val[0] = input_val
# we add over a uint8 value (will properly model overflow)
correct_val[0] += offset_value
assert output_val == correct_val[0], (
f"output({x}, {y}) was {output_val} instead of {correct_val}"
)
# Everything worked!
print("Success!")
return 0
if __name__ == "__main__":
main()