// Copyright 2019 The Marl Authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // https://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // This is an example application that uses Marl to parallelize the calculation // of a Julia fractal. #include "marl/defer.h" #include "marl/scheduler.h" #include "marl/thread.h" #include "marl/waitgroup.h" #include #include #include // A color formed from a red, green and blue component. template struct Color { T r, g, b; inline Color& operator+=(const Color& rhs) { r += rhs.r; g += rhs.g; b += rhs.b; return *this; } inline Color& operator/=(T rhs) { r /= rhs; g /= rhs; b /= rhs; return *this; } }; // colorize returns a 'rainbow-color' for the scalar v. inline Color colorize(float v) { constexpr float PI = 3.141592653589793f; constexpr float PI_2_THIRDS = 2.0f * PI / 3.0f; return Color{ 0.5f + 0.5f * cosf(v + 0 * PI_2_THIRDS), 0.5f + 0.5f * cosf(v + 1 * PI_2_THIRDS), 0.5f + 0.5f * cosf(v + 2 * PI_2_THIRDS), }; } // lerp returns the linear interpolation between min and max using the weight x. inline float lerp(float x, float min, float max) { return min + x * (max - min); } // julia calculates the Julia-set fractal value for the given coordinate and // constant. See https://en.wikipedia.org/wiki/Julia_set for more information. Color julia(float x, float y, float cx, float cy) { for (int i = 0; i < 1000; i++) { if (x * x + y * y > 4) { return colorize(sqrtf(static_cast(i))); } auto xtemp = x * x - y * y; y = 2 * x * y + cy; x = xtemp + cx; } return {}; } // writeBMP writes the given image as a bitmap to the given file, returning // true on success and false on error. bool writeBMP(const Color* texels, int width, int height, const char* path) { auto file = fopen(path, "wb"); if (!file) { fprintf(stderr, "Could not open file '%s'\n", path); return false; } defer(fclose(file)); bool ok = true; auto put1 = [&](uint8_t val) { ok = ok && fwrite(&val, 1, 1, file) == 1; }; auto put2 = [&](uint16_t val) { put1(static_cast(val)); put1(static_cast(val >> 8)); }; auto put4 = [&](uint32_t val) { put2(static_cast(val)); put2(static_cast(val >> 16)); }; const uint32_t padding = -(3 * width) & 3U; // in bytes const uint32_t stride = 3 * width + padding; // in bytes const uint32_t offset = 54; // Bitmap file header put1('B'); // header field put1('M'); put4(offset + stride * height); // size in bytes put4(0); // reserved put4(offset); // BITMAPINFOHEADER put4(40); // size of header in bytes put4(width); // width in pixels put4(height); // height in pixels put2(1); // number of color planes put2(24); // bits per pixel put4(0); // compression scheme (none) put4(0); // size put4(72); // horizontal resolution put4(72); // vertical resolution put4(0); // color pallete size put4(0); // 'important colors' count for (int y = height - 1; y >= 0; y--) { for (int x = 0; x < width; x++) { auto& texel = texels[x + y * width]; put1(texel.b); put1(texel.g); put1(texel.r); } for (uint32_t i = 0; i < padding; i++) { put1(0); } } return ok; } // Constants used for rendering the fractal. constexpr uint32_t imageWidth = 2048; constexpr uint32_t imageHeight = 2048; constexpr int samplesPerPixelW = 3; constexpr int samplesPerPixelH = 3; constexpr float windowMinX = -0.5f; constexpr float windowMaxX = +0.5f; constexpr float windowMinY = -0.5f; constexpr float windowMaxY = +0.5f; constexpr float cx = -0.8f; constexpr float cy = 0.156f; int main() { // Create a marl scheduler using the full number of logical cpus. // Bind this scheduler to the main thread so we can call marl::schedule() marl::Scheduler scheduler(marl::Scheduler::Config::allCores()); scheduler.bind(); defer(scheduler.unbind()); // unbind before destructing the scheduler. // Allocate the image. auto pixels = new Color[imageWidth * imageHeight]; defer(delete[] pixels); // free memory before returning. // Create a wait group that will be used to synchronize the tasks. // The wait group is constructed with an initial count of imageHeight as // there will be a total of imageHeight tasks. marl::WaitGroup wg(imageHeight); // For each line of the image... for (uint32_t y = 0; y < imageHeight; y++) { // Schedule a task to calculate the image for this line. // These may run concurrently across hardware threads. marl::schedule([=] { // Before this task returns, decrement the wait group counter. // This is used to indicate that the task is done. defer(wg.done()); for (uint32_t x = 0; x < imageWidth; x++) { // Calculate the fractal pixel color. Color color = {}; // Take a number of sub-pixel samples. for (int sy = 0; sy < samplesPerPixelH; sy++) { auto fy = float(y) + (sy / float(samplesPerPixelH)); auto dy = float(fy) / float(imageHeight); for (int sx = 0; sx < samplesPerPixelW; sx++) { auto fx = float(x) + (sx / float(samplesPerPixelW)); auto dx = float(fx) / float(imageWidth); color += julia(lerp(dx, windowMinX, windowMaxX), lerp(dy, windowMinY, windowMaxY), cx, cy); } } // Average the color. color /= samplesPerPixelW * samplesPerPixelH; // Write the pixel out to the image buffer. pixels[x + y * imageWidth] = {static_cast(color.r * 255), static_cast(color.g * 255), static_cast(color.b * 255)}; } }); } // Wait until all image lines have been calculated. wg.wait(); // Write the image to "fractal.bmp". if (!writeBMP(pixels, imageWidth, imageHeight, "fractal.bmp")) { return 1; } // All done. return 0; }