| // 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 <fstream> |
| |
| #include <math.h> |
| #include <stdint.h> |
| |
| // A color formed from a red, green and blue component. |
| template <typename T> |
| struct Color { |
| T r, g, b; |
| |
| inline Color<T>& operator+=(const Color<T>& rhs) { |
| r += rhs.r; |
| g += rhs.g; |
| b += rhs.b; |
| return *this; |
| } |
| |
| inline Color<T>& operator/=(T rhs) { |
| r /= rhs; |
| g /= rhs; |
| b /= rhs; |
| return *this; |
| } |
| }; |
| |
| // colorize returns a 'rainbow-color' for the scalar v. |
| inline Color<float> colorize(float v) { |
| constexpr float PI = 3.141592653589793f; |
| constexpr float PI_2_THIRDS = 2.0f * PI / 3.0f; |
| return Color<float>{ |
| 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<float> 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<float>(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<uint8_t>* 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<uint8_t>(val)); |
| put1(static_cast<uint8_t>(val >> 8)); }; |
| auto put4 = [&](uint32_t val) { put2(static_cast<uint16_t>(val)); |
| put2(static_cast<uint16_t>(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<uint8_t>[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<float> 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<uint8_t>(color.r * 255), |
| static_cast<uint8_t>(color.g * 255), |
| static_cast<uint8_t>(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; |
| } |