Skip to content

Commit

Permalink
functional mandelbrot
Browse files Browse the repository at this point in the history
  • Loading branch information
antonkesy committed Jan 28, 2024
1 parent f660826 commit 2488d1e
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 43 deletions.
14 changes: 7 additions & 7 deletions benchmarks/mandelbrot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,26 @@
#include "../src/mandelbrot/openmp.hpp"
#include "../src/mandelbrot/sequential.hpp"

// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables)
// static volatile int dummy = 0;
// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables,misc-use-anonymous-namespace)
static volatile float dummy = 0;

// NOLINTNEXTLINE(readability-identifier-naming,misc-use-anonymous-namespace)
static void BM_MANDELBROT(
benchmark::State &state,
std::unique_ptr<mandelbrot_visualizer::Mandelbrot> mandel) {
std::optional<mandelbrot_visualizer::MandelbrotData> tmp = std::nullopt;
for ([[maybe_unused]] auto _ : state) {
mandel->Compute(false);
tmp = std::move(mandel->Compute(false));
}

// TODO(ak): compute should return list of pixels to avoid optimization
// dummy = A[0];
dummy = tmp.value().pixels[0].r;
}

auto KSettings() {
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers)
return mandelbrot_visualizer::Settings{
.height = 900,
.width = 900,
.height = 1000,
.width = 1000,
.max_iterations = 1000,
.progress = std::make_shared<float>(0),
.area = {.start = {-2.0F, -1.0F}, .end = {1.0F, 1.0F}}};
Expand Down
26 changes: 16 additions & 10 deletions src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ int main(int argc, char** argv) {
BootstrapOmpCancel(argc, argv);

using mandelbrot_visualizer::Mandelbrot;
using mandelbrot_visualizer::MandelbrotData;
using mandelbrot_visualizer::OpenMPMandelbrot;
using mandelbrot_visualizer::SequentialMandelbrot;
using mandelbrot_visualizer::Settings;
Expand All @@ -31,7 +32,7 @@ int main(int argc, char** argv) {
const auto start_size = 900;
Window window("Mandelbrot Visualizer", start_size, start_size);

std::unique_ptr<Mandelbrot> mandelbrot = nullptr;
std::optional<MandelbrotData> mandelbrot = std::nullopt;

VisualizerState current_state;
VisualizerState last_state = current_state;
Expand All @@ -40,13 +41,13 @@ int main(int argc, char** argv) {

std::atomic<bool> request_stop{false};
// FIXME: replace with stack with mutex ... or something
std::vector<std::future<std::unique_ptr<Mandelbrot>>> results{};
std::vector<std::future<std::optional<MandelbrotData>>> results{};

const auto render_mandelbrot = [&](const WindowInfo& window) {
current_state.display_width = window.width;
current_state.display_height = window.height;

if (mandelbrot != nullptr && window.mouse_selection.has_value()) {
if (mandelbrot.has_value() && window.mouse_selection.has_value()) {
const auto selection = window.mouse_selection;
const auto before = current_state.area;
// square constraint
Expand All @@ -69,11 +70,11 @@ int main(int argc, char** argv) {
}

if (current_state.NeedsRecomputation(last_state) ||
(mandelbrot == nullptr && results.empty())) {
mandelbrot = nullptr;
(!mandelbrot.has_value() && results.empty())) {
mandelbrot = std::nullopt;
request_stop = true;

const auto compute = [&]() -> std::unique_ptr<Mandelbrot> {
const auto compute = [&]() -> auto {
Settings settings{
window.height,
window.width,
Expand All @@ -92,12 +93,13 @@ int main(int argc, char** argv) {
}
}();

std::optional<MandelbrotData> data = std::nullopt;
request_stop = false;
current_state.is_computing = true;
current_state.compute_time =
Stopwatch::Time([&]() { next->Compute(request_stop); });
Stopwatch::Time([&]() { data = next->Compute(request_stop); });
current_state.is_computing = false;
return next;
return std::move(data);
};

results.emplace_back(std::async(std::launch::async, compute));
Expand All @@ -116,15 +118,19 @@ int main(int argc, char** argv) {
case std::future_status::ready:
mandelbrot = result.get();
// invalidate computation if there are more futures available
if (results.size() > 1) mandelbrot = nullptr;
if (results.size() > 1) mandelbrot = std::nullopt;
results.clear();
break;
}
}
}

current_state.draw_time = Stopwatch::Time([&]() {
if (mandelbrot != nullptr) mandelbrot->Draw();
if (mandelbrot.has_value()) {
glRasterPos2i(-1, -1);
glDrawPixels(mandelbrot->width, mandelbrot->height, GL_RGBA, GL_FLOAT,
mandelbrot->pixels.data());
}
});
last_state = current_state;
};
Expand Down
2 changes: 1 addition & 1 deletion src/mandelbrot/mandelbrot.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ int Mandelbrot::Iteration(const std::complex<double> &c) const {
return n - static_cast<int>(std::log(std::log2(std::abs(z))));
}

ImVec4 Mandelbrot::MandelbrotColor(const std::complex<double> &c) const {
Color Mandelbrot::MandelbrotColor(const std::complex<double> &c) const {
// TODO(ak): add more color schemes
// https://en.wikipedia.org/wiki/Plotting_algorithms_for_the_Mandelbrot_set

Expand Down
41 changes: 33 additions & 8 deletions src/mandelbrot/mandelbrot.hpp
Original file line number Diff line number Diff line change
@@ -1,19 +1,45 @@
#pragma once

#include <GLFW/glfw3.h>
#include <imgui.h>
#include <imgui_impl_glfw.h>
#include <imgui_impl_opengl3.h>

#include <atomic>
#include <complex>
#include <memory>
#include <optional>
#include <vector>

#include "settings.hpp"

namespace mandelbrot_visualizer {

struct Color {
float r;
float g;
float b;
float a;

bool operator==(const Color &other) const {
return (r == other.r) && (g == other.g) && (b == other.b) && (a == other.a);
}

bool operator!=(const Color &other) const { return !(*this == other); }
};

struct MandelbrotData {
int height;
int width;
std::vector<Color> pixels;

bool operator==(const MandelbrotData &other) const {
return (height == other.height) && (width == other.width) &&
(pixels == other.pixels);
}

bool operator!=(const MandelbrotData &other) const {
return !(*this == other);
}
};

class Mandelbrot {
public:
explicit Mandelbrot(const Settings &settings)
Expand All @@ -29,24 +55,23 @@ class Mandelbrot {
Mandelbrot &operator=(const Mandelbrot &) = delete;
Mandelbrot &operator=(Mandelbrot &&) = delete;

// not best desing -> can be forgotten to call ...
virtual void Compute(const std::atomic<bool> &request_stop) = 0;
virtual void Draw() = 0;
virtual std::optional<MandelbrotData> Compute(
const std::atomic<bool> &request_stop) = 0;

[[nodiscard]] static std::complex<double> PixelToComplex(int x, int y,
int width,
int height,
Settings::Area area);

[[nodiscard]] ImVec4 MandelbrotColor(const std::complex<double> &c) const;
[[nodiscard]] Color MandelbrotColor(const std::complex<double> &c) const;

[[nodiscard]] int Iteration(const std::complex<double> &c) const;

protected:
// NOLINTBEGIN(cppcoreguidelines-non-private-member-variables-in-classes,cppcoreguidelines-avoid-const-or-ref-data-members)
const int height;
const int width;
std::vector<ImVec4> pixels;
std::vector<Color> pixels;
const int max_iterations;
const std::shared_ptr<float> progress;
const Settings::Area area;
Expand Down
12 changes: 6 additions & 6 deletions src/mandelbrot/openmp.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ class OpenMPMandelbrot : public Mandelbrot {
}
}

void Compute(const std::atomic<bool> &request_stop) override {
std::optional<MandelbrotData> Compute(
const std::atomic<bool> &request_stop) override {
*progress = 0;
#pragma omp parallel shared(pixels, request_stop) default(none)
#pragma omp for schedule(dynamic)
Expand All @@ -31,13 +32,12 @@ class OpenMPMandelbrot : public Mandelbrot {
pixels[y * width + x] = MandelbrotColor(c);
}
}

if (request_stop) {
return std::nullopt;
}
*progress = 1;
}

void Draw() override {
glRasterPos2i(-1, -1);
glDrawPixels(width, height, GL_RGBA, GL_FLOAT, pixels.data());
return std::make_optional<MandelbrotData>({height, width, pixels});
}
};

Expand Down
11 changes: 4 additions & 7 deletions src/mandelbrot/sequential.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,19 @@ class SequentialMandelbrot : public Mandelbrot {
explicit SequentialMandelbrot(const Settings& settings)
: Mandelbrot(settings) {}

void Compute(const std::atomic<bool>& request_stop) override {
std::optional<MandelbrotData> Compute(
const std::atomic<bool>& request_stop) override {
*progress = 0;
for (int y = 0; y < height; ++y) {
*progress = static_cast<float>(y) / static_cast<float>(height);
for (int x = 0; x < width; ++x) {
if (request_stop) return;
if (request_stop) return std::nullopt;
std::complex<double> c = PixelToComplex(x, y, width, height, area);
pixels[y * width + x] = MandelbrotColor(c);
}
}
*progress = 1;
}

void Draw() override {
glRasterPos2i(-1, -1);
glDrawPixels(width, height, GL_RGBA, GL_FLOAT, pixels.data());
return std::make_optional<MandelbrotData>({height, width, pixels});
}
};

Expand Down
3 changes: 1 addition & 2 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
enable_testing()
# find_package(GTest REQUIRED)

add_executable(test_mandelbrot mandelbrot.cpp)
target_link_libraries(test_mandelbrot PUBLIC GTest::gtest_main)
target_link_libraries(test_mandelbrot PUBLIC mandelbrot_lib GTest::gtest_main)
add_test(test_mandelbrot test_mandelbrot)
28 changes: 26 additions & 2 deletions tests/mandelbrot.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,30 @@
#include <gtest/gtest.h>

// TODO(ak): add tests
#include "../src/mandelbrot/openmp.hpp"
#include "../src/mandelbrot/sequential.hpp"

auto TestSettings() {
// NOLINTBEGIN(cppcoreguidelines-avoid-magic-numbers)
return mandelbrot_visualizer::Settings{
.height = 1000,
.width = 1000,
.max_iterations = 1000,
.progress = std::make_shared<float>(0),
.area = {.start = {-2.0F, -1.0F}, .end = {1.0F, 1.0F}}};
// NOLINTEND(cppcoreguidelines-avoid-magic-numbers)
}

// NOLINTNEXTLINE(cert-err58-cpp,cppcoreguidelines-avoid-non-const-global-variables,cppcoreguidelines-owning-memory)
TEST(TemplateTest, BasicAssertions) { EXPECT_EQ(7 * 6, 42); }
TEST(MandelbrotTest, Oracle) {
using mandelbrot_visualizer::OpenMPMandelbrot;
using mandelbrot_visualizer::SequentialMandelbrot;

const auto reference = SequentialMandelbrot(TestSettings()).Compute(false);
EXPECT_TRUE(reference.has_value());

const auto open_mp = OpenMPMandelbrot(TestSettings()).Compute(false);
EXPECT_TRUE(open_mp.has_value());

// NOLINTNEXTLINE(bugprone-unchecked-optional-access)
EXPECT_EQ(*reference, *open_mp);
}

0 comments on commit 2488d1e

Please sign in to comment.