From 4bb7c3fcbe7d94e76b8e954c945f9d4b2644960f Mon Sep 17 00:00:00 2001 From: Lauchmelder Date: Sat, 19 Feb 2022 14:23:46 +0100 Subject: [PATCH] i think its done --- src/Application.cpp | 83 +++++++++++++++- src/Application.hpp | 10 +- src/CMakeLists.txt | 2 +- src/Canvas.cpp | 228 +++++++++++++++++++++++++++++++++----------- src/Canvas.hpp | 33 ++++++- src/Shader.cpp | 117 +++++++++++++++++++++++ src/Shader.hpp | 21 ++++ src/Window.hpp | 6 +- 8 files changed, 434 insertions(+), 66 deletions(-) create mode 100644 src/Shader.cpp create mode 100644 src/Shader.hpp diff --git a/src/Application.cpp b/src/Application.cpp index 5e2c03f..7ec4ace 100644 --- a/src/Application.cpp +++ b/src/Application.cpp @@ -2,12 +2,13 @@ #include "Application.hpp" #include +#include #include "backends/imgui_impl_glfw.h" #include "backends/imgui_impl_opengl3.h" Application::Application() : - window(new Window(1280, 720, "Mandelbrot")), canvas(nullptr), backgroundColor{ 0.1f, 0.01f, 0.19f } + window(new Window(1280, 720, "Julia Sets")), canvas(nullptr) { window->MakeContextCurrent(); @@ -28,6 +29,27 @@ Application::Application() : ImGui_ImplOpenGL3_Init("#version 460 core"); ImGui::StyleColorsDark(); + + data.lastMousePos = { 0.0, 0.0 }; + data.mouseDelta = { 0.0, 0.0 }; + data.wheel = { 0.0, 0.0 }; + + GLFWwindow* nativeHandle = window->GetHandle(); + glfwSetWindowUserPointer(nativeHandle, (void*)&data); + + if (glfwRawMouseMotionSupported()) + glfwSetInputMode(nativeHandle, GLFW_RAW_MOUSE_MOTION, GLFW_TRUE); + + + glfwSetScrollCallback(nativeHandle, + [] (GLFWwindow* window, double x, double y) + { + WindowData* data = (WindowData*)glfwGetWindowUserPointer(window); + + data->wheel.x = x; + data->wheel.y = y; + } + ); } Application::~Application() @@ -41,8 +63,10 @@ void Application::Launch() { while (!window->ShouldClose()) { + canvas->CalculateJuliaSet(); + glfwPollEvents(); - glClearColor(backgroundColor[0], backgroundColor[1], backgroundColor[2], 1.0f); + glClearColor(0.0f, 0.0f, 0.0f, 1.0f); glClear(GL_COLOR_BUFFER_BIT); ImGui_ImplOpenGL3_NewFrame(); @@ -51,8 +75,59 @@ void Application::Launch() canvas->Render(); - ImGui::Begin("Settings"); - ImGui::SliderFloat3("Background Color", backgroundColor, 0.0f, 1.0f); + JuliaProperties& props = canvas->GetProperties(); + int width, height; + window->GetWindowSize(width, height); + props.aspectRatio = (float)height / (float)width; + + double mouseX, mouseY; + glfwGetCursorPos(window->GetHandle(), &mouseX, &mouseY); + + data.mouseDelta.x = mouseX - data.lastMousePos.x; + data.mouseDelta.y = mouseY - data.lastMousePos.y; + + data.lastMousePos.x = mouseX; + data.lastMousePos.y = mouseY; + + float xSize = props.xBounds[1] - props.xBounds[0]; + + if (glfwGetMouseButton(window->GetHandle(), GLFW_MOUSE_BUTTON_LEFT)) + { + float stepSize = xSize / (float)width; + + props.xBounds[0] -= data.mouseDelta.x * stepSize; + props.xBounds[1] -= data.mouseDelta.x * stepSize; + + props.yCenter += data.mouseDelta.y * stepSize; + } + + if (data.wheel.y != 0.0) + { + props.xBounds[0] += data.wheel.y * (xSize / 10.0f); + props.xBounds[1] -= data.wheel.y * (xSize / 10.0f); + } + + data.wheel = { 0.0, 0.0 }; + + const WorkProperties& workProps = canvas->GetWorkProperties(); + + ImGui::Begin("Julia Set Properties"); + + ImGui::SliderInt("max iterations", (int*)&props.maxIterations, 10, 1000); + ImGui::SliderFloat("max color", &props.iterationColorCutoff, 10, 1000); + ImGui::SliderInt("texture width", (int*)&props.textureWidth, 480, 2560); + + ImGui::SliderFloat2("c param", props.c, -1.5f, 1.5f); + + if (ImGui::Button(props.doublePrecision ? "Double Precision" : "Single Precision")) + props.doublePrecision = !props.doublePrecision; + + ImGui::Separator(); + + ImGui::Text("Max work groups - x: %i, y: %i, z: %i", workProps.groupCount[0], workProps.groupCount[1], workProps.groupCount[2]); + ImGui::Text("Max group sizes - x: %i, y: %i, z: %i", workProps.groupSize[0], workProps.groupSize[1], workProps.groupSize[2]); + ImGui::Text("Max invocations - %i", workProps.maxInvocations); + ImGui::End(); ImGui::Render(); diff --git a/src/Application.hpp b/src/Application.hpp index 9434547..4a52438 100644 --- a/src/Application.hpp +++ b/src/Application.hpp @@ -3,6 +3,14 @@ #include "Window.hpp" #include "Canvas.hpp" +struct WindowData +{ + struct + { + double x, y; + } mouseDelta, lastMousePos, wheel; +}; + class Application { public: @@ -15,5 +23,5 @@ private: Window* window; Canvas* canvas; - float backgroundColor[3]; + WindowData data; }; \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 224d12f..2c84cf5 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -4,7 +4,7 @@ cmake_minimum_required (VERSION 3.8) # Add source to this project's executable. -add_executable (Mandelbrot "main.cpp" "Window.cpp" "Application.cpp" "Canvas.cpp") +add_executable (Mandelbrot "main.cpp" "Window.cpp" "Application.cpp" "Canvas.cpp" "Shader.cpp") target_sources(Mandelbrot PRIVATE ${IMGUI_SOURCE_FILES} diff --git a/src/Canvas.cpp b/src/Canvas.cpp index 2928c31..dd775ef 100644 --- a/src/Canvas.cpp +++ b/src/Canvas.cpp @@ -2,20 +2,41 @@ #include #include +#include +#include #include -Canvas::Canvas() : - vao(0), vbo(0), shader(0) +double Map(double fromMin, double fromMax, double toMin, double toMax, double val) { + return (val - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin; +} + +Canvas::Canvas() : + vao(0), vbo(0), texture(0) +{ + properties.xBounds[0] = -2.5f; + properties.xBounds[1] = 2.5f; + properties.yCenter = 0.0f; + properties.aspectRatio = 9.0f / 16.0f; + properties.textureWidth = 1920; + properties.maxIterations = 100; + properties.iterationColorCutoff = 100.0f; + properties.c[0] = -0.835; + properties.c[1] = -0.2321; + properties.doublePrecision = false; + CreateVertexArrayObject(); CreateShaderProgram(); + CreateTexture(); + + CreateCompueShader(); } Canvas::~Canvas() { - if (shader) - glDeleteProgram(shader); + if (texture) + glDeleteTextures(1, &texture); if (vbo) glDeleteBuffers(1, &vbo); @@ -26,19 +47,60 @@ Canvas::~Canvas() void Canvas::Render() { - glUseProgram(shader); - glBindVertexArray(vao); + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); + shader.Use(); + glBindTexture(GL_TEXTURE_2D, texture); + + glBindVertexArray(vao); glDrawArrays(GL_TRIANGLE_FAN, 0, 4); } +void Canvas::CalculateJuliaSet() +{ + glMemoryBarrier(GL_SHADER_IMAGE_ACCESS_BARRIER_BIT); + + int width = properties.textureWidth; + int height = properties.textureWidth * properties.aspectRatio; + + float yLength = (properties.xBounds[1] - properties.xBounds[0]) * properties.aspectRatio; + float yMin = properties.yCenter - 0.5f * yLength; + float yMax = properties.yCenter + 0.5f * yLength; + + glBindTexture(GL_TEXTURE_2D, texture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA32F, width, height, 0, GL_RGBA, GL_FLOAT, nullptr); + glGenerateMipmap(GL_TEXTURE_2D); + glBindImageTexture(0, texture, 0, GL_FALSE, 0, GL_WRITE_ONLY, GL_RGBA32F); + + if (properties.doublePrecision) + doubleComputeShader.Use(); + else + computeShader.Use(); + + glUniform2f(1, properties.xBounds[0], properties.xBounds[1]); + glUniform2f(2, yMin, yMax); + glUniform2f(3, properties.c[0], properties.c[1]); + glUniform1i(4, properties.maxIterations); + glUniform1f(5, properties.iterationColorCutoff); + + glDispatchCompute(width, height, 1); + + // delete[] image; +} + void Canvas::CreateVertexArrayObject() { - float vertices[4 * 2] = { - -0.9f, -0.9f, - -0.9f, 0.9f, - 0.9f, 0.9f, - 0.9f, -0.9f + float vertices[4 * (2 + 2)] = { + -1.0f, -1.0f, 0.0f, 0.0f, + -1.0f, 1.0f, 0.0f, 1.0f, + 1.0f, 1.0f, 1.0f, 1.0f, + 1.0f, -1.0f, 1.0f, 0.0f }; glGenVertexArrays(1, &vao); @@ -48,77 +110,127 @@ void Canvas::CreateVertexArrayObject() glBindBuffer(GL_ARRAY_BUFFER, vbo); glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), (const void*)&vertices, GL_STATIC_DRAW); - glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(float), (const void*)0); + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const void*)0); glEnableVertexAttribArray(0); + + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 4 * sizeof(float), (const void*)(2 * sizeof(float))); + glEnableVertexAttribArray(1); } void Canvas::CreateShaderProgram() { - GLint result; - char infoLog[512]; - - shader = glCreateProgram(); - - GLuint vertexShader = glCreateShader(GL_VERTEX_SHADER); std::string vertexShaderSource = R"( #version 460 core layout (location = 0) in vec2 pos; + layout (location = 1) in vec2 uv; + + out vec2 uvCoord; void main() { + uvCoord = uv; gl_Position = vec4(pos, 0.0f, 1.0f); } )"; - const char* shaderSourceCString = vertexShaderSource.c_str(); - glShaderSource(vertexShader, 1, &shaderSourceCString, NULL); - glCompileShader(vertexShader); + shader.AttachVertexShader(vertexShaderSource); - glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result); - if (!result) - { - glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); - glDeleteShader(vertexShader); - throw std::runtime_error("Failed to compile vertex shader\n" + std::string(infoLog)); - } - - GLuint fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); std::string fragmentShaderSource = R"( #version 460 core + in vec2 uvCoord; out vec4 FragColor; + uniform sampler2D canvas; + void main() { - FragColor = vec4(0.1f, 0.3f, 0.3f, 1.0f); + FragColor = texture(canvas, uvCoord); } )"; - shaderSourceCString = fragmentShaderSource.c_str(); - glShaderSource(fragmentShader, 1, &shaderSourceCString, NULL); - glCompileShader(fragmentShader); + shader.AttachFragmentShader(fragmentShaderSource); - glAttachShader(shader, vertexShader); - glAttachShader(shader, fragmentShader); - glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result); - if (!result) - { - glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); - glDeleteShader(fragmentShader); - glDeleteShader(vertexShader); - throw std::runtime_error("Failed to compile fragment shader\n" + std::string(infoLog)); - } - - glLinkProgram(shader); - - glGetProgramiv(shader, GL_LINK_STATUS, &result); - if (!result) - { - glGetProgramInfoLog(shader, 512, NULL, infoLog); - glDeleteShader(fragmentShader); - glDeleteShader(vertexShader); - throw std::runtime_error("Failed to link shader program\n" + std::string(infoLog)); - } - - glDeleteShader(fragmentShader); - glDeleteShader(vertexShader); + shader.Link(); +} + +void Canvas::CreateCompueShader() +{ + QueryWorkGroupInfo(); + + std::string shaderSource = R"( + #version 460 core + + layout(local_size_x = 1, local_size_y = 1) in; + layout(rgba32f, binding = 0) uniform image2D img_out; + layout(location = 1) uniform vec2 xDomain; + layout(location = 2) uniform vec2 yDomain; + layout(location = 3) uniform vec2 c; + layout(location = 4) uniform int maxIterations; + layout(location = 5) uniform float iterationColorCutoff; + + double map(double fromMin, double fromMax, double toMin, double toMax, double val) + { + return (val - fromMin) * (toMax - toMin) / (fromMax - fromMin) + toMin; + } + + dvec2 complexMul(dvec2 a, dvec2 b) + { + return dvec2(a.x * b.x - a.y * b.y, a.x * b.y + a.y * b.x); + } + + void main() + { + vec4 pixel = vec4(0.0f, 0.05f, 0.2f, 1.0f); + + ivec2 pixel_coords = ivec2(gl_GlobalInvocationID.xy); + ivec2 image_size = ivec2(gl_NumWorkGroups.xy); + + double threshold = 0.5f * (sqrt(4 * length(c) + 1) + 1); + + dvec2 z = dvec2( + map(0, image_size.x, xDomain.x, xDomain.y, pixel_coords.x), + map(0, image_size.y, yDomain.x, yDomain.y, pixel_coords.y) + ); + + for(int i = 0; i < maxIterations; i++) + { + if(length(z) > threshold) + { + pixel.x = float(i) / iterationColorCutoff; + break; + } + + z = complexMul(z, z) + c; + } + + imageStore(img_out, pixel_coords, pixel); + } + )"; + doubleComputeShader.AttachComputeShader(shaderSource); + doubleComputeShader.Link(); + + shaderSource = std::regex_replace(shaderSource, std::regex("double"), "float"); + shaderSource = std::regex_replace(shaderSource, std::regex("dvec2"), "vec2"); + computeShader.AttachComputeShader(shaderSource); + computeShader.Link(); +} + +void Canvas::CreateTexture() +{ + glGenTextures(1, &texture); + + // CalculateJuliaSet(); +} + +void Canvas::QueryWorkGroupInfo() +{ + glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 0, &workProperties.groupCount[0]); + glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 1, &workProperties.groupCount[1]); + glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_COUNT, 2, &workProperties.groupCount[2]); + + glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 0, &workProperties.groupSize[0]); + glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 1, &workProperties.groupSize[1]); + glGetIntegeri_v(GL_MAX_COMPUTE_WORK_GROUP_SIZE, 2, &workProperties.groupSize[2]); + + glGetIntegerv(GL_MAX_COMPUTE_WORK_GROUP_INVOCATIONS, &workProperties.maxInvocations); } diff --git a/src/Canvas.hpp b/src/Canvas.hpp index 843379e..afad46b 100644 --- a/src/Canvas.hpp +++ b/src/Canvas.hpp @@ -1,6 +1,26 @@ #pragma once #include +#include "Shader.hpp" + +struct JuliaProperties +{ + float xBounds[2]; + float yCenter; + float aspectRatio; + uint32_t maxIterations; + float iterationColorCutoff; + uint32_t textureWidth; + float c[2]; + bool doublePrecision; +}; + +struct WorkProperties +{ + int groupCount[3]; + int groupSize[3]; + int maxInvocations; +}; class Canvas { @@ -9,12 +29,23 @@ public: ~Canvas(); void Render(); + void CalculateJuliaSet(); + inline JuliaProperties& GetProperties() { return properties; } + inline const WorkProperties& GetWorkProperties() { return workProperties; } private: void CreateVertexArrayObject(); void CreateShaderProgram(); + void CreateCompueShader(); + void CreateTexture(); + + void QueryWorkGroupInfo(); private: uint32_t vao, vbo; - uint32_t shader; + Shader shader, computeShader, doubleComputeShader; + uint32_t texture; + + JuliaProperties properties; + WorkProperties workProperties; }; \ No newline at end of file diff --git a/src/Shader.cpp b/src/Shader.cpp new file mode 100644 index 0000000..3b07643 --- /dev/null +++ b/src/Shader.cpp @@ -0,0 +1,117 @@ +#include "Shader.hpp" + +#include +#include + +Shader::Shader() : + program(0), vertexShader(0), fragmentShader(0), computeShader(0) +{ + program = glCreateProgram(); +} + +Shader::~Shader() +{ + if (vertexShader) + glDeleteShader(vertexShader); + + if (fragmentShader) + glDeleteShader(fragmentShader); + + if (computeShader) + glDeleteShader(computeShader); + + glDeleteProgram(program); +} + +void Shader::AttachVertexShader(const std::string& vertexSource) +{ + GLint result; + char infoLog[512]; + + vertexShader = glCreateShader(GL_VERTEX_SHADER); + const char* shaderSourceCString = vertexSource.c_str(); + glShaderSource(vertexShader, 1, &shaderSourceCString, NULL); + glCompileShader(vertexShader); + + glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &result); + if (!result) + { + glGetShaderInfoLog(vertexShader, 512, NULL, infoLog); + glDeleteShader(vertexShader); + throw std::runtime_error("Failed to compile vertex shader\n" + std::string(infoLog)); + } + + glAttachShader(program, vertexShader); +} + +void Shader::AttachFragmentShader(const std::string& fragmentSource) +{ + GLint result; + char infoLog[512]; + + fragmentShader = glCreateShader(GL_FRAGMENT_SHADER); + const char* shaderSourceCString = fragmentSource.c_str(); + glShaderSource(fragmentShader, 1, &shaderSourceCString, NULL); + glCompileShader(fragmentShader); + + glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &result); + if (!result) + { + glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog); + glDeleteShader(fragmentShader); + throw std::runtime_error("Failed to compile fragment shader\n" + std::string(infoLog)); + } + + glAttachShader(program, fragmentShader); +} + +void Shader::AttachComputeShader(const std::string& computeSource) +{ + GLint result; + char infoLog[512]; + + computeShader = glCreateShader(GL_COMPUTE_SHADER); + const char* shaderSourceCString = computeSource.c_str(); + glShaderSource(computeShader, 1, &shaderSourceCString, NULL); + glCompileShader(computeShader); + + glGetShaderiv(computeShader, GL_COMPILE_STATUS, &result); + if (!result) + { + glGetShaderInfoLog(computeShader, 512, NULL, infoLog); + glDeleteShader(computeShader); + throw std::runtime_error("Failed to compile compute shader\n" + std::string(infoLog)); + } + + glAttachShader(program, computeShader); +} + +void Shader::Link() +{ + GLint result; + char infoLog[512]; + + glLinkProgram(program); + glGetProgramiv(program, GL_LINK_STATUS, &result); + if (!result) + { + glGetProgramInfoLog(program, 512, NULL, infoLog); + glDeleteShader(fragmentShader); + glDeleteShader(vertexShader); + throw std::runtime_error("Failed to link shader program\n" + std::string(infoLog)); + } + + if (vertexShader) + glDeleteShader(vertexShader); + + if (fragmentShader) + glDeleteShader(fragmentShader); + + if (computeShader) + glDeleteShader(computeShader); +} + +void Shader::Use() +{ + glUseProgram(program); +} \ No newline at end of file diff --git a/src/Shader.hpp b/src/Shader.hpp new file mode 100644 index 0000000..a18e6f3 --- /dev/null +++ b/src/Shader.hpp @@ -0,0 +1,21 @@ +#pragma once + +#include + +class Shader +{ +public: + Shader(); + ~Shader(); + + void AttachVertexShader(const std::string& vertexSource); + void AttachFragmentShader(const std::string& fragmentSource); + void AttachComputeShader(const std::string& computeSource); + void Link(); + + void Use(); + +private: + int program; + int vertexShader, fragmentShader, computeShader; +}; \ No newline at end of file diff --git a/src/Window.hpp b/src/Window.hpp index 540b6df..4b7639b 100644 --- a/src/Window.hpp +++ b/src/Window.hpp @@ -11,11 +11,15 @@ public: ~Window(); inline bool ShouldClose() { return glfwWindowShouldClose(handle); } + inline void SetShouldClose(int value) { return glfwSetWindowShouldClose(handle, value); } inline void MakeContextCurrent() { glfwMakeContextCurrent(handle); } - void Display(); + inline void GetWindowSize(int& width, int& height) { glfwGetWindowSize(handle, &width, &height); } + void Display(); void InitImGui(); + inline GLFWwindow* GetHandle() { return handle; } + private: GLFWwindow* handle; }; \ No newline at end of file